curses_display.py 11.9 KB
Newer Older
Mario Hock's avatar
Mario Hock committed
1
2
# -*- coding:utf-8 -*-

Mario Hock's avatar
Mario Hock committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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
#
#
# Acknowledgment:
#
# This code is inspired from and contains some code primitives found in »bwtop«
# written by "Mahmoud Adel <mahmoud.adel2@gmail.com>" and distributed under
# the MIT license.
#
# For more information about »bwtop« please refer to its project site:
# https://github.com/mahmoudadel2/bwtop


Mario Hock's avatar
Mario Hock committed
22
23
24
25
26
27
'''
Curses display for »cpunetlog«.

'''
import curses
import time
Mario Hock's avatar
Mario Hock committed
28
import helpers
Mario Hock's avatar
Mario Hock committed
29
30
31
32
33
34
35
36

## XXX disable colors
disablecolorskipped = True

stdscr = None

## some "constants"/preferences
nics = None
Mario Hock's avatar
Mario Hock committed
37
nic_speeds = None
Mario Hock's avatar
Mario Hock committed
38
divisor = 1000000.0
Mario Hock's avatar
Mario Hock committed
39
rounding_digits = 2
Mario Hock's avatar
Mario Hock committed
40
unit = "MBits"
Mario Hock's avatar
Mario Hock committed
41

42
43
44
EXISTING_NIC_SPEEDS = [ x * 1000000 for x in (10, 100, 1000, 10000) ]   # in bit/s


45
LOGGING_STATE_COLORS = {"Active": 3, "(Active)": 2, "Disabled": 1, "Standby": 4, "Enabled": 3}
Mario Hock's avatar
Mario Hock committed
46

47
48
49
50
51
52
53
54
55
56
CPU_TYPE_LABELS = { "user": "user: ",
                    "nice": "nice: ",
                    "system": "system: ",
                    "idle": "idle: ",
                    "iowait": "iowait: ",
                    "irq": "irq: ",
                    "softirq": "sftirq: ",
                    "steal": "steal: ",
                    "guest": "guest: ",
                    "guest_nice": "g_nice: " }
Mario Hock's avatar
Mario Hock committed
57
58
59
60

## Reference to the logging manager, to display its state.
logging_manager = None

Mario Hock's avatar
Mario Hock committed
61
## GUI, positions of fields
Mario Hock's avatar
Mario Hock committed
62
63
64
LABEL_Sent = 18
LABEL_Received = 48
LABEL_CPU_UTIL = LABEL_Sent
65
66
LABEL_CPU_1 = LABEL_Received-1
LABEL_CPU_2 = 63
Mario Hock's avatar
Mario Hock committed
67

68
69
COMMENT_WIDTH = 66

Mario Hock's avatar
Mario Hock committed
70

Mario Hock's avatar
..    
Mario Hock committed
71
## TODO ideas..
Mario Hock's avatar
Mario Hock committed
72
73
74
75
#   - Add an option to set a fixed max. net-speed manually (for comparison)
#       --> Maybe change colors, when speed higher than this max
#           (and switch to a scale with the real max. value for the nic.)
#
Mario Hock's avatar
..    
Mario Hock committed
76
#   - total (GB transferred)
Mario Hock's avatar
..    
Mario Hock committed
77
78
#   - "Total" field for CPU usage (as well)
#   - Also draw a bar for the totals?
Mario Hock's avatar
..    
Mario Hock committed
79
80
81

## TODO idea: smoothing..?

Mario Hock's avatar
Mario Hock committed
82

Mario Hock's avatar
Mario Hock committed
83

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

def reset_nic_speeds():
    """
      Resets the maximum NIC-speed of all devices to a minimum.

      Note: The maximum will subsequently be adjusted by the function "adjust_nic_speeds".
    """

    for x in nic_speeds:
        nic_speeds[x] = EXISTING_NIC_SPEEDS[0]



