__init__.py 7.2 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
26
from logging import LoggingManager

27
28
29
30
31

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

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

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

44
45

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

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

    def __str__(self):
Mario Hock's avatar
Mario Hock committed
58
59
60
61
62
63
        ## •‣∘⁕∗◘☉☀★◾☞☛⦿
        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
64
65


66
67

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

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
    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
86
87
    """ Calculates and stores CPU utilization, network traffic, ... during a timespan. Based two »Readings«. """

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
    def __init__(self, reading1, reading2):
        self.r1 = reading1
        self.r2 = reading2

        ## calculate differences
        self.timespan = self.r2.timestamp - self.r1.timestamp
        self.cpu_times_percent = helpers.calculate_cpu_times_percent(self.r1.cpu_times, self.r2.cpu_times, percpu=True)
        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
106
107
108
109
110
111
    def get_begin(self):
        return self.r1.timestamp

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

112
113
114


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

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

    m = Measurement(r1, r2)

    return m



Mario Hock's avatar
Mario Hock committed
127

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

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

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

        # Take an initial reading.
        old_reading = Reading()

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

152
153
154
155
156
157
158
159
        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
160
            # Display/log the measurement.
Mario Hock's avatar
..    
Mario Hock committed
161
            running &= logging_manager.log(measurement)
Mario Hock's avatar
Mario Hock committed
162
            running = ui.display( measurement )
163
164
165

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

167
168
169
170
            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
171
172

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

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

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

        print
        print( "QUIT." )

194

195
196
197
198
199
200
201
202
203


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

    ## Command line arguments
    import argparse

    parser = argparse.ArgumentParser()
204
205

    ## Logging
206
207
    parser.add_argument("-l", "--logging", action="store_true",
                        help="Enables logging.")
Mario Hock's avatar
Mario Hock committed
208
209
    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
210
211
    parser.add_argument("-W", "--watch",
                        help="Store the command-line of the given program as log-comment. (Use together with --autologging.)")
212
213
214
215
    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.)")
216
217
218
219
220
221
222
    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).")

223
224
    args = parser.parse_args()

Mario Hock's avatar
Mario Hock committed
225

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

    ## 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
240
241
242
243
244
    ## --autologging implies --logging
    if ( args.autologging ):
        args.logging = True


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


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