__init__.py 7.25 KB
Newer Older
Mario Hock's avatar
Mario Hock committed
1
2
3
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

Mario Hock's avatar
Mario Hock committed
4
5
6
7
8
9
10
11
12
# Copyright (c) 2014,
# Karlsruhe Institute of Technology, Institute of Telematics
#
# This code is provided under the BSD 2-Clause License.
# Please refer to the LICENSE.txt file for further information.
#
# Author: Mario Hock


Mario Hock's avatar
Mario Hock committed
13
import os
Mario Hock's avatar
Mario Hock committed
14
15
import psutil
import time
Mario Hock's avatar
Mario Hock committed
16
17
import sys
import traceback
18
import math
19
import json
Mario Hock's avatar
Mario Hock committed
20

21
22
23
from collections import namedtuple

import helpers
Mario Hock's avatar
Mario Hock committed
24
import curses_display as ui
25
from logging import LoggingManager
26
from psutil_functions import calculate_cpu_times_percent
27

28
29
30
31
32

def get_time():
    """ Unified/comparable clock access """
    return time.time()

Mario Hock's avatar
Mario Hock committed
33
34
35
36
37
38
39
40
41

## XXX for interactive debugging only
def RELOAD():
    print ("import importlib")
    print ("importlib.reload(cpunetlog)")


MEASUREMENT_INTERVAL = 0.2

Mario Hock's avatar
Mario Hock committed
42
nic_speeds = helpers.get_nic_speeds()
43
nics = list( nic_speeds.keys() )
Mario Hock's avatar
Mario Hock committed
44

45
46

class Reading:
Mario Hock's avatar
Mario Hock committed
47
48
    """ A single reading of various CPU, NET, ... values. --> Building block for the »Measurement« class."""

Mario Hock's avatar
Mario Hock committed
49
50
    def __init__(self):
        ## * measurements *
51
        self.timestamp = get_time()
Mario Hock's avatar
Mario Hock committed
52
53
        #self.cpu_util = psutil.cpu_percent(interval=0, percpu=True)                      ## XXX
        #self.cpu_times_percent = psutil.cpu_times_percent(interval=0, percpu=True)       ## XXX
54
        self.cpu_times = psutil.cpu_times(percpu=True)
Mario Hock's avatar
Mario Hock committed
55
56
        self.memory = psutil.virtual_memory()
        self.net_io = psutil.net_io_counters(pernic=True)
Mario Hock's avatar
Mario Hock committed
57
58

    def __str__(self):
Mario Hock's avatar
Mario Hock committed
59
60
61
62
63
64
        ## •‣∘⁕∗◘☉☀★◾☞☛⦿
        return "◘ Timespan: " + str(self.timespan) +              \
                "\n◘ CPU utilization: " + str(self.cpu_util) +    \
                "\n◘ CPU times: " + str(self.cpu_times) +         \
                "\n◘ RAM: " + str(self.memory) +                  \
                "\n◘ NET: " + str(self.net_io)
Mario Hock's avatar
Mario Hock committed
65
66


67
68

class NetworkTraffic:
Mario Hock's avatar
Mario Hock committed
69
70
    """ Utility class for calculating and storing network traffic: Total amount (during a timespan) and ratio. """

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    def __init__(self, older_counters, younger_counters, timespan):
        self.total = dict()
        self.ratio = dict()

        for field in older_counters._fields:
            field_delta = getattr(younger_counters, field) - getattr(older_counters, field)

            self.total[field] = field_delta
            self.ratio[field] = field_delta / timespan

    def __str__(self):
        return "Total (bytes):" + str(self.total) + "; Ratio (bytes/s)" + str(self.ratio)



class Measurement:
Mario Hock's avatar
Mario Hock committed
87
88
    """ Calculates and stores CPU utilization, network traffic, ... during a timespan. Based two »Readings«. """

89
90
91
92
93
94
    def __init__(self, reading1, reading2):
        self.r1 = reading1
        self.r2 = reading2

        ## calculate differences
        self.timespan = self.r2.timestamp - self.r1.timestamp
95
        self.cpu_times_percent = calculate_cpu_times_percent(self.r1.cpu_times, self.r2.cpu_times, percpu=True)
96
97
98
99
100
101
102
103
104
105
106
        self.net_io = self._calculate_net_io()


    def _calculate_net_io(self):
        ret = dict()

        for nic in self.r1.net_io.keys():
            ret[nic] = NetworkTraffic(self.r1.net_io[nic], self.r2.net_io[nic], self.timespan)

        return ret

Mario Hock's avatar
Mario Hock committed
107
108
109
110
111
112
    def get_begin(self):
        return self.r1.timestamp

    def get_end(self):
        return self.r2.timestamp

113
114
115


def measure(interval = MEASUREMENT_INTERVAL):
Mario Hock's avatar
Mario Hock committed
116
117
    """ Convenience function to perform one »Measurement« """

118
119
120
121
122
123
124
125
126
127
    r1 = Reading()
    time.sleep(interval)
    r2 = Reading()

    m = Measurement(r1, r2)

    return m