def adjust_nic_speeds(send, receive, nic):
    """
      Adjust the maximum speed of the NIC, if it was detected wrong
    """
    global nic_speeds

    MARGIN = 1000   # +1 kbit/s just to be sure..
    #MARGIN = 0

    cur = max(send, receive)

    if ( cur > nic_speeds[nic] + MARGIN ):
        new_speed = nic_speeds[nic]
        for x in EXISTING_NIC_SPEEDS:
            if ( cur < x + MARGIN ):
                new_speed = x
                break

        nic_speeds[nic] = new_speed

        ## TODO: maybe change color...



Mario Hock's avatar
Mario Hock committed
121
122
123
124
def _format_net_speed(speed):
    return str( round(speed / divisor, rounding_digits) )


Mario Hock's avatar
..    
Mario Hock committed
125
126
127
128
129
130
def _calculate_net_ratio( cur_speed, max_speed ):
    ratio = cur_speed / max_speed

    ratio = min( ratio, 1.0 )
    ratio = max( ratio, 0.0 )

Mario Hock's avatar
..    
Mario Hock committed
131
    return ratio
Mario Hock's avatar
..    
Mario Hock committed
132
133


Mario Hock's avatar
Mario Hock committed
134
135
136
137
def _display_cpu_bar(y, x, cpu):
    # Constants
    CPU_BAR_COLORS = ( curses.color_pair(3) | curses.A_REVERSE,    # user
                       curses.color_pair(4) | curses.A_REVERSE,    # system
138
                       curses.color_pair(5) | curses.A_REVERSE,    # irq / softirq
Mario Hock's avatar
Mario Hock committed
139
                       curses.color_pair(6) | curses.A_REVERSE,    # other
Mario Hock's avatar
Mario Hock committed
140
141
142
143
                       curses.color_pair(3) )                      # idle

    # Calculate proportions
    cpu_util = 100-cpu.idle
144
145
    other = 100 - sum( (cpu.user, cpu.system, cpu.irq, cpu.idle) )
    proportions = [cpu.user, cpu.system, cpu.irq, other, cpu.idle]
Mario Hock's avatar
Mario Hock committed
146
147
148
149
150
151
152
153
154
155
156
157

    # Prepare text.
    text = '{0:.2%}'.format((cpu_util)/100.0)
    split_text = helpers.split_proprtionally(text, proportions, 20)

    # Write text on screen (curses).
    stdscr.move(y,x)
    for s, options in zip(split_text, CPU_BAR_COLORS):
        stdscr.addstr(s, options)



Mario Hock's avatar
Mario Hock committed
158
159
160
161
162
163
164
165
166
167
def _display_logging_state(y, x):
    if ( not logging_manager ):
        stdscr.addstr(y, x, 'Disabled', curses.A_BOLD)

    else:
        state = logging_manager.get_logging_state()
        color = LOGGING_STATE_COLORS[state]

        stdscr.addstr(y, x, state, curses.A_BOLD | curses.color_pair(color))

Mario Hock's avatar
Mario Hock committed
168
169
170

def init():
    global stdscr
Mario Hock's avatar
Mario Hock committed
171
172
173
174
    global nic_speeds

    if not nic_speeds:
        nic_speeds = helpers.get_nic_speeds()
Mario Hock's avatar
Mario Hock committed
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

    stdscr = curses.initscr()
    curses.noecho()
    curses.cbreak()
    stdscr.keypad(True)
    curses.curs_set(False)
    stdscr.nodelay(True)

    curses.start_color()
    curses.use_default_colors()
    if not disablecolorskipped:
        curses.init_pair(1, -1, -1)
        curses.init_pair(2, -1, -1)
        curses.init_pair(3, -1, -1)
        curses.init_pair(4, -1, -1)
    else:
