curses_display.py 9.19 KB
Newer Older
Mario Hock's avatar
Mario Hock committed
1
2
3
4
5
6
7
8
9
10
11
# -*- coding:utf-8 -*-

'''
Curses display for »cpunetlog«.

This code is inspired and partially copied from »bwtop« written by
"Mahmoud Adel <mahmoud.adel2@gmail.com>" and distributed under the MIT license.
'''

import curses
import time
Mario Hock's avatar
Mario Hock committed
12
import helpers
Mario Hock's avatar
Mario Hock committed
13
14
15
16
17
18
19
20

## XXX disable colors
disablecolorskipped = True

stdscr = None

## some "constants"/preferences
nics = None
Mario Hock's avatar
Mario Hock committed
21
nic_speeds = None
Mario Hock's avatar
Mario Hock committed
22
divisor = 1000000.0
Mario Hock's avatar
Mario Hock committed
23
rounding_digits = 2
Mario Hock's avatar
Mario Hock committed
24
unit = "MBits"
Mario Hock's avatar
Mario Hock committed
25

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

28
29
30
31
32
33
34
35
36
37
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
38
39
40
41

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

Mario Hock's avatar
Mario Hock committed
42
## GUI, positions of fields
Mario Hock's avatar
Mario Hock committed
43
44
45
LABEL_Sent = 18
LABEL_Received = 48
LABEL_CPU_UTIL = LABEL_Sent
46
47
LABEL_CPU_1 = LABEL_Received-1
LABEL_CPU_2 = 63
Mario Hock's avatar
Mario Hock committed
48

49
50
COMMENT_WIDTH = 66

Mario Hock's avatar
Mario Hock committed
51

Mario Hock's avatar
..    
Mario Hock committed
52
## TODO ideas..
Mario Hock's avatar
Mario Hock committed
53
54
55
56
#   - 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
57
#   - total (GB transferred)
Mario Hock's avatar
..    
Mario Hock committed
58
59
#   - "Total" field for CPU usage (as well)
#   - Also draw a bar for the totals?
Mario Hock's avatar
..    
Mario Hock committed
60
61
62

## TODO idea: smoothing..?

Mario Hock's avatar
Mario Hock committed
63

Mario Hock's avatar
Mario Hock committed
64

Mario Hock's avatar
Mario Hock committed
65
66
67
68
def _format_net_speed(speed):
    return str( round(speed / divisor, rounding_digits) )


Mario Hock's avatar
Mario Hock committed
69
70
71
72
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
73
                       curses.color_pair(5) | curses.A_REVERSE,    # irq / softirq
Mario Hock's avatar
Mario Hock committed
74
                       curses.color_pair(6) | curses.A_REVERSE,    # other
Mario Hock's avatar
Mario Hock committed
75
76
77
78
                       curses.color_pair(3) )                      # idle

    # Calculate proportions
    cpu_util = 100-cpu.idle
79
80
    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
81
82
83
84
85
86
87
88
89
90
91
92

    # 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
93
94
95
96
97
98
99
100
101
102
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
103
104
105

def init():
    global stdscr
Mario Hock's avatar
Mario Hock committed
106
107
108
109
    global nic_speeds

    if not nic_speeds:
        nic_speeds = helpers.get_nic_speeds()
Mario Hock's avatar
Mario Hock committed
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

    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
126
127
128
129
130
131
        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
132
        curses.init_pair(7, curses.COLOR_RED, -1)
Mario Hock's avatar
Mario Hock committed
133
134
135
136
137
138
139
140
141
142

    ## Show some output to avoid upsetting the user
    stdscr.addstr(3, 3, "loading ...", curses.A_BOLD)
    stdscr.refresh()



def display(measurement):
    global stdscr

Mario Hock's avatar
Mario Hock committed
143
    ## Press 'q' to quit.
Mario Hock's avatar
Mario Hock committed
144
145
146
147
    pressedkey = stdscr.getch()
    if pressedkey == ord('q'):
        return False

Mario Hock's avatar
Mario Hock committed
148
    ## Header
Mario Hock's avatar
Mario Hock committed
149
150
151
    stdscr.clear()
    stdscr.border(0)
    timenow = time.strftime("%H:%M:%S")
Mario Hock's avatar
Mario Hock committed
152
    stdscr.addstr(1, 1, 'CPUnetLOG', curses.A_BOLD)
Mario Hock's avatar
Mario Hock committed
153
154
155
156
    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
157
158
    stdscr.refresh()

Mario Hock's avatar
Mario Hock committed
159
160
161
162
163
    y = 3

    ## CPU ##
    num=1
    for cpu in measurement.cpu_times_percent:
Mario Hock's avatar
Mario Hock committed
164
        # static labels
Mario Hock's avatar
Mario Hock committed
165
        stdscr.addstr(y, 1, 'CPU{0}'.format( num ), curses.color_pair(1))