Mario Hock's avatar
Mario Hock committed
128

129
130
131
132
133
def main_loop():
    """ Main Loop:
      - Sets up curses-display
      - Takes a reading every second
      - Displays the measurements
134
      - Logs the measurements with the LoggingManager
135
136
    """

Mario Hock's avatar
Mario Hock committed
137
138
139
    err = None

    try:
140
        # Set up (curses) UI.
141
        ui.nics = nics
Mario Hock's avatar
Mario Hock committed
142
        ui.nic_speeds = nic_speeds
Mario Hock's avatar
Mario Hock committed
143
        ui.logging_manager = logging_manager
144
145
146
147
148
        ui.init()

        # Take an initial reading.
        old_reading = Reading()

149
150
151
152
        # Sleep till the next "full" second begins. (In order to roughly synchronize with other instances.)
        now = time.time()
        time.sleep(math.ceil(now)-now)

153
154
155
156
157
158
159
160
        running = True
        while running:
            # Take a new reading.
            new_reading = Reading()

            # Calculate the measurement from the last two readings.
            measurement = Measurement(old_reading, new_reading)

Mario Hock's avatar
Mario Hock committed
161
            # Display/log the measurement.
Mario Hock's avatar
..    
Mario Hock committed
162
            running &= logging_manager.log(measurement)
Mario Hock's avatar
Mario Hock committed
163
            running = ui.display( measurement )
164
165
166

            # Store the last reading as |old_reading|.
            old_reading = new_reading
Mario Hock's avatar
Mario Hock committed
167

168
169
170
171
            time.sleep(1)
                ## XXX We could calculating the remaining waiting-time here.
                #    (But I assume the difference is negligible.)

Mario Hock's avatar
Mario Hock committed
172
173

    except KeyboardInterrupt:
174
        # Quit gracefully on Ctrl-C
Mario Hock's avatar
Mario Hock committed
175
176
        pass
    except Exception as e:
177
        # On error: Store stack trace for later processing.
Mario Hock's avatar
Mario Hock committed
178
179
180
        err = e
        exc_type, exc_value, exc_traceback = sys.exc_info()
    finally:
181
        # Tear down the UI.
Mario Hock's avatar
Mario Hock committed
182
        ui.close()
183
        logging_manager.close()
Mario Hock's avatar
Mario Hock committed
184
185
186
187

    ## On error: Print error message *after* curses has quit.
    if ( err ):
        print( "Unexpected exception happened: '" + str(err) + "'" )
Mario Hock's avatar
Mario Hock committed
188
        print
189

Mario Hock's avatar
Mario Hock committed
190
191
192
193
194
        traceback.print_exception(exc_type, exc_value, exc_traceback, file=sys.stdout)

        print
        print( "QUIT." )

195

196
197
198
199
200
201
202
203
204


## MAIN ##
if __name__ == "__main__":

    ## Command line arguments
    import argparse

    parser = argparse.ArgumentParser()
205
206

    ## Logging
207
208
    parser.add_argument("-l", "--logging", action="store_true",
                        help="Enables logging.")
Mario Hock's avatar
Mario Hock committed
209
210
    parser.add_argument("-A", "--autologging", action="store_true",
                        help="Enables auto-logging. (Log only on network activity. Implies --logging)")
Mario Hock's avatar
Mario Hock committed
211
212
    parser.add_argument("-W", "--watch",
                        help="Store the command-line of the given program as log-comment. (Use together with --autologging.)")
213
214
215
216
    parser.add_argument("-c", "--comment",
                        help="A comment that is stored in the logfile. (See --logging.)")
    parser.add_argument("--path", default="/tmp/cpunetlog",
                        help="Path where the log files are stored in. (See --logging.)")
217
218
219
220
221
222
223
    parser.add_argument("-e", "--environment",
                        help="JSON file that holds arbitrary environment context. (This can be seen as a structured comment field.)")

    # NICs
    parser.add_argument("--nics", nargs='+',
                        help="The network interfaces that should be displayed (and logged, see --logging).")

224
225
    args = parser.parse_args()

Mario Hock's avatar
Mario Hock committed
226

227
228
229
230
231
232
233
234
235
236
237
238
239
240

    ## NICs
    monitored_nics = nics
    if ( args.nics ):
        assert( set(nics).issuperset(args.nics) )
        monitored_nics = args.nics

    ## Read environment file (if given).
    if ( args.environment ):
        with open(args.environment) as f:
            environment = json.load(f)
    else:
        environment = None

Mario Hock's avatar
Mario Hock committed
241
242
243
244
245
    ## --autologging implies --logging
    if ( args.autologging ):
        args.logging = True


246
    ## Logging
247
    logging_manager = LoggingManager( psutil.NUM_CPUS, monitored_nics, helpers.get_sysinfo(), environment,
Mario Hock's avatar
Mario Hock committed
248
                                      args.comment, args.path, args.autologging, args.watch )
249
250
251
252
253
254
    if args.logging:
        logging_manager.enable_measurement_logger()


    # Run the main loop.
    main_loop()
Mario Hock's avatar
Mario Hock committed
255