mass update.
[my-dotfiles.git] / oldies / executors / psuinfo.py
diff --git a/oldies/executors/psuinfo.py b/oldies/executors/psuinfo.py
new file mode 100755 (executable)
index 0000000..40b6907
--- /dev/null
@@ -0,0 +1,483 @@
+#!/usr/bin/env python3
+# _*_ coding: utf-8 _*_
+
+"""
+A psutil-based command to display customizable system usage info in a single line, intended for Tint2 executors
+
+Author: Piotr Miller
+e-mail: nwg.piotr@gmail.com
+Website: http://nwg.pl
+Project: https://github.com/nwg-piotr/psuinfo
+License: GPL3
+
+Inspired by https://github.com/tknomanzr/scripts/blob/master/tint2/executors/cpu.py by William Bradley (@tknomanzr)
+"""
+
+import sys
+import psutil
+import time
+import os
+
+
+def main():
+    fahrenheit = False
+    names = False
+    testing = False
+    time_start = None
+    components = "gStfM"
+    separator = "  "
+    home = os.getenv("HOME")
+    draw_icons = False
+
+    pcpu, avg, speed, freqs, temp, fans, b_time, memory, swap, disks_usage, which, ul, dl, xfer_start, xfer_finish, \
+        path_to_icon, c_name= None, None, None, None, None, None, None, None, None, None, None, None, None, None, \
+        None, None, None
+
+    for i in range(1, len(sys.argv)):
+        if sys.argv[i] == "-h" or sys.argv[i] == "--help":
+            print_help()
+            exit(0)
+
+        if sys.argv[i] == "-F":
+            fahrenheit = True
+
+        if sys.argv[i] == "-N":
+            names = True
+
+        if sys.argv[i] == "-T":
+            testing = True
+
+        if sys.argv[i].startswith("-C"):
+            components = sys.argv[i][2::]
+
+        if sys.argv[i].startswith("-S"):
+            try:
+                # if number given
+                spacing = int(sys.argv[i][2::])
+                separator = " " * spacing
+            except ValueError:
+                # string given
+                separator = sys.argv[i][2::]
+
+        if sys.argv[i].startswith("-W"):
+            try:
+                which = int(sys.argv[i][2::])
+            except ValueError:
+                pass
+
+        if sys.argv[i].upper() == "-ALL":
+            components = "gpaQStfMcWDUk"
+            names = True
+            testing = True
+
+        if sys.argv[i].startswith("-I"):
+            draw_icons = True
+            # We can only have one icon per executor, so let's strip components to the first one
+            components = sys.argv[i][2]
+            # exception for UL/DL speed; to assign an icon to it we need to calculate speeds first
+            if components != "k":
+                path_to_icon = icon_path(home, components)
+
+        if sys.argv[i].startswith("-M"):
+            # We can only have a custom name for a single component
+            components = components[0]
+            names = True
+            c_name = sys.argv[i][2::]
+
+    if testing:
+        time_start = int(round(time.time() * 1000))
+
+    output = ""
+
+    # Prepare ONLY requested data, ONLY once
+    if "g" or "p" in components:
+        try:
+            pcpu = psutil.cpu_percent(interval=1, percpu=True)
+        except:
+            pass
+
+    if "a" in components:
+        try:
+            avg = str(psutil.cpu_percent(interval=1))
+            if len(avg) < 4:
+                avg = " " + avg
+        except:
+            pass
+
+    if "s" or "S" in components:
+        try:
+            speed = psutil.cpu_freq(False)
+        except:
+            pass
+
+    if "q" or "Q" in components:
+        try:
+            freqs = psutil.cpu_freq(True)
+            if len(freqs) == 0:
+                freqs = None
+        except:
+            pass
+
+    if "t" in components:
+        try:
+            temp = psutil.sensors_temperatures(fahrenheit)
+        except:
+            pass
+
+    if "f" in components:
+        try:
+            fans = psutil.sensors_fans()
+        except:
+            pass
+
+    if "m" or "M" or "z" or "Z" in components:
+        try:
+            memory = psutil.virtual_memory()
+        except:
+            pass
+
+    if "w" or "W" or "x" in components:
+        try:
+            swap = psutil.swap_memory()
+        except:
+            pass
+
+    if "k" in components:
+        try:
+            xfer_start = psutil.net_io_counters()
+            time.sleep(1)
+            xfer_finish = psutil.net_io_counters()
+            ul = (xfer_finish[0] - xfer_start[0]) / 1024
+            dl = (xfer_finish[1] - xfer_start[1]) / 1024
+            # We've not selected an icon previously. Now we have enough data.
+            if draw_icons:
+                path_to_icon = net_icon(home, ul, dl)
+        except:
+            pass
+
+    drives = []
+    # Find drive names, mountpoints
+    if "d" or "D" or "n" or "N" in components:
+        try:
+            d = psutil.disk_partitions()
+            # This will store name, mountpoint
+            for entry in d:
+                n = entry[0].split("/")
+                name = n[len(n) - 1]
+                # name, mountpoint
+                drive = name, entry[1]
+                drives.append(drive)
+        except:
+            pass
+
+    if "d" or "D" in components:
+        try:
+            disks_usage = []
+            for drive in drives:
+                # Search drives by path
+                data = psutil.disk_usage(drive[1])
+                # Store name, used, total, percent
+                essential = drive[0].upper(), data[1], data[0], data[3]
+                disks_usage.append(essential)
+        except:
+            pass
+
+    if "n" in components or "N" in components:
+        try:
+            disks_usage = []
+            for drive in drives:
+                # Search drives by path
+                data = psutil.disk_usage(drive[1])
+                # Store mountpoint, used, total, percent
+                essential = drive[1], data[1], data[0], data[3]
+                disks_usage.append(essential)
+        except:
+            pass
+
+    if "u" or "U" in components:
+        try:
+            b_time = psutil.boot_time()
+        except:
+            pass
+
+    # Build output component after component
+    output += separator
+
+    for char in components:
+        if char == "g" and pcpu is not None:
+            if c_name:
+                output += c_name
+            output += graph_per_cpu(pcpu) + separator
+
+        if char == "p" and pcpu is not None:
+            if names:
+                output += c_name if c_name else "CPU: "
+            output += per_cpu(pcpu) + separator
+
+        if char == "a" and avg is not None:
+            if names:
+                output += c_name if c_name else "avCPU: "
+            output += avg + "%" + separator
+
+        if char == "q" and freqs is not None:
+            if names:
+                output += c_name if c_name else "CPU: "
+            output += freq_per_cpu(freqs)[0][:-1] + " GHz" + separator
+
+        if char == "Q" and freqs is not None:
+            if names:
+                output += c_name if c_name else "CPU: "
+            result = freq_per_cpu(freqs)
+            output += result[0][:-1] + "/" + str(result[1]) + " GHz" + separator
+
+        if char == "s" and speed is not None:
+            if names:
+                output += c_name if c_name else "SPD: "
+            output += str(round(speed[0] / 1000, 1)) + " GHz" + separator
+
+        if char == "S" and speed is not None:
+            if names:
+                output += c_name if c_name else "avSPD: "
+            output += str(round(speed[0] / 1000, 1)) + "/" + str(round(speed[2] / 1000, 1)) + " GHz" + separator
+
+        if char == "t" and temp is not None and len(temp) > 0:
+            if names:
+                output += c_name if c_name else "CORE: "
+            if "k10temp" in temp.keys():
+                # ryzen, multiple Die temperatures for threadripper/Epyc
+                ryzen_die_temps = [sensor.current for sensor in temp["k10temp"] if sensor.label == 'Tdie']
+                output += str(int(max(ryzen_die_temps)))
+            if "coretemp" in temp.keys():
+                # intel
+                output += str(int(temp["coretemp"][0][1]))
+            output += "℉" if fahrenheit else "℃"
+            output += separator
+
+        if char == "f" and fans is not None and len(fans) > 0:
+            if names:
+                output += c_name if c_name else "FAN: "
+            fan0 = next(iter(fans.values()))
+            output += str(fan0[0][1]) + "/m" + separator
+
+        if char == 'm' and memory is not None:
+            if names:
+                output += c_name if c_name else "MEM: "
+            output += str(round((memory[0] - memory[1]) / 1073741824, 1)) + " GB" + separator
+
+        if char == 'M' and memory is not None:
+            if names:
+                output += c_name if c_name else "MEM: "
+            output += str(round((memory[3]) / 1073741824, 1)) + "/" + str(
+                round(memory[0] / 1073741824, 1)) + " GB" + separator
+
+        if char == 'c' and memory is not None:
+            if names:
+                output += c_name if c_name else "MEM: "
+            output += str(memory[2]) + "%" + separator
+
+        if char == 'C' and memory is not None:
+            if names:
+                output += c_name if c_name else "MEM: "
+            output += str(100 - memory[2]) + "%" + separator
+
+        if char == 'u' and b_time is not None:
+            up_time = int(time.time()) - b_time
+            m, s = divmod(up_time, 60)
+            h, m = divmod(m, 60)
+            if names:
+                output += c_name if c_name else "UP: "
+            output += "%d:%02d" % (h, m) + separator
+
+        if char == 'U' and b_time is not None:
+            up_time = int(time.time()) - b_time
+            m, s = divmod(up_time, 60)
+            h, m = divmod(m, 60)
+            if names:
+                output += c_name if c_name else "UP: "
+            output += "%d:%02d:%02d" % (h, m, s) + separator
+
+        if char == "w" and swap is not None:
+            if names:
+                output += c_name if c_name else "SWAP: "
+            output += str(round(swap[1] / 1073741824, 1)) + " GB" + separator
+
+        if char == "W" and swap is not None:
+            if names:
+                output += c_name if c_name else "SWAP: "
+            output += str(round(swap[1] / 1073741824, 1)) + "/"
+            output += str(round(swap[0] / 1073741824, 1)) + " GB" + separator
+
+        if char == "x" and swap is not None:
+            if names:
+                output += c_name if c_name else "SWAP: "
+            output += str(swap[3]) + "%" + separator
+
+        if char == "d" or char == "n" and disks_usage is not None:
+            if which is not None:
+                try:
+                    entry = disks_usage[which]
+                    output += entry[0] + ": "
+                    output += str(entry[3]) + "%" + separator
+                except IndexError:
+                    pass
+            else:
+                for entry in disks_usage:
+                    output += entry[0] + ": "
+                    output += str(entry[3]) + "%" + separator
+
+        if char == "D" or char == "N" and disks_usage is not None:
+            if c_name:
+                output += c_name
+            if which is not None:
+                try:
+                    entry = disks_usage[which]
+                    output += entry[0] + ": "
+                    output += str(round(entry[1] / 1073741824, 1)) + "/"
+                    output += str(round(entry[2] / 1073741824, 1)) + " GB" + separator
+                except IndexError:
+                    pass
+            else:
+                for entry in disks_usage:
+                    output += entry[0] + ": "
+                    output += str(round(entry[1] / 1073741824, 1)) + "/"
+                    output += str(round(entry[2] / 1073741824, 1)) + " GB" + separator
+
+        if char == "k":
+            if names and xfer_start is not None and xfer_finish is not None:
+                output += c_name if c_name else "Net: "
+            output += '{:0.2f}'.format((xfer_finish[0] - xfer_start[0]) / 1024) + '  {:0.2f} kB/s'.format(
+                (xfer_finish[1] - xfer_start[1]) / 1024) + separator
+
+    if testing:
+        output += "[" + str(int((round(time.time() * 1000)) - time_start) / 1000) + "s]" + separator
+
+    # remove leading and trailing separator
+    l = len(separator)
+    if l > 0:
+        output = output[l:-l]
+
+    if draw_icons:
+        print(path_to_icon)
+
+    print(output)
+
+
+def per_cpu(result):
+    string = ""
+    for val in result:
+        proc = str(int(round(val, 1)))
+        if len(proc) < 2:
+            proc = " " + proc
+        string += proc + "% "
+    return string
+
+
+def freq_per_cpu(result):
+    string = ""
+    max_freq = 0
+    for val in result:
+        freq = str(round(val[0] / 1000, 1))
+        string += freq + "|"
+        max_freq = str(round(val[2] / 1000, 1))
+
+    return string, max_freq
+
+
+def graph_per_cpu(result):
+    graph = "_▁▂▃▄▅▆▇███"
+
+    string = ""
+    for val in result:
+        proc = int(round(val / 10, 0))
+        string += graph[proc]
+    return string
+
+
+def print_help():
+
+    print("\npsuinfo [-C{components}] | [-I{component}] [-F] [-N] [-S<number>] | [-S<string>] [-T] [-W{number}] [-all] [-h] [--help]")
+
+    print("\n-C defines multiple components. -I defines a single component. If none given, -CgStfM argument will be used by default.\n")
+    print("  g - (g)raphical CPU load bar")
+    print("  p - (p)ercentage for each core (text)")
+    print("  a - (a)verage CPU load (text)")
+    print("  q - fre(q)ency for each thread")
+    print("  Q - fre(Q)ency for each thread/max frequency")
+    print("  s - current CPU (s)peed")
+    print("  S - current/max CPU (S)peed")
+    print("  t - CPU (t)emperature")
+    print("  f - (f)an speed")
+    print("  m - (m)emory in use")
+    print("  M - (M)emory in use/total")
+    print("  c - used memory per(c)entage")
+    print("  C - free memory per(C)entage")
+    print("  w - s(w)ap memory in use")
+    print("  W - s(W)ap memory in use/total")
+    print("  x - swap usage in %")
+    print("  d - (d)rives as names usage in %")
+    print("  D - (D)rives as names used/total")
+    print("  n - drives as mou(n)tpoints usage in %")
+    print("  N - drives as mou(N)tpoints used/total")
+    print("  u - (u)ptime HH:MM")
+    print("  U - (U)ptime HH:MM:SS")
+    print("  k - current networ(k) traffic as upload/download in kB/s")
+
+    print("\n-F - use Fahrenheit instead of ℃")
+    print("-N - display field names (except for (g)raphical CPU load bar)")
+    print("-S<number> - number of spaces between components (-S2 if none given)")
+    print("-S<string> for custom separator (use \' | \' to include spaces)")
+    print("-M<string> for custom component name (\'My custom name: \')")
+    print("-T - test execution time")
+    print("-all - display all possible data (for use in terminal)\n")
+
+    print("-I<component> - show an icon before text; 1 component per executor allowed")
+    print("-W<number> - select 0 to n-th element from multiple output (drives, mountpoints)\n")
+
+
+def icon_path(home, component):
+    icons = {'g': '',
+             'p': 'cpu.svg',
+             'a': 'cpu.svg',
+             'q': 'cpu.svg',
+             'Q': 'cpu.svg',
+             's': 'cpu.svg',
+             'S': 'cpu.svg',
+             't': 'temp.svg',
+             'f': 'fan.svg',
+             'm': 'network-card.svg',
+             'M': 'network-card.svg',
+             'c': 'network-card.svg',
+             'C': 'network-card.svg',
+             'w': 'swap.svg',
+             'W': 'swap.svg',
+             'x': 'swap.svg',
+             'd': 'drive-harddisk.svg',
+             'D': 'drive-harddisk.svg',
+             'n': 'drive-harddisk.svg',
+             'N': 'drive-harddisk.svg',
+             'u': 'system.svg',
+             'U': 'system.svg'}
+    try:
+        f_name = icons[component]
+    except KeyError:
+        return ""
+
+    return icon_to_use(home, f_name)
+
+
+def net_icon(home, ul, dl):
+    f_name = "knemo-monitor-transmit.svg"
+    return icon_to_use(home, f_name)
+
+
+def icon_to_use(home, f_name):
+    icon_custom = home + '/.local/share/psuinfo/' + f_name
+    icon_default = "/usr/share/icons/MB-Mango-Suru-GLOW/devices/16/" + f_name
+    if os.path.isfile(icon_custom):
+        return icon_custom
+    else:
+        return icon_default
+
+
+if __name__ == "__main__":
+    main()