__init__.py 8.11 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
    ## --autologging implies --logging
    if ( args.autologging ):
        args.logging = True

258 259 260 261 262
    ## compensate psutil version incompatibilities
    try:
        num_cpus = psutil.cpu_count()
    except:
        num_cpus = psutil.NUM_CPUS
Mario Hock's avatar
Mario Hock committed
263

264
    ## Logging
265
    logging_manager = LoggingManager( num_cpus, monitored_nics, helpers.get_sysinfo(), args.environment,
Mario Hock's avatar
Mario Hock committed
266
                                      args.comment, args.path, args.autologging, args.watch )
267 268 269 270 271 272
    if args.logging:
        logging_manager.enable_measurement_logger()


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