Mario Hock's avatar
Mario Hock committed
191
192
193
194
195
196
        curses.init_pair(1, curses.COLOR_MAGENTA, -1)         # "CPUX", "ethX"
        curses.init_pair(2, curses.COLOR_BLUE, -1)            # "util", "sent", "received", ...
        curses.init_pair(3, curses.COLOR_GREEN, -1)           # <values>
        curses.init_pair(4, curses.COLOR_YELLOW, -1)          # "Total", (<cpu-system> ?)
        curses.init_pair(5, curses.COLOR_CYAN, -1)            # <cpu-system> / <cpu-other>
        curses.init_pair(6, curses.COLOR_WHITE, -1)           # <cpu-system> / <cpu-other>
Mario Hock's avatar
Mario Hock committed
197
        curses.init_pair(7, curses.COLOR_RED, -1)
Mario Hock's avatar
Mario Hock committed
198
199

    ## Show some output to avoid upsetting the user
200
201
202
203
204
205
    try:
        stdscr.addstr(3, 3, "loading ...", curses.A_BOLD)
        stdscr.refresh()
    except:
        print( "\nDisplay Error! (Check terminal-size)" )

Mario Hock's avatar
Mario Hock committed
206
207
208
209



def display(measurement):
210
211
212
213
214
215
216
217
218
    try:
        return _display(measurement)
    except:
        print( "\nDisplay Error! (Check terminal-size)" )

        return True


def _display(measurement):
Mario Hock's avatar
Mario Hock committed
219
220
    global stdscr

Mario Hock's avatar
Mario Hock committed
221
    ## Press 'q' to quit.
Mario Hock's avatar
Mario Hock committed
222
223
224
    pressedkey = stdscr.getch()
    if pressedkey == ord('q'):
        return False
225
226
    elif pressedkey == ord('-'):
        reset_nic_speeds()
Mario Hock's avatar
Mario Hock committed
227

Mario Hock's avatar
Mario Hock committed
228
    ## Header
Mario Hock's avatar
Mario Hock committed
229
230
231
    stdscr.clear()
    stdscr.border(0)
    timenow = time.strftime("%H:%M:%S")
Mario Hock's avatar
Mario Hock committed
232
    stdscr.addstr(1, 1, 'CPUnetLOG', curses.A_BOLD)
Mario Hock's avatar
Mario Hock committed
233
234
235
236
    stdscr.addstr(1, LABEL_Sent, 'Time: {}'.format( timenow ), curses.A_BOLD)
    stdscr.addstr(1, 39, 'Interval: {}s'.format( round(measurement.timespan, 1) ), curses.A_BOLD)
    stdscr.addstr(1, 62, 'Logging: ', curses.A_BOLD)
    _display_logging_state(1, 71)
Mario Hock's avatar
Mario Hock committed
237
238
    stdscr.refresh()

Mario Hock's avatar
Mario Hock committed
239
240
241
242
243
    y = 3

    ## CPU ##
    num=1
    for cpu in measurement.cpu_times_percent:
Mario Hock's avatar
Mario Hock committed
244
        # static labels
Mario Hock's avatar
Mario Hock committed
245
        stdscr.addstr(y, 1, 'CPU{0}'.format( num ), curses.color_pair(1))
