__init__.py 6.43 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
import os
Mario Hock's avatar
Mario Hock committed
5
6
import psutil
import time
Mario Hock's avatar
Mario Hock committed
7
8
import sys
import traceback
9
import math
10
import json
Mario Hock's avatar
Mario Hock committed
11

12
13
14
from collections import namedtuple

import helpers
Mario Hock's avatar
Mario Hock committed
15
import curses_display as ui
16
17
from logging import LoggingManager

18
19
20
21
22

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

Mario Hock's avatar
Mario Hock committed
23
24
25
26
27
28
29
30
31

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

35
36

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

Mario Hock's avatar
Mario Hock committed
39
40
    def __init__(self):
        ## * measurements *
41
        self.timestamp = get_time()
Mario Hock's avatar
Mario Hock committed
42
43
        #self.cpu_util = psutil.cpu_percent(interval=0, percpu=True)                      ## XXX
        #self.cpu_times_percent = psutil.cpu_times_percent(interval=0, percpu=True)       ## XXX
44
        self.cpu_times = psutil.cpu_times(percpu=True)
Mario Hock's avatar
Mario Hock committed
45
46
        self.memory = psutil.virtual_memory()
        self.net_io = psutil.net_io_counters(pernic=True)
Mario Hock's avatar
Mario Hock committed
47
48

    def __str__(self):
Mario Hock's avatar
Mario Hock committed
49
50
51
52
53
54
        ## •‣∘⁕∗◘☉☀★◾☞☛⦿
        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
55
56


57
58

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

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    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
77
78
    """ Calculates and stores CPU utilization, network traffic, ... during a timespan. Based two »Readings«. """

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
        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



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

102
103
104
105
106
107
108
109
110
111
    r1 = Reading()
    time.sleep(interval)
    r2 = Reading()

    m = Measurement(r1, r2)

    return m



Mario Hock's avatar
Mario Hock committed
112

113
114
115
116
117
def main_loop():
    """ Main Loop:
      - Sets up curses-display
      - Takes a reading every second
      - Displays the measurements
118
      - Logs the measurements with the LoggingManager
119
120
    """

Mario Hock's avatar
Mario Hock committed
121
122
123
    err = None

    try:
124
        # Set up (curses) UI.
125
        ui.nics = nics
Mario Hock's avatar
Mario Hock committed
126
        ui.nic_speeds = nic_speeds
127
128
129
130
131
        ui.init()

        # Take an initial reading.
        old_reading = Reading()

132
133
134
135
        # Sleep till the next "full" second begins. (In order to roughly synchronize with other instances.)
        now = time.time()
        time.sleep(math.ceil(now)-now)

136
137
138
139
140
141
142
143
144
145
        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)

            # Display the measurement.
            running = ui.display( measurement )
146
            logging_manager.log(measurement)
147
148
149

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

151
152
153
154
            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
155
156

    except KeyboardInterrupt:
157
        # Quit gracefully on Ctrl-C
Mario Hock's avatar
Mario Hock committed
158
159
        pass
    except Exception as e:
160
        # On error: Store stack trace for later processing.
Mario Hock's avatar
Mario Hock committed
161
162
163
        err = e
        exc_type, exc_value, exc_traceback = sys.exc_info()
    finally:
164
        # Tear down the UI.
Mario Hock's avatar
Mario Hock committed
165
        ui.close()
166
        logging_manager.close()
Mario Hock's avatar
Mario Hock committed
167
168
169
170

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

Mario Hock's avatar
Mario Hock committed
173
174
175
176
177
        traceback.print_exception(exc_type, exc_value, exc_traceback, file=sys.stdout)

        print
        print( "QUIT." )

178

179
180
181
182
183
184
185
186
187


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

    ## Command line arguments
    import argparse

    parser = argparse.ArgumentParser()
188
189

    ## Logging
190
191
192
193
194
195
    parser.add_argument("-l", "--logging", action="store_true",
                        help="Enables logging.")
    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.)")
196
197
198
199
200
201
202
    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).")

203
204
    args = parser.parse_args()

Mario Hock's avatar
Mario Hock committed
205

206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

    ## 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
221
222
223
224
225
    ## Get hostname (for display and logging).
    osdetails = tuple(os.uname())
    ostype = osdetails[0]
    hostname = osdetails[1]

226

227
    ## Logging
228
    logging_manager = LoggingManager( psutil.NUM_CPUS, monitored_nics, hostname, environment, args.comment, args.path )
229
230
231
232
233
234
    if args.logging:
        logging_manager.enable_measurement_logger()


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