__init__.py 8.51 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
20
import signal
Mario Hock's avatar
Mario Hock committed
21

22
23
24
from collections import namedtuple

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

29
30
31
32
def signal_handler(signal, frame):
    global running
    running = False

33
34
35
36
37

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

Mario Hock's avatar
Mario Hock committed
38
39
40
41
42
43
44
45
46

## 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
47
nic_speeds = helpers.get_nic_speeds()
48
nics = list( nic_speeds.keys() )
Mario Hock's avatar
Mario Hock committed
49

50
51

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

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

    def __str__(self):
Mario Hock's avatar
Mario Hock committed
64
65
66
67
68
69
        ## •‣∘⁕∗◘☉☀★◾☞☛⦿
        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
70
71


72
73

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

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
    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
92
93
    """ Calculates and stores CPU utilization, network traffic, ... during a timespan. Based two »Readings«. """

94
95
96
97
98
99
    def __init__(self, reading1, reading2):
        self.r1 = reading1
        self.r2 = reading2

        ## calculate differences
        self.timespan = self.r2.timestamp - self.r1.timestamp
100
        self.cpu_times_percent = calculate_cpu_times_percent(self.r1.cpu_times, self.r2.cpu_times, percpu=True)
101
        self.net_io = self._calculate_net_io()
102
        self.memory = psutil.virtual_memory()
103
104
105
106
107
108
109
110
111

    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
112
113
114
115
116
117
    def get_begin(self):
        return self.r1.timestamp

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

118
119
120


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

123
124
125
126
127
128
129
130
131
132
    r1 = Reading()
    time.sleep(interval)
    r2 = Reading()

    m = Measurement(r1, r2)

    return m



Mario Hock's avatar
Mario Hock committed
133

134
135
136
137
138
def main_loop():
    """ Main Loop:
      - Sets up curses-display
      - Takes a reading every second
      - Displays the measurements
139
      - Logs the measurements with the LoggingManager
140
    """
141
    signal.signal(signal.SIGINT, signal_handler)
142

143
    ## TODO this should be configurable by command line options
144
145
    sample_interval = float(args.interval)
    display_interval = float(args.displayinterval)
146
147
148

    display_skip = max(display_interval / sample_interval, 1)

Mario Hock's avatar
Mario Hock committed
149
150
151
    err = None

    try:
152
        # Set up (curses) UI.
153
        ui.nics = nics
Mario Hock's avatar
Mario Hock committed
154
        ui.nic_speeds = nic_speeds
Mario Hock's avatar
Mario Hock committed
155
        ui.logging_manager = logging_manager
Deathcrow's avatar
Deathcrow committed
156
157
        if not args.headless:
            ui.init()
158
159
160
161

        # Take an initial reading.
        old_reading = Reading()

162
163
164
165
        # Sleep till the next "full" second begins. (In order to roughly synchronize with other instances.)
        now = time.time()
        time.sleep(math.ceil(now)-now)

166
        display_skip_counter = 0
167
168

        global running
169
170
171
172
173
174
175
176
        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
177
            # Display/log the measurement.
Mario Hock's avatar
..    
Mario Hock committed
178
            running &= logging_manager.log(measurement)
179
            running &= not logging_manager.get_killstate()
Deathcrow's avatar
Deathcrow committed
180
            if ( display_skip_counter % display_skip < 1 ) and not args.headless:   # the display may skip some samples
181
182
183
                running = ui.display( measurement )
                display_skip_counter = 0
            display_skip_counter += 1
184
185
186

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

188
189
190
            time.sleep(sample_interval)
                ## XXX TODO We could calculating the remaining waiting-time here.

191

Mario Hock's avatar
Mario Hock committed
192
193

    except KeyboardInterrupt:
194
        # Quit gracefully on Ctrl-C
Mario Hock's avatar
Mario Hock committed
195
196
        pass
    except Exception as e:
197
        # On error: Store stack trace for later processing.
Mario Hock's avatar
Mario Hock committed
198
199
200
        err = e
        exc_type, exc_value, exc_traceback = sys.exc_info()
    finally:
201
        # Tear down the UI.
Deathcrow's avatar
Deathcrow committed
202
203
        if not args.headless:
            ui.close()
204
        logging_manager.close()
Mario Hock's avatar
Mario Hock committed
205
206
207
208

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

Mario Hock's avatar
Mario Hock committed
211
212
213
214
215
        traceback.print_exception(exc_type, exc_value, exc_traceback, file=sys.stdout)

        print
        print( "QUIT." )

216

217
218
219
220
221
222
223


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

    ## Command line arguments
    import argparse
Deathcrow's avatar
Deathcrow committed
224
#
225
    parser = argparse.ArgumentParser()
226
227

    ## Logging
228
229
    parser.add_argument("-l", "--logging", action="store_true",
                        help="Enables logging.")
Mario Hock's avatar
Mario Hock committed
230
231
    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
232
233
    parser.add_argument("-W", "--watch",
                        help="Store the command-line of the given program as log-comment. (Use together with --autologging.)")
234
235
236
237
    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.)")
238
239
    parser.add_argument("-e", "--environment",
                        help="JSON file that holds arbitrary environment context. (This can be seen as a structured comment field.)")
240
241
242
243
    parser.add_argument("-i", "--interval", default="0.5",
                        help="Time between two samples (in seconds). [Default = 0.5]")
    parser.add_argument("-d", "--displayinterval", default="1",
                        help="Time between two display updates (in seconds). [Default = 1]")
244
245
    parser.add_argument("-k", "--killswitch", action="store_true",
                        help="Stop auto-logging after first traffic. Implies --autologging")
246

247
248
249
250
251

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

Deathcrow's avatar
Deathcrow committed
252
253
254
    parser.add_argument("-q", "--headless", action="store_true", 
                        help="Run in quiet/headless mode without GUI")

255
256
    args = parser.parse_args()

Mario Hock's avatar
Mario Hock committed
257

258
259
260
261

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

265
266
267
268
    ## --killswitch implies --autologging
    if ( args.killswitch ):
        args.autologging = True

Mario Hock's avatar
Mario Hock committed
269
270
271
272
273
    ## --autologging implies --logging
    if ( args.autologging ):
        args.logging = True


274
    ## Logging
275
    logging_manager = LoggingManager( psutil.cpu_count(), monitored_nics, helpers.get_sysinfo(), args.environment,
276
                                      args.comment, args.path, args.autologging, args.watch, args.killswitch )
277
278
279
280
281
282
    if args.logging:
        logging_manager.enable_measurement_logger()


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