__init__.py 7.98 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
    """

137
    ## TODO this should be configurable by command line options
138
139
    sample_interval = float(args.interval)
    display_interval = float(args.displayinterval)
140
141
142

    display_skip = max(display_interval / sample_interval, 1)

Mario Hock's avatar
Mario Hock committed
143
144
145
    err = None

    try:
146
        # Set up (curses) UI.
147
        ui.nics = nics
Mario Hock's avatar
Mario Hock committed
148
        ui.nic_speeds = nic_speeds
Mario Hock's avatar
Mario Hock committed
149
        ui.logging_manager = logging_manager
Deathcrow's avatar
Deathcrow committed
150
151
        if not args.headless:
            ui.init()
152
153
154
155

        # Take an initial reading.
        old_reading = Reading()

156
157
158
159
        # Sleep till the next "full" second begins. (In order to roughly synchronize with other instances.)
        now = time.time()
        time.sleep(math.ceil(now)-now)

160
        display_skip_counter = 0
161
162
163
164
165
166
167
168
        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
169
            # Display/log the measurement.
Mario Hock's avatar
..    
Mario Hock committed
170
            running &= logging_manager.log(measurement)
Deathcrow's avatar
Deathcrow committed
171
            if ( display_skip_counter % display_skip < 1 ) and not args.headless:   # the display may skip some samples
172
173
174
                running = ui.display( measurement )
                display_skip_counter = 0
            display_skip_counter += 1
175
176
177

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

179
180
181
            time.sleep(sample_interval)
                ## XXX TODO We could calculating the remaining waiting-time here.

182

Mario Hock's avatar
Mario Hock committed
183
184

    except KeyboardInterrupt:
185
        # Quit gracefully on Ctrl-C
Mario Hock's avatar
Mario Hock committed
186
187
        pass
    except Exception as e:
188
        # On error: Store stack trace for later processing.
Mario Hock's avatar
Mario Hock committed
189
190
191
        err = e
        exc_type, exc_value, exc_traceback = sys.exc_info()
    finally:
192
        # Tear down the UI.
Deathcrow's avatar
Deathcrow committed
193
194
        if not args.headless:
            ui.close()
195
        logging_manager.close()
Mario Hock's avatar
Mario Hock committed
196
197
198
199

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

Mario Hock's avatar
Mario Hock committed
202
203
204
205
206
        traceback.print_exception(exc_type, exc_value, exc_traceback, file=sys.stdout)

        print
        print( "QUIT." )

207

208
209
210
211
212
213
214


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

    ## Command line arguments
    import argparse
Deathcrow's avatar
Deathcrow committed
215
#
216
    parser = argparse.ArgumentParser()
217
218

    ## Logging
219
220
    parser.add_argument("-l", "--logging", action="store_true",
                        help="Enables logging.")
Mario Hock's avatar
Mario Hock committed
221
222
    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
223
224
    parser.add_argument("-W", "--watch",
                        help="Store the command-line of the given program as log-comment. (Use together with --autologging.)")
225
226
227
228
    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.)")
229
230
    parser.add_argument("-e", "--environment",
                        help="JSON file that holds arbitrary environment context. (This can be seen as a structured comment field.)")
231
232
233
234
235
    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]")

236
237
238
239
240

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

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

244
245
    args = parser.parse_args()

Mario Hock's avatar
Mario Hock committed
246

247
248
249
250

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

Mario Hock's avatar
Mario Hock committed
254
255
256
257
258
    ## --autologging implies --logging
    if ( args.autologging ):
        args.logging = True


259
    ## Logging
260
    logging_manager = LoggingManager( psutil.cpu_count(), monitored_nics, helpers.get_sysinfo(), args.environment,
Mario Hock's avatar
Mario Hock committed
261
                                      args.comment, args.path, args.autologging, args.watch )
262
263
264
265
266
267
    if args.logging:
        logging_manager.enable_measurement_logger()


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