__init__.py 7.75 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
150
151
152
153
154
        ui.init()

        # Take an initial reading.
        old_reading = Reading()

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

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

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

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

181

Mario Hock's avatar
Mario Hock committed
182
183

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

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

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

        print
        print( "QUIT." )

205

206
207
208
209
210
211
212
213
214


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

    ## Command line arguments
    import argparse

    parser = argparse.ArgumentParser()
215
216

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

234
235
236
237
238

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

239
240
    args = parser.parse_args()

Mario Hock's avatar
Mario Hock committed
241

242
243
244
245

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

Mario Hock's avatar
Mario Hock committed
249
250
251
252
253
    ## --autologging implies --logging
    if ( args.autologging ):
        args.logging = True


254
    ## Logging
255
    logging_manager = LoggingManager( psutil.NUM_CPUS, monitored_nics, helpers.get_sysinfo(), args.environment,
Mario Hock's avatar
Mario Hock committed
256
                                      args.comment, args.path, args.autologging, args.watch )
257
258
259
260
261
262
    if args.logging:
        logging_manager.enable_measurement_logger()


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