Mario Hock's avatar
Mario Hock committed
246
        stdscr.addstr(y, LABEL_CPU_UTIL, 'util: ', curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
247
        stdscr.addstr(y, LABEL_CPU_UTIL+26, '|', curses.color_pair(2))
248
249
        #stdscr.addstr(y, LABEL_CPU_USER, 'type: ', curses.color_pair(2))
        #stdscr.addstr(y, LABEL_CPU_SYSTEM, 'system: ', curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
250
251

        # CPU bar
Mario Hock's avatar
Mario Hock committed
252
        _display_cpu_bar( y, LABEL_CPU_UTIL+6, cpu )
Mario Hock's avatar
Mario Hock committed
253
254

        # user/system
255
        cpu_sorted = helpers.sort_named_tuple(cpu, skip="idle")
256
257
        t = '{0: >8}'.format( CPU_TYPE_LABELS[cpu_sorted[0][0]] )
        stdscr.addstr(y, LABEL_CPU_1, t, curses.color_pair(4))
258
        stdscr.addstr("{:>5.2f}%".format(cpu_sorted[0][1]), curses.color_pair(3))
259
260
261

        t = '{0: >8}'.format( CPU_TYPE_LABELS[cpu_sorted[1][0]] )
        stdscr.addstr(y, LABEL_CPU_2, t, curses.color_pair(4))
262
        stdscr.addstr("{:>5.2f}%".format(cpu_sorted[1][1]), curses.color_pair(3))
Mario Hock's avatar
Mario Hock committed
263
264
265
266
267
268
269
270
271
272

        num += 1
        y += 1




    ## Network ##

    y += 1
Mario Hock's avatar
Mario Hock committed
273
    stdscr.hline(y, 1, "-", 78)
Mario Hock's avatar
Mario Hock committed
274
275
    y += 1

Mario Hock's avatar
Mario Hock committed
276
277
278
279
280
281
282
283
284
285
    # display all nics (if not set otherwise)
    if nics:
        active_nics = nics
    else:
        active_nics = measurement.net_io.keys()

    sum_sending = 0
    sum_receiving = 0

    ## display the values
Mario Hock's avatar
Mario Hock committed
286
    for nic in sorted(active_nics):
Mario Hock's avatar
Mario Hock committed
287
288
        values = measurement.net_io[nic]

Mario Hock's avatar
Mario Hock committed
289
290
        _send = values.ratio["bytes_sent"] * 8  # Bits/s
        _recv = values.ratio["bytes_recv"] * 8  # Bits/s
291
292
293
294

        ## TESTING increase inc_speed, if throughput is higher than (expected) maximum
        adjust_nic_speeds(_send, _recv, nic)

Mario Hock's avatar
Mario Hock committed
295
        sending = _format_net_speed( _send )
Mario Hock's avatar
..    
Mario Hock committed
296
        send_ratio = _calculate_net_ratio( _send, nic_speeds[nic] )
Mario Hock's avatar
Mario Hock committed
297
        receiving = _format_net_speed( _recv )
Mario Hock's avatar
..    
Mario Hock committed
298
        receive_ratio = _calculate_net_ratio( _recv, nic_speeds[nic] )
Mario Hock's avatar
Mario Hock committed
299
300
301

        sum_sending += _send
        sum_receiving += _recv
Mario Hock's avatar
Mario Hock committed
302
303

        stdscr.addstr(y, 1, '{0}'.format(nic), curses.color_pair(1))
Mario Hock's avatar
Mario Hock committed
304
        stdscr.addstr(y, LABEL_Sent, 'Sent: ', curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
305
        stdscr.addstr(y, LABEL_Sent+26, "|", curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
306
        stdscr.addstr(y, LABEL_Received, 'Received: ', curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
307
        stdscr.addstr(y, LABEL_Received+30, "|", curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
308

Mario Hock's avatar
Mario Hock committed
309
        ## TODO rewrite in nice ^^ [see _display_cpu_bar()]
Mario Hock's avatar
Mario Hock committed
310
311
312
313
        ## XXX prototypical "inline"-coloring
        _snd_str = '{0} {1}/s'.format(sending, unit, send_ratio)
        _snd_str += " " * (20-len(_snd_str))
        _load_len = int(send_ratio * 20)
Mario Hock's avatar
Mario Hock committed
314
315
        stdscr.addstr(y, LABEL_Sent+6, _snd_str[0:_load_len], curses.color_pair(3)|curses.A_REVERSE)
        stdscr.addstr(y, LABEL_Sent+6+_load_len, _snd_str[_load_len:], curses.color_pair(3))
Mario Hock's avatar
Mario Hock committed
316
317
318
319

        _recv_str = '{0} {1}/s'.format(receiving, unit, send_ratio)
        _recv_str += " " * (20-len(_recv_str))
        _load_len = int(receive_ratio * 20)
Mario Hock's avatar
Mario Hock committed
320
321
        stdscr.addstr(y, LABEL_Received+10, _recv_str[0:_load_len], curses.color_pair(3)|curses.A_REVERSE)
        stdscr.addstr(y, LABEL_Received+10+_load_len, _recv_str[_load_len:], curses.color_pair(3))
Mario Hock's avatar
Mario Hock committed
322

Mario Hock's avatar
Mario Hock committed
323
        ## XXX TESTING
Mario Hock's avatar
Mario Hock committed
324
325
326
327
328
        #y += 1
        #stdscr.addstr(y, 25, "|" + " "*20 + "|", curses.color_pair(2))
        #stdscr.addstr(y, 26, " " * int(send_ratio * 20), curses.color_pair(3)|curses.A_REVERSE)
        #stdscr.addstr(y, 59, "|" + " "*20 + "|", curses.color_pair(2))
        #stdscr.addstr(y, 60, " " * int(receive_ratio * 20), curses.color_pair(3)|curses.A_REVERSE)
Mario Hock's avatar
Mario Hock committed
329

Mario Hock's avatar
Mario Hock committed
330
331
332
333
334
        y += 1

    ## Total
    y+=1
    stdscr.addstr(y, 1, 'Total:', curses.color_pair(4))
Mario Hock's avatar
Mario Hock committed
335
336
337
338
    stdscr.addstr(y, LABEL_Sent, 'Sent:', curses.color_pair(2))
    stdscr.addstr(y, LABEL_Sent+6, '{0} {1}/s'.format(_format_net_speed(sum_sending), unit), curses.color_pair(3))
    stdscr.addstr(y, LABEL_Received, 'Received:', curses.color_pair(2))
    stdscr.addstr(y, LABEL_Received+10, '{0} {1}/s'.format(_format_net_speed(sum_receiving),unit), curses.color_pair(3))
Mario Hock's avatar
Mario Hock committed
339

340
341
342
343
344
345
346
347
    '''
    #RAM
    y += 1
    stdscr.hline(y, 1, "-", 78)
    y += 1

    #vmem(total=135092002816, available=134568681472, percent=0.4, used=1066811392, free=134025191424, active=524701696, inactive=43393024, buffers=153694208, cached=389795840)
    stdscr.addstr(y, 1, "RAM", curses.color_pair(1))
Mario Hock's avatar
Mario Hock committed
348

349
350
351
352
353
354
355
356
357
    for field in measurement.memory._fields:
        value = getattr(measurement.memory, field)
        if field != 'percent':
            value = bytes2human(value)

        stdscr.addstr(y, LABEL_Sent, '{0}:'.format( field.capitalize()), curses.color_pair(2))
        stdscr.addstr(y, LABEL_Sent+11, '{0:>7}'.format( value ), curses.color_pair(3))
        y += 1
    '''
Mario Hock's avatar
Mario Hock committed
358
359
360
361
362
363

    ## Show logging comment
    comment = logging_manager.get_logging_comment()
    if ( comment ):
        y += 3
        stdscr.addstr(y, 3, 'Comment: ', curses.A_BOLD)
364
365
366
367
368

        parts = ( comment[i:i+COMMENT_WIDTH] for i in range(0, len(comment), COMMENT_WIDTH) )
        for part in parts:
            stdscr.addstr(y, 3+9, part)
            y += 1
Mario Hock's avatar
Mario Hock committed
369

Mario Hock's avatar
Mario Hock committed
370
371
372
373
374
375
376
377
378
379
380
381
382
    stdscr.refresh()

    return True


def close():
    global stdscr

    curses.nocbreak()
    stdscr.keypad(False)
    curses.echo()
    curses.curs_set(True)
    curses.endwin()
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399


def bytes2human(n):
    # http://code.activestate.com/recipes/578019
    # >>> bytes2human(10000)
    # '9.8K'
    # >>> bytes2human(100001221)
    # '95.4M'
    symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
    prefix = {}
    for i, s in enumerate(symbols):
        prefix[s] = 1 << (i + 1) * 10
    for s in reversed(symbols):
        if n >= prefix[s]:
            value = float(n) / prefix[s]
            return '%.1f%s' % (value, s)
    return "%sB" % n