fdd76fc5 |
1 | #!/usr/bin/env python3 |
2 | # _*_ coding: utf-8 _*_ |
3 | |
4 | """ |
5 | A psutil-based command to display customizable system usage info in a single line, intended for Tint2 executors |
6 | |
7 | Author: Piotr Miller |
8 | e-mail: nwg.piotr@gmail.com |
9 | Website: http://nwg.pl |
10 | Project: https://github.com/nwg-piotr/psuinfo |
11 | License: GPL3 |
12 | |
13 | Inspired by https://github.com/tknomanzr/scripts/blob/master/tint2/executors/cpu.py by William Bradley (@tknomanzr) |
14 | """ |
15 | |
16 | import sys |
17 | import psutil |
18 | import time |
19 | import os |
20 | |
21 | |
22 | def main(): |
23 | fahrenheit = False |
24 | names = False |
25 | testing = False |
26 | time_start = None |
27 | components = "gStfM" |
28 | separator = " " |
29 | home = os.getenv("HOME") |
30 | draw_icons = False |
31 | |
32 | pcpu, avg, speed, freqs, temp, fans, b_time, memory, swap, disks_usage, which, ul, dl, xfer_start, xfer_finish, \ |
33 | path_to_icon, c_name= None, None, None, None, None, None, None, None, None, None, None, None, None, None, \ |
34 | None, None, None |
35 | |
36 | for i in range(1, len(sys.argv)): |
37 | if sys.argv[i] == "-h" or sys.argv[i] == "--help": |
38 | print_help() |
39 | exit(0) |
40 | |
41 | if sys.argv[i] == "-F": |
42 | fahrenheit = True |
43 | |
44 | if sys.argv[i] == "-N": |
45 | names = True |
46 | |
47 | if sys.argv[i] == "-T": |
48 | testing = True |
49 | |
50 | if sys.argv[i].startswith("-C"): |
51 | components = sys.argv[i][2::] |
52 | |
53 | if sys.argv[i].startswith("-S"): |
54 | try: |
55 | # if number given |
56 | spacing = int(sys.argv[i][2::]) |
57 | separator = " " * spacing |
58 | except ValueError: |
59 | # string given |
60 | separator = sys.argv[i][2::] |
61 | |
62 | if sys.argv[i].startswith("-W"): |
63 | try: |
64 | which = int(sys.argv[i][2::]) |
65 | except ValueError: |
66 | pass |
67 | |
68 | if sys.argv[i].upper() == "-ALL": |
69 | components = "gpaQStfMcWDUk" |
70 | names = True |
71 | testing = True |
72 | |
73 | if sys.argv[i].startswith("-I"): |
74 | draw_icons = True |
75 | # We can only have one icon per executor, so let's strip components to the first one |
76 | components = sys.argv[i][2] |
77 | # exception for UL/DL speed; to assign an icon to it we need to calculate speeds first |
78 | if components != "k": |
79 | path_to_icon = icon_path(home, components) |
80 | |
81 | if sys.argv[i].startswith("-M"): |
82 | # We can only have a custom name for a single component |
83 | components = components[0] |
84 | names = True |
85 | c_name = sys.argv[i][2::] |
86 | |
87 | if testing: |
88 | time_start = int(round(time.time() * 1000)) |
89 | |
90 | output = "" |
91 | |
92 | # Prepare ONLY requested data, ONLY once |
93 | if "g" or "p" in components: |
94 | try: |
95 | pcpu = psutil.cpu_percent(interval=1, percpu=True) |
96 | except: |
97 | pass |
98 | |
99 | if "a" in components: |
100 | try: |
101 | avg = str(psutil.cpu_percent(interval=1)) |
102 | if len(avg) < 4: |
103 | avg = " " + avg |
104 | except: |
105 | pass |
106 | |
107 | if "s" or "S" in components: |
108 | try: |
109 | speed = psutil.cpu_freq(False) |
110 | except: |
111 | pass |
112 | |
113 | if "q" or "Q" in components: |
114 | try: |
115 | freqs = psutil.cpu_freq(True) |
116 | if len(freqs) == 0: |
117 | freqs = None |
118 | except: |
119 | pass |
120 | |
121 | if "t" in components: |
122 | try: |
123 | temp = psutil.sensors_temperatures(fahrenheit) |
124 | except: |
125 | pass |
126 | |
127 | if "f" in components: |
128 | try: |
129 | fans = psutil.sensors_fans() |
130 | except: |
131 | pass |
132 | |
133 | if "m" or "M" or "z" or "Z" in components: |
134 | try: |
135 | memory = psutil.virtual_memory() |
136 | except: |
137 | pass |
138 | |
139 | if "w" or "W" or "x" in components: |
140 | try: |
141 | swap = psutil.swap_memory() |
142 | except: |
143 | pass |
144 | |
145 | if "k" in components: |
146 | try: |
147 | xfer_start = psutil.net_io_counters() |
148 | time.sleep(1) |
149 | xfer_finish = psutil.net_io_counters() |
150 | ul = (xfer_finish[0] - xfer_start[0]) / 1024 |
151 | dl = (xfer_finish[1] - xfer_start[1]) / 1024 |
152 | # We've not selected an icon previously. Now we have enough data. |
153 | if draw_icons: |
154 | path_to_icon = net_icon(home, ul, dl) |
155 | except: |
156 | pass |
157 | |
158 | drives = [] |
159 | # Find drive names, mountpoints |
160 | if "d" or "D" or "n" or "N" in components: |
161 | try: |
162 | d = psutil.disk_partitions() |
163 | # This will store name, mountpoint |
164 | for entry in d: |
165 | n = entry[0].split("/") |
166 | name = n[len(n) - 1] |
167 | # name, mountpoint |
168 | drive = name, entry[1] |
169 | drives.append(drive) |
170 | except: |
171 | pass |
172 | |
173 | if "d" or "D" in components: |
174 | try: |
175 | disks_usage = [] |
176 | for drive in drives: |
177 | # Search drives by path |
178 | data = psutil.disk_usage(drive[1]) |
179 | # Store name, used, total, percent |
180 | essential = drive[0].upper(), data[1], data[0], data[3] |
181 | disks_usage.append(essential) |
182 | except: |
183 | pass |
184 | |
185 | if "n" in components or "N" in components: |
186 | try: |
187 | disks_usage = [] |
188 | for drive in drives: |
189 | # Search drives by path |
190 | data = psutil.disk_usage(drive[1]) |
191 | # Store mountpoint, used, total, percent |
192 | essential = drive[1], data[1], data[0], data[3] |
193 | disks_usage.append(essential) |
194 | except: |
195 | pass |
196 | |
197 | if "u" or "U" in components: |
198 | try: |
199 | b_time = psutil.boot_time() |
200 | except: |
201 | pass |
202 | |
203 | # Build output component after component |
204 | output += separator |
205 | |
206 | for char in components: |
207 | if char == "g" and pcpu is not None: |
208 | if c_name: |
209 | output += c_name |
210 | output += graph_per_cpu(pcpu) + separator |
211 | |
212 | if char == "p" and pcpu is not None: |
213 | if names: |
214 | output += c_name if c_name else "CPU: " |
215 | output += per_cpu(pcpu) + separator |
216 | |
217 | if char == "a" and avg is not None: |
218 | if names: |
219 | output += c_name if c_name else "avCPU: " |
220 | output += avg + "%" + separator |
221 | |
222 | if char == "q" and freqs is not None: |
223 | if names: |
224 | output += c_name if c_name else "CPU: " |
225 | output += freq_per_cpu(freqs)[0][:-1] + " GHz" + separator |
226 | |
227 | if char == "Q" and freqs is not None: |
228 | if names: |
229 | output += c_name if c_name else "CPU: " |
230 | result = freq_per_cpu(freqs) |
231 | output += result[0][:-1] + "/" + str(result[1]) + " GHz" + separator |
232 | |
233 | if char == "s" and speed is not None: |
234 | if names: |
235 | output += c_name if c_name else "SPD: " |
236 | output += str(round(speed[0] / 1000, 1)) + " GHz" + separator |
237 | |
238 | if char == "S" and speed is not None: |
239 | if names: |
240 | output += c_name if c_name else "avSPD: " |
241 | output += str(round(speed[0] / 1000, 1)) + "/" + str(round(speed[2] / 1000, 1)) + " GHz" + separator |
242 | |
243 | if char == "t" and temp is not None and len(temp) > 0: |
244 | if names: |
245 | output += c_name if c_name else "CORE: " |
246 | if "k10temp" in temp.keys(): |
247 | # ryzen, multiple Die temperatures for threadripper/Epyc |
248 | ryzen_die_temps = [sensor.current for sensor in temp["k10temp"] if sensor.label == 'Tdie'] |
249 | output += str(int(max(ryzen_die_temps))) |
250 | if "coretemp" in temp.keys(): |
251 | # intel |
252 | output += str(int(temp["coretemp"][0][1])) |
253 | output += "℉" if fahrenheit else "℃" |
254 | output += separator |
255 | |
256 | if char == "f" and fans is not None and len(fans) > 0: |
257 | if names: |
258 | output += c_name if c_name else "FAN: " |
259 | fan0 = next(iter(fans.values())) |
260 | output += str(fan0[0][1]) + "/m" + separator |
261 | |
262 | if char == 'm' and memory is not None: |
263 | if names: |
264 | output += c_name if c_name else "MEM: " |
265 | output += str(round((memory[0] - memory[1]) / 1073741824, 1)) + " GB" + separator |
266 | |
267 | if char == 'M' and memory is not None: |
268 | if names: |
269 | output += c_name if c_name else "MEM: " |
270 | output += str(round((memory[3]) / 1073741824, 1)) + "/" + str( |
271 | round(memory[0] / 1073741824, 1)) + " GB" + separator |
272 | |
273 | if char == 'c' and memory is not None: |
274 | if names: |
275 | output += c_name if c_name else "MEM: " |
276 | output += str(memory[2]) + "%" + separator |
277 | |
278 | if char == 'C' and memory is not None: |
279 | if names: |
280 | output += c_name if c_name else "MEM: " |
281 | output += str(100 - memory[2]) + "%" + separator |
282 | |
283 | if char == 'u' and b_time is not None: |
284 | up_time = int(time.time()) - b_time |
285 | m, s = divmod(up_time, 60) |
286 | h, m = divmod(m, 60) |
287 | if names: |
288 | output += c_name if c_name else "UP: " |
289 | output += "%d:%02d" % (h, m) + separator |
290 | |
291 | if char == 'U' and b_time is not None: |
292 | up_time = int(time.time()) - b_time |
293 | m, s = divmod(up_time, 60) |
294 | h, m = divmod(m, 60) |
295 | if names: |
296 | output += c_name if c_name else "UP: " |
297 | output += "%d:%02d:%02d" % (h, m, s) + separator |
298 | |
299 | if char == "w" and swap is not None: |
300 | if names: |
301 | output += c_name if c_name else "SWAP: " |
302 | output += str(round(swap[1] / 1073741824, 1)) + " GB" + separator |
303 | |
304 | if char == "W" and swap is not None: |
305 | if names: |
306 | output += c_name if c_name else "SWAP: " |
307 | output += str(round(swap[1] / 1073741824, 1)) + "/" |
308 | output += str(round(swap[0] / 1073741824, 1)) + " GB" + separator |
309 | |
310 | if char == "x" and swap is not None: |
311 | if names: |
312 | output += c_name if c_name else "SWAP: " |
313 | output += str(swap[3]) + "%" + separator |
314 | |
315 | if char == "d" or char == "n" and disks_usage is not None: |
316 | if which is not None: |
317 | try: |
318 | entry = disks_usage[which] |
319 | output += entry[0] + ": " |
320 | output += str(entry[3]) + "%" + separator |
321 | except IndexError: |
322 | pass |
323 | else: |
324 | for entry in disks_usage: |
325 | output += entry[0] + ": " |
326 | output += str(entry[3]) + "%" + separator |
327 | |
328 | if char == "D" or char == "N" and disks_usage is not None: |
329 | if c_name: |
330 | output += c_name |
331 | if which is not None: |
332 | try: |
333 | entry = disks_usage[which] |
334 | output += entry[0] + ": " |
335 | output += str(round(entry[1] / 1073741824, 1)) + "/" |
336 | output += str(round(entry[2] / 1073741824, 1)) + " GB" + separator |
337 | except IndexError: |
338 | pass |
339 | else: |
340 | for entry in disks_usage: |
341 | output += entry[0] + ": " |
342 | output += str(round(entry[1] / 1073741824, 1)) + "/" |
343 | output += str(round(entry[2] / 1073741824, 1)) + " GB" + separator |
344 | |
345 | if char == "k": |
346 | if names and xfer_start is not None and xfer_finish is not None: |
347 | output += c_name if c_name else "Net: " |
348 | output += '{:0.2f}'.format((xfer_finish[0] - xfer_start[0]) / 1024) + ' {:0.2f} kB/s'.format( |
349 | (xfer_finish[1] - xfer_start[1]) / 1024) + separator |
350 | |
351 | if testing: |
352 | output += "[" + str(int((round(time.time() * 1000)) - time_start) / 1000) + "s]" + separator |
353 | |
354 | # remove leading and trailing separator |
355 | l = len(separator) |
356 | if l > 0: |
357 | output = output[l:-l] |
358 | |
359 | if draw_icons: |
360 | print(path_to_icon) |
361 | |
362 | print(output) |
363 | |
364 | |
365 | def per_cpu(result): |
366 | string = "" |
367 | for val in result: |
368 | proc = str(int(round(val, 1))) |
369 | if len(proc) < 2: |
370 | proc = " " + proc |
371 | string += proc + "% " |
372 | return string |
373 | |
374 | |
375 | def freq_per_cpu(result): |
376 | string = "" |
377 | max_freq = 0 |
378 | for val in result: |
379 | freq = str(round(val[0] / 1000, 1)) |
380 | string += freq + "|" |
381 | max_freq = str(round(val[2] / 1000, 1)) |
382 | |
383 | return string, max_freq |
384 | |
385 | |
386 | def graph_per_cpu(result): |
387 | graph = "_▁▂▃▄▅▆▇███" |
388 | |
389 | string = "" |
390 | for val in result: |
391 | proc = int(round(val / 10, 0)) |
392 | string += graph[proc] |
393 | return string |
394 | |
395 | |
396 | def print_help(): |
397 | |
398 | print("\npsuinfo [-C{components}] | [-I{component}] [-F] [-N] [-S<number>] | [-S<string>] [-T] [-W{number}] [-all] [-h] [--help]") |
399 | |
400 | print("\n-C defines multiple components. -I defines a single component. If none given, -CgStfM argument will be used by default.\n") |
401 | print(" g - (g)raphical CPU load bar") |
402 | print(" p - (p)ercentage for each core (text)") |
403 | print(" a - (a)verage CPU load (text)") |
404 | print(" q - fre(q)ency for each thread") |
405 | print(" Q - fre(Q)ency for each thread/max frequency") |
406 | print(" s - current CPU (s)peed") |
407 | print(" S - current/max CPU (S)peed") |
408 | print(" t - CPU (t)emperature") |
409 | print(" f - (f)an speed") |
410 | print(" m - (m)emory in use") |
411 | print(" M - (M)emory in use/total") |
412 | print(" c - used memory per(c)entage") |
413 | print(" C - free memory per(C)entage") |
414 | print(" w - s(w)ap memory in use") |
415 | print(" W - s(W)ap memory in use/total") |
416 | print(" x - swap usage in %") |
417 | print(" d - (d)rives as names usage in %") |
418 | print(" D - (D)rives as names used/total") |
419 | print(" n - drives as mou(n)tpoints usage in %") |
420 | print(" N - drives as mou(N)tpoints used/total") |
421 | print(" u - (u)ptime HH:MM") |
422 | print(" U - (U)ptime HH:MM:SS") |
423 | print(" k - current networ(k) traffic as upload/download in kB/s") |
424 | |
425 | print("\n-F - use Fahrenheit instead of ℃") |
426 | print("-N - display field names (except for (g)raphical CPU load bar)") |
427 | print("-S<number> - number of spaces between components (-S2 if none given)") |
428 | print("-S<string> for custom separator (use \' | \' to include spaces)") |
429 | print("-M<string> for custom component name (\'My custom name: \')") |
430 | print("-T - test execution time") |
431 | print("-all - display all possible data (for use in terminal)\n") |
432 | |
433 | print("-I<component> - show an icon before text; 1 component per executor allowed") |
434 | print("-W<number> - select 0 to n-th element from multiple output (drives, mountpoints)\n") |
435 | |
436 | |
437 | def icon_path(home, component): |
438 | icons = {'g': '', |
439 | 'p': 'cpu.svg', |
440 | 'a': 'cpu.svg', |
441 | 'q': 'cpu.svg', |
442 | 'Q': 'cpu.svg', |
443 | 's': 'cpu.svg', |
444 | 'S': 'cpu.svg', |
445 | 't': 'temp.svg', |
446 | 'f': 'fan.svg', |
447 | 'm': 'network-card.svg', |
448 | 'M': 'network-card.svg', |
449 | 'c': 'network-card.svg', |
450 | 'C': 'network-card.svg', |
451 | 'w': 'swap.svg', |
452 | 'W': 'swap.svg', |
453 | 'x': 'swap.svg', |
454 | 'd': 'drive-harddisk.svg', |
455 | 'D': 'drive-harddisk.svg', |
456 | 'n': 'drive-harddisk.svg', |
457 | 'N': 'drive-harddisk.svg', |
458 | 'u': 'system.svg', |
459 | 'U': 'system.svg'} |
460 | try: |
461 | f_name = icons[component] |
462 | except KeyError: |
463 | return "" |
464 | |
465 | return icon_to_use(home, f_name) |
466 | |
467 | |
468 | def net_icon(home, ul, dl): |
469 | f_name = "knemo-monitor-transmit.svg" |
470 | return icon_to_use(home, f_name) |
471 | |
472 | |
473 | def icon_to_use(home, f_name): |
474 | icon_custom = home + '/.local/share/psuinfo/' + f_name |
475 | icon_default = "/usr/share/icons/MB-Mango-Suru-GLOW/devices/16/" + f_name |
476 | if os.path.isfile(icon_custom): |
477 | return icon_custom |
478 | else: |
479 | return icon_default |
480 | |
481 | |
482 | if __name__ == "__main__": |
483 | main() |