mass update.
[my-dotfiles.git] / oldies / executors / psuinfo.py
CommitLineData
fdd76fc5 1#!/usr/bin/env python3
2# _*_ coding: utf-8 _*_
3
4"""
5A psutil-based command to display customizable system usage info in a single line, intended for Tint2 executors
6
7Author: Piotr Miller
8e-mail: nwg.piotr@gmail.com
9Website: http://nwg.pl
10Project: https://github.com/nwg-piotr/psuinfo
11License: GPL3
12
13Inspired by https://github.com/tknomanzr/scripts/blob/master/tint2/executors/cpu.py by William Bradley (@tknomanzr)
14"""
15
16import sys
17import psutil
18import time
19import os
20
21
22def 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
365def 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
375def 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
386def 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
396def 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
437def 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
468def net_icon(home, ul, dl):
469 f_name = "knemo-monitor-transmit.svg"
470 return icon_to_use(home, f_name)
471
472
473def 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
482if __name__ == "__main__":
483 main()