Mario Hock's avatar
Mario Hock committed
166
        stdscr.addstr(y, LABEL_CPU_UTIL, 'util: ', curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
167
        stdscr.addstr(y, LABEL_CPU_UTIL+26, '|', curses.color_pair(2))
168
169
        #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
170
171

        # CPU bar
Mario Hock's avatar
Mario Hock committed
172
        _display_cpu_bar( y, LABEL_CPU_UTIL+6, cpu )
Mario Hock's avatar
Mario Hock committed
173
174

        # user/system
175
176
        #stdscr.addstr(y, LABEL_CPU_1+6, '{0:.2%}'.format( cpu.user/100.0 ), curses.color_pair(3))
        #stdscr.addstr(y, LABEL_CPU_2+8, '{0:.2%}'.format( cpu.system/100.0 ), curses.color_pair(3))
177
        cpu_sorted = helpers.sort_named_tuple(cpu, skip="idle")
178
179
180
181
182
183
184
185
186
        t = '{0: >8}'.format( CPU_TYPE_LABELS[cpu_sorted[0][0]] )
        stdscr.addstr(y, LABEL_CPU_1, t, curses.color_pair(4))
        #stdscr.addstr("{:.2%}".format(cpu_sorted[0][1]/100), curses.color_pair(3))
        stdscr.addstr("{:>6.2f}%".format(cpu_sorted[0][1]), curses.color_pair(3))

        t = '{0: >8}'.format( CPU_TYPE_LABELS[cpu_sorted[1][0]] )
        stdscr.addstr(y, LABEL_CPU_2, t, curses.color_pair(4))
        #stdscr.addstr("{:.2%}".format(cpu_sorted[1][1]/100), curses.color_pair(3))
        stdscr.addstr("{:>6.2f}%".format(cpu_sorted[0][1]), curses.color_pair(3))
Mario Hock's avatar
Mario Hock committed
187
188
189
190
191
192
193
194
195
196

        num += 1
        y += 1




    ## Network ##

    y += 1
Mario Hock's avatar
Mario Hock committed
197
    stdscr.hline(y, 1, "-", 78)
Mario Hock's avatar
Mario Hock committed
198
199
    y += 1

Mario Hock's avatar
Mario Hock committed
200
201
202
203
204
205
206
207
208
209
    # 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
210
    for nic in sorted(active_nics):
Mario Hock's avatar
Mario Hock committed
211
212
        values = measurement.net_io[nic]

Mario Hock's avatar
Mario Hock committed
213
214
        _send = values.ratio["bytes_sent"] * 8  # Bits/s
        _recv = values.ratio["bytes_recv"] * 8  # Bits/s
Mario Hock's avatar
Mario Hock committed
215
        sending = _format_net_speed( _send )
Mario Hock's avatar
Mario Hock committed
216
        send_ratio = _send/nic_speeds[nic]
Mario Hock's avatar
Mario Hock committed
217
        receiving = _format_net_speed( _recv )
Mario Hock's avatar
Mario Hock committed
218
        receive_ratio = _recv/nic_speeds[nic]
Mario Hock's avatar
Mario Hock committed
219
220
221

        sum_sending += _send
        sum_receiving += _recv
Mario Hock's avatar
Mario Hock committed
222
223

        stdscr.addstr(y, 1, '{0}'.format(nic), curses.color_pair(1))
Mario Hock's avatar
Mario Hock committed
224
        stdscr.addstr(y, LABEL_Sent, 'Sent: ', curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
225
        stdscr.addstr(y, LABEL_Sent+26, "|", curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
226
        stdscr.addstr(y, LABEL_Received, 'Received: ', curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
227
        stdscr.addstr(y, LABEL_Received+30, "|", curses.color_pair(2))
Mario Hock's avatar
Mario Hock committed
228

Mario Hock's avatar
Mario Hock committed
229
        ## TODO rewrite in nice ^^ [see _display_cpu_bar()]
Mario Hock's avatar
Mario Hock committed
230
231
232
233
        ## 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
234
235
        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
236
237
238
239

        _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
240
241
        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
242

Mario Hock's avatar
Mario Hock committed
243
        ## XXX TESTING
Mario Hock's avatar
Mario Hock committed
244
245
246
247
248
        #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
249

Mario Hock's avatar
Mario Hock committed
250
251
252
253
254
        y += 1

    ## Total
    y+=1
    stdscr.addstr(y, 1, 'Total:', curses.color_pair(4))
Mario Hock's avatar
Mario Hock committed
255
256
257
258
    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
259

Mario Hock's avatar
Mario Hock committed
260
261
262
263
264
265
266


    ## Show logging comment
    comment = logging_manager.get_logging_comment()
    if ( comment ):
        y += 3
        stdscr.addstr(y, 3, 'Comment: ', curses.A_BOLD)
267
268
269
270
271

        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
272

Mario Hock's avatar
Mario Hock committed
273
274
275
276
277
278
279
280
281
282
283
284
285
    stdscr.refresh()

    return True


def close():
    global stdscr

    curses.nocbreak()
    stdscr.keypad(False)
    curses.echo()
    curses.curs_set(True)
    curses.endwin()