diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2020-10-17 00:53:59 +0200 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2020-10-17 00:53:59 +0200 |
commit | a8cd5893a920adcd5b8922b80748461fbea8dc1c (patch) | |
tree | f74c5f7cfc8ae84319f201f21528c51c5ba553e7 /scripts | |
parent | bd4d2bf420e1dfa3298143daebd485b97335b256 (diff) | |
download | psutil-a8cd5893a920adcd5b8922b80748461fbea8dc1c.tar.gz |
pypi download stats script
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/battery.py | 2 | ||||
-rwxr-xr-x | scripts/cpu_distribution.py | 2 | ||||
-rwxr-xr-x | scripts/disk_usage.py | 2 | ||||
-rwxr-xr-x | scripts/free.py | 2 | ||||
-rwxr-xr-x | scripts/ifconfig.py | 2 | ||||
-rw-r--r-- | scripts/internal/download_wheels.py | 154 | ||||
-rwxr-xr-x | scripts/internal/download_wheels_appveyor.py (renamed from scripts/internal/win_download_wheels.py) | 26 | ||||
-rwxr-xr-x | scripts/internal/download_wheels_github.py | 81 | ||||
-rwxr-xr-x | scripts/internal/print_announce.py | 2 | ||||
-rwxr-xr-x | scripts/internal/print_downloads.py | 154 | ||||
-rwxr-xr-x | scripts/internal/print_wheels.py | 66 | ||||
-rwxr-xr-x | scripts/iotop.py | 46 | ||||
-rwxr-xr-x | scripts/meminfo.py | 2 | ||||
-rwxr-xr-x | scripts/netstat.py | 2 | ||||
-rwxr-xr-x | scripts/nettop.py | 57 | ||||
-rwxr-xr-x | scripts/pmap.py | 2 | ||||
-rwxr-xr-x | scripts/procinfo.py | 2 | ||||
-rwxr-xr-x | scripts/ps.py | 2 | ||||
-rwxr-xr-x | scripts/pstree.py | 2 | ||||
-rwxr-xr-x | scripts/sensors.py | 2 | ||||
-rwxr-xr-x | scripts/temperatures.py | 2 | ||||
-rwxr-xr-x | scripts/top.py | 79 | ||||
-rwxr-xr-x | scripts/who.py | 2 | ||||
-rwxr-xr-x | scripts/winservices.py | 2 |
24 files changed, 435 insertions, 260 deletions
diff --git a/scripts/battery.py b/scripts/battery.py index 0da2b958..edf4ce8c 100755 --- a/scripts/battery.py +++ b/scripts/battery.py @@ -7,7 +7,7 @@ """ Show battery information. -$ python scripts/battery.py +$ python3 scripts/battery.py charge: 74% left: 2:11:31 status: discharging diff --git a/scripts/cpu_distribution.py b/scripts/cpu_distribution.py index 08997797..fb39d888 100755 --- a/scripts/cpu_distribution.py +++ b/scripts/cpu_distribution.py @@ -7,7 +7,7 @@ """ Shows CPU workload split across different CPUs. -$ python scripts/cpu_workload.py +$ python3 scripts/cpu_workload.py CPU 0 CPU 1 CPU 2 CPU 3 CPU 4 CPU 5 CPU 6 CPU 7 19.8 20.6 18.2 15.8 6.9 17.3 5.0 20.4 gvfsd pytho kwork chrom unity kwork kwork kwork diff --git a/scripts/disk_usage.py b/scripts/disk_usage.py index 901dbf8c..851ae9b1 100755 --- a/scripts/disk_usage.py +++ b/scripts/disk_usage.py @@ -7,7 +7,7 @@ """ List all mounted disk partitions a-la "df -h" command. -$ python scripts/disk_usage.py +$ python3 scripts/disk_usage.py Device Total Used Free Use % Type Mount /dev/sdb3 18.9G 14.7G 3.3G 77% ext4 / /dev/sda6 345.9G 83.8G 244.5G 24% ext4 /home diff --git a/scripts/free.py b/scripts/free.py index 000323c5..8c3359d8 100755 --- a/scripts/free.py +++ b/scripts/free.py @@ -7,7 +7,7 @@ """ A clone of 'free' cmdline utility. -$ python scripts/free.py +$ python3 scripts/free.py total used free shared buffers cache Mem: 10125520 8625996 1499524 0 349500 3307836 Swap: 0 0 0 diff --git a/scripts/ifconfig.py b/scripts/ifconfig.py index cfd02f0d..ae137fb4 100755 --- a/scripts/ifconfig.py +++ b/scripts/ifconfig.py @@ -7,7 +7,7 @@ """ A clone of 'ifconfig' on UNIX. -$ python scripts/ifconfig.py +$ python3 scripts/ifconfig.py lo: stats : speed=0MB, duplex=?, mtu=65536, up=yes incoming : bytes=1.95M, pkts=22158, errs=0, drops=0 diff --git a/scripts/internal/download_wheels.py b/scripts/internal/download_wheels.py deleted file mode 100644 index 1834f1b3..00000000 --- a/scripts/internal/download_wheels.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -""" -Script which downloads wheel files hosted on GitHub: -https://github.com/giampaolo/psutil/actions -It needs an access token string generated from personal GitHub profile: -https://github.com/settings/tokens -The token must be created with at least "public_repo" scope/rights. -If you lose it, just generate a new token. -REST API doc: -https://developer.github.com/v3/actions/artifacts/ -""" - -import argparse -import collections -import json -import os -import requests -import shutil -import zipfile - -from psutil import __version__ as PSUTIL_VERSION -from psutil._common import bytes2human -from psutil._common import print_color - - -USER = "" -PROJECT = "" -TOKEN = "" -OUTFILE = "wheels.zip" - - -# --- GitHub API - - -def get_artifacts(): - base_url = "https://api.github.com/repos/%s/%s" % (USER, PROJECT) - url = base_url + "/actions/artifacts" - res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) - res.raise_for_status() - data = json.loads(res.content) - return data - - -def download_zip(url): - print("downloading: " + url) - res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) - res.raise_for_status() - totbytes = 0 - with open(OUTFILE, 'wb') as f: - for chunk in res.iter_content(chunk_size=16384): - f.write(chunk) - totbytes += len(chunk) - print("got %s, size %s)" % (OUTFILE, bytes2human(totbytes))) - - -# --- extract - - -def rename_27_wheels(): - # See: https://github.com/giampaolo/psutil/issues/810 - src = 'dist/psutil-%s-cp27-cp27m-win32.whl' % PSUTIL_VERSION - dst = 'dist/psutil-%s-cp27-none-win32.whl' % PSUTIL_VERSION - print("rename: %s\n %s" % (src, dst)) - os.rename(src, dst) - src = 'dist/psutil-%s-cp27-cp27m-win_amd64.whl' % PSUTIL_VERSION - dst = 'dist/psutil-%s-cp27-none-win_amd64.whl' % PSUTIL_VERSION - print("rename: %s\n %s" % (src, dst)) - os.rename(src, dst) - - -def extract(): - with zipfile.ZipFile(OUTFILE, 'r') as zf: - zf.extractall('dist') - - -def print_wheels(): - def is64bit(name): - return name.endswith(('x86_64.whl', 'amd64.whl')) - - groups = collections.defaultdict(list) - for name in os.listdir('dist'): - plat = name.split('-')[-1] - pyimpl = name.split('-')[3] - ispypy = 'pypy' in pyimpl - if 'linux' in plat: - if ispypy: - groups['pypy_on_linux'].append(name) - else: - groups['linux'].append(name) - elif 'win' in plat: - if ispypy: - groups['pypy_on_windows'].append(name) - else: - groups['windows'].append(name) - elif 'macosx' in plat: - if ispypy: - groups['pypy_on_macos'].append(name) - else: - groups['macos'].append(name) - else: - assert 0, name - - totsize = 0 - templ = "%-54s %7s %7s %7s" - for platf, names in groups.items(): - ppn = "%s (total = %s)" % (platf.replace('_', ' '), len(names)) - s = templ % (ppn, "size", "arch", "pyver") - print_color('\n' + s, color=None, bold=True) - for name in sorted(names): - path = os.path.join('dist', name) - size = os.path.getsize(path) - totsize += size - arch = '64' if is64bit(name) else '32' - pyver = 'pypy' if name.split('-')[3].startswith('pypy') else 'py' - pyver += name.split('-')[2][2:] - s = templ % (name, bytes2human(size), arch, pyver) - if 'pypy' in pyver: - print_color(s, color='violet') - else: - print_color(s, color='brown') - - -def run(): - if os.path.isdir('dist'): - shutil.rmtree('dist') - data = get_artifacts() - download_zip(data['artifacts'][0]['archive_download_url']) - os.mkdir('dist') - extract() - # rename_27_wheels() - print_wheels() - - -def main(): - global USER, PROJECT, TOKEN - parser = argparse.ArgumentParser(description='GitHub wheels downloader') - parser.add_argument('--user', required=True) - parser.add_argument('--project', required=True) - parser.add_argument('--tokenfile', required=True) - args = parser.parse_args() - USER = args.user - PROJECT = args.project - with open(os.path.expanduser(args.tokenfile)) as f: - TOKEN = f.read().strip() - run() - - -if __name__ == '__main__': - main() diff --git a/scripts/internal/win_download_wheels.py b/scripts/internal/download_wheels_appveyor.py index 8dae0573..b7c0aeae 100755 --- a/scripts/internal/win_download_wheels.py +++ b/scripts/internal/download_wheels_appveyor.py @@ -15,10 +15,8 @@ http://code.saghul.net/index.php/2015/09/09/ from __future__ import print_function import argparse import concurrent.futures -import errno import os import requests -import shutil import sys from psutil import __version__ as PSUTIL_VERSION @@ -29,33 +27,12 @@ from psutil._common import print_color BASE_URL = 'https://ci.appveyor.com/api' PY_VERSIONS = ['2.7', '3.5', '3.6', '3.7', '3.8'] TIMEOUT = 30 -COLORS = True - - -def safe_makedirs(path): - try: - os.makedirs(path) - except OSError as err: - if err.errno == errno.EEXIST: - if not os.path.isdir(path): - raise - else: - raise - - -def safe_rmtree(path): - def onerror(fun, path, excinfo): - exc = excinfo[1] - if exc.errno != errno.ENOENT: - raise - - shutil.rmtree(path, onerror=onerror) def download_file(url): local_fname = url.split('/')[-1] local_fname = os.path.join('dist', local_fname) - safe_makedirs('dist') + os.makedirs('dist', exist_ok=True) r = requests.get(url, stream=True, timeout=TIMEOUT) tot_bytes = 0 with open(local_fname, 'wb') as f: @@ -102,7 +79,6 @@ def rename_27_wheels(): def run(options): - safe_rmtree('dist') urls = get_file_urls(options) completed = 0 exc = None diff --git a/scripts/internal/download_wheels_github.py b/scripts/internal/download_wheels_github.py new file mode 100755 index 00000000..4aa50d5d --- /dev/null +++ b/scripts/internal/download_wheels_github.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Script which downloads wheel files hosted on GitHub: +https://github.com/giampaolo/psutil/actions +It needs an access token string generated from personal GitHub profile: +https://github.com/settings/tokens +The token must be created with at least "public_repo" scope/rights. +If you lose it, just generate a new token. +REST API doc: +https://developer.github.com/v3/actions/artifacts/ +""" + +import argparse +import json +import os +import requests +import zipfile + +from psutil._common import bytes2human +from psutil.tests import safe_rmpath + + +USER = "" +PROJECT = "" +TOKEN = "" +OUTFILE = "wheels-github.zip" + + +def get_artifacts(): + base_url = "https://api.github.com/repos/%s/%s" % (USER, PROJECT) + url = base_url + "/actions/artifacts" + res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) + res.raise_for_status() + data = json.loads(res.content) + return data + + +def download_zip(url): + print("downloading: " + url) + res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) + res.raise_for_status() + totbytes = 0 + with open(OUTFILE, 'wb') as f: + for chunk in res.iter_content(chunk_size=16384): + f.write(chunk) + totbytes += len(chunk) + print("got %s, size %s)" % (OUTFILE, bytes2human(totbytes))) + + +def run(): + data = get_artifacts() + download_zip(data['artifacts'][0]['archive_download_url']) + os.makedirs('dist', exist_ok=True) + with zipfile.ZipFile(OUTFILE, 'r') as zf: + zf.extractall('dist') + + +def main(): + global USER, PROJECT, TOKEN + parser = argparse.ArgumentParser(description='GitHub wheels downloader') + parser.add_argument('--user', required=True) + parser.add_argument('--project', required=True) + parser.add_argument('--tokenfile', required=True) + args = parser.parse_args() + USER = args.user + PROJECT = args.project + with open(os.path.expanduser(args.tokenfile)) as f: + TOKEN = f.read().strip() + try: + run() + finally: + safe_rmpath(OUTFILE) + + +if __name__ == '__main__': + main() diff --git a/scripts/internal/print_announce.py b/scripts/internal/print_announce.py index 9569c367..180bf377 100755 --- a/scripts/internal/print_announce.py +++ b/scripts/internal/print_announce.py @@ -58,7 +58,7 @@ Links -- -Giampaolo - http://grodola.blogspot.com +Giampaolo - https://gmpy.dev/about """ diff --git a/scripts/internal/print_downloads.py b/scripts/internal/print_downloads.py new file mode 100755 index 00000000..81132db9 --- /dev/null +++ b/scripts/internal/print_downloads.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Print PYPI statistics in MarkDown format. +Useful sites: +* https://pepy.tech/project/psutil +* https://pypistats.org/packages/psutil +* https://hugovk.github.io/top-pypi-packages/ +""" + +from __future__ import print_function +import json +import os +import subprocess +import sys + +import pypinfo # NOQA + +from psutil._common import memoize + + +AUTH_FILE = os.path.expanduser("~/.pypinfo.json") +PKGNAME = 'psutil' +DAYS = 30 +LIMIT = 100 +GITHUB_SCRIPT_URL = "https://github.com/giampaolo/psutil/blob/master/" \ + "scripts/internal/pypistats.py" +bytes_billed = 0 + + +# --- get + +@memoize +def sh(cmd): + assert os.path.exists(AUTH_FILE) + env = os.environ.copy() + env['GOOGLE_APPLICATION_CREDENTIALS'] = AUTH_FILE + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, universal_newlines=True) + stdout, stderr = p.communicate() + if p.returncode != 0: + raise RuntimeError(stderr) + assert not stderr, stderr + return stdout.strip() + + +@memoize +def query(cmd): + global bytes_billed + ret = json.loads(sh(cmd)) + bytes_billed += ret['query']['bytes_billed'] + return ret + + +def top_packages(): + return query( + f"pypinfo --all --json --days {DAYS} --limit {LIMIT} '' project") + + +def ranking(): + data = top_packages() + for i, line in enumerate(data['rows'], 1): + if line['project'] == PKGNAME: + return i + raise ValueError(f"can't find {PKGNAME}") + + +def downloads(): + data = top_packages() + for line in data['rows']: + if line['project'] == PKGNAME: + return line['download_count'] + raise ValueError(f"can't find {PKGNAME}") + + +def downloads_pyver(): + return query(f"pypinfo --json --days {DAYS} {PKGNAME} pyversion") + + +def downloads_by_country(): + return query(f"pypinfo --json --days {DAYS} {PKGNAME} country") + + +def downloads_by_system(): + return query(f"pypinfo --json --days {DAYS} {PKGNAME} system") + + +def downloads_by_distro(): + return query(f"pypinfo --json --days {DAYS} {PKGNAME} distro") + + +# --- print + + +templ = "| %-30s | %15s |" + + +def print_row(left, right): + if isinstance(right, int): + right = '{0:,}'.format(right) + print(templ % (left, right)) + + +def print_header(left, right="Downloads"): + print_row(left, right) + s = templ % ("-" * 30, "-" * 15) + print("|:" + s[2:-2] + ":|") + + +def print_markdown_table(title, left, rows): + pleft = left.replace('_', ' ').capitalize() + print("### " + title) + print() + print_header(pleft) + for row in rows: + lval = row[left] + print_row(lval, row['download_count']) + print() + + +def main(): + last_update = top_packages()['last_update'] + print("# Download stats") + print("") + s = f"psutil download statistics of the last {DAYS} days (last update " + s += f"*{last_update}*).\n" + s += f"Generated via [pypistats.py]({GITHUB_SCRIPT_URL}) script.\n" + print(s) + + data = [ + {'what': 'Per month', 'download_count': downloads()}, + {'what': 'Per day', 'download_count': int(downloads() / 30)}, + {'what': 'PYPI ranking', 'download_count': ranking()} + ] + print_markdown_table('Overview', 'what', data) + print_markdown_table('Operating systems', 'system_name', + downloads_by_system()['rows']) + print_markdown_table('Distros', 'distro_name', + downloads_by_distro()['rows']) + print_markdown_table('Python versions', 'python_version', + downloads_pyver()['rows']) + print_markdown_table('Countries', 'country', + downloads_by_country()['rows']) + + +if __name__ == '__main__': + try: + main() + finally: + print("bytes billed: %s" % bytes_billed, file=sys.stderr) diff --git a/scripts/internal/print_wheels.py b/scripts/internal/print_wheels.py new file mode 100755 index 00000000..be8290e0 --- /dev/null +++ b/scripts/internal/print_wheels.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Nicely print wheels print in dist/ directory.""" + +import collections +import glob +import os + +from psutil._common import print_color +from psutil._common import bytes2human + + +def main(): + def is64bit(name): + return name.endswith(('x86_64.whl', 'amd64.whl')) + + groups = collections.defaultdict(list) + for path in glob.glob('dist/*.whl'): + name = os.path.basename(path) + plat = name.split('-')[-1] + pyimpl = name.split('-')[3] + ispypy = 'pypy' in pyimpl + if 'linux' in plat: + if ispypy: + groups['pypy_on_linux'].append(name) + else: + groups['linux'].append(name) + elif 'win' in plat: + if ispypy: + groups['pypy_on_windows'].append(name) + else: + groups['windows'].append(name) + elif 'macosx' in plat: + if ispypy: + groups['pypy_on_macos'].append(name) + else: + groups['macos'].append(name) + else: + assert 0, name + + totsize = 0 + templ = "%-54s %7s %7s %7s" + for platf, names in groups.items(): + ppn = "%s (total = %s)" % (platf.replace('_', ' '), len(names)) + s = templ % (ppn, "size", "arch", "pyver") + print_color('\n' + s, color=None, bold=True) + for name in sorted(names): + path = os.path.join('dist', name) + size = os.path.getsize(path) + totsize += size + arch = '64' if is64bit(name) else '32' + pyver = 'pypy' if name.split('-')[3].startswith('pypy') else 'py' + pyver += name.split('-')[2][2:] + s = templ % (name, bytes2human(size), arch, pyver) + if 'pypy' in pyver: + print_color(s, color='violet') + else: + print_color(s, color='brown') + + +if __name__ == '__main__': + main() diff --git a/scripts/iotop.py b/scripts/iotop.py index c3afd071..04683673 100755 --- a/scripts/iotop.py +++ b/scripts/iotop.py @@ -14,7 +14,7 @@ It doesn't work on Windows as curses module is required. Example output: -$ python scripts/iotop.py +$ python3 scripts/iotop.py Total DISK READ: 0.00 B/s | Total DISK WRITE: 472.00 K/s PID USER DISK READ DISK WRITE COMMAND 13155 giampao 0.00 B/s 428.00 K/s /usr/bin/google-chrome-beta @@ -30,7 +30,6 @@ PID USER DISK READ DISK WRITE COMMAND Author: Giampaolo Rodola' <g.rodola@gmail.com> """ -import atexit import time import sys try: @@ -42,20 +41,11 @@ import psutil from psutil._common import bytes2human -def tear_down(): - win.keypad(0) - curses.nocbreak() - curses.echo() - curses.endwin() - - win = curses.initscr() -atexit.register(tear_down) -curses.endwin() lineno = 0 -def print_line(line, highlight=False): +def printl(line, highlight=False): """A thin wrapper around curses's addstr().""" global lineno try: @@ -129,10 +119,10 @@ def refresh_window(procs, disks_read, disks_write): disks_tot = "Total DISK READ: %s | Total DISK WRITE: %s" \ % (bytes2human(disks_read), bytes2human(disks_write)) - print_line(disks_tot) + printl(disks_tot) header = templ % ("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND") - print_line(header, highlight=True) + printl(header, highlight=True) for p in procs: line = templ % ( @@ -142,21 +132,45 @@ def refresh_window(procs, disks_read, disks_write): bytes2human(p._write_per_sec), p._cmdline) try: - print_line(line) + printl(line) except curses.error: break win.refresh() +def setup(): + curses.start_color() + curses.use_default_colors() + for i in range(0, curses.COLORS): + curses.init_pair(i + 1, i, -1) + curses.endwin() + win.nodelay(1) + + +def tear_down(): + win.keypad(0) + curses.nocbreak() + curses.echo() + curses.endwin() + + def main(): + global lineno + setup() try: interval = 0 while True: + if win.getch() == ord('q'): + break args = poll(interval) refresh_window(*args) - interval = 1 + lineno = 0 + interval = 0.5 + time.sleep(interval) except (KeyboardInterrupt, SystemExit): pass + finally: + tear_down() if __name__ == '__main__': diff --git a/scripts/meminfo.py b/scripts/meminfo.py index 550fcd01..b98aa60b 100755 --- a/scripts/meminfo.py +++ b/scripts/meminfo.py @@ -7,7 +7,7 @@ """ Print system memory information. -$ python scripts/meminfo.py +$ python3 scripts/meminfo.py MEMORY ------ Total : 9.7G diff --git a/scripts/netstat.py b/scripts/netstat.py index fe3bfe40..5a21358e 100755 --- a/scripts/netstat.py +++ b/scripts/netstat.py @@ -7,7 +7,7 @@ """ A clone of 'netstat -antp' on Linux. -$ python scripts/netstat.py +$ python3 scripts/netstat.py Proto Local address Remote address Status PID Program name tcp 127.0.0.1:48256 127.0.0.1:45884 ESTABLISHED 13646 chrome tcp 127.0.0.1:47073 127.0.0.1:45884 ESTABLISHED 13646 chrome diff --git a/scripts/nettop.py b/scripts/nettop.py index ce647c9d..8cc19fda 100755 --- a/scripts/nettop.py +++ b/scripts/nettop.py @@ -11,7 +11,7 @@ Shows real-time network statistics. Author: Giampaolo Rodola' <g.rodola@gmail.com> -$ python scripts/nettop.py +$ python3 scripts/nettop.py ----------------------------------------------------------- total bytes: sent: 1.49 G received: 4.82 G total packets: sent: 7338724 received: 8082712 @@ -31,7 +31,6 @@ pkts-sent 0 0 pkts-recv 1214470 0 """ -import atexit import time import sys try: @@ -43,20 +42,11 @@ import psutil from psutil._common import bytes2human -def tear_down(): - win.keypad(0) - curses.nocbreak() - curses.echo() - curses.endwin() - - -win = curses.initscr() -atexit.register(tear_down) -curses.endwin() lineno = 0 +win = curses.initscr() -def print_line(line, highlight=False): +def printl(line, highlight=False): """A thin wrapper around curses's addstr().""" global lineno try: @@ -89,59 +79,80 @@ def refresh_window(tot_before, tot_after, pnic_before, pnic_after): global lineno # totals - print_line("total bytes: sent: %-10s received: %s" % ( + printl("total bytes: sent: %-10s received: %s" % ( bytes2human(tot_after.bytes_sent), bytes2human(tot_after.bytes_recv)) ) - print_line("total packets: sent: %-10s received: %s" % ( + printl("total packets: sent: %-10s received: %s" % ( tot_after.packets_sent, tot_after.packets_recv)) # per-network interface details: let's sort network interfaces so # that the ones which generated more traffic are shown first - print_line("") + printl("") nic_names = list(pnic_after.keys()) nic_names.sort(key=lambda x: sum(pnic_after[x]), reverse=True) for name in nic_names: stats_before = pnic_before[name] stats_after = pnic_after[name] templ = "%-15s %15s %15s" - print_line(templ % (name, "TOTAL", "PER-SEC"), highlight=True) - print_line(templ % ( + printl(templ % (name, "TOTAL", "PER-SEC"), highlight=True) + printl(templ % ( "bytes-sent", bytes2human(stats_after.bytes_sent), bytes2human( stats_after.bytes_sent - stats_before.bytes_sent) + '/s', )) - print_line(templ % ( + printl(templ % ( "bytes-recv", bytes2human(stats_after.bytes_recv), bytes2human( stats_after.bytes_recv - stats_before.bytes_recv) + '/s', )) - print_line(templ % ( + printl(templ % ( "pkts-sent", stats_after.packets_sent, stats_after.packets_sent - stats_before.packets_sent, )) - print_line(templ % ( + printl(templ % ( "pkts-recv", stats_after.packets_recv, stats_after.packets_recv - stats_before.packets_recv, )) - print_line("") + printl("") win.refresh() lineno = 0 +def setup(): + curses.start_color() + curses.use_default_colors() + for i in range(0, curses.COLORS): + curses.init_pair(i + 1, i, -1) + curses.endwin() + win.nodelay(1) + + +def tear_down(): + win.keypad(0) + curses.nocbreak() + curses.echo() + curses.endwin() + + def main(): + setup() try: interval = 0 while True: + if win.getch() == ord('q'): + break args = poll(interval) refresh_window(*args) - interval = 1 + interval = 0.5 except (KeyboardInterrupt, SystemExit): pass + finally: + tear_down() if __name__ == '__main__': diff --git a/scripts/pmap.py b/scripts/pmap.py index 5f7246a1..459927bf 100755 --- a/scripts/pmap.py +++ b/scripts/pmap.py @@ -8,7 +8,7 @@ A clone of 'pmap' utility on Linux, 'vmmap' on macOS and 'procstat -v' on BSD. Report memory map of a process. -$ python scripts/pmap.py 32402 +$ python3 scripts/pmap.py 32402 Address RSS Mode Mapping 0000000000400000 1200K r-xp /usr/bin/python2.7 0000000000838000 4K r--p /usr/bin/python2.7 diff --git a/scripts/procinfo.py b/scripts/procinfo.py index 01974513..f0605386 100755 --- a/scripts/procinfo.py +++ b/scripts/procinfo.py @@ -8,7 +8,7 @@ Print detailed information about a process. Author: Giampaolo Rodola' <g.rodola@gmail.com> -$ python scripts/procinfo.py +$ python3 scripts/procinfo.py pid 4600 name chrome parent 4554 (bash) diff --git a/scripts/ps.py b/scripts/ps.py index 540c032a..a234209f 100755 --- a/scripts/ps.py +++ b/scripts/ps.py @@ -7,7 +7,7 @@ """ A clone of 'ps aux'. -$ python scripts/ps.py +$ python3 scripts/ps.py USER PID %MEM VSZ RSS NICE STATUS START TIME CMDLINE root 1 0.0 220.9M 6.5M sleep Mar27 09:10 /lib/systemd root 2 0.0 0.0B 0.0B sleep Mar27 00:00 kthreadd diff --git a/scripts/pstree.py b/scripts/pstree.py index 0005e4a1..dba9f1bd 100755 --- a/scripts/pstree.py +++ b/scripts/pstree.py @@ -8,7 +8,7 @@ Similar to 'ps aux --forest' on Linux, prints the process list as a tree structure. -$ python scripts/pstree.py +$ python3 scripts/pstree.py 0 ? |- 1 init | |- 289 cgmanager diff --git a/scripts/sensors.py b/scripts/sensors.py index e532beba..911d7c9b 100755 --- a/scripts/sensors.py +++ b/scripts/sensors.py @@ -9,7 +9,7 @@ A clone of 'sensors' utility on Linux printing hardware temperatures, fans speed and battery info. -$ python scripts/sensors.py +$ python3 scripts/sensors.py asus Temperatures: asus 57.0°C (high=None°C, critical=None°C) diff --git a/scripts/temperatures.py b/scripts/temperatures.py index e83df440..f2dd51a7 100755 --- a/scripts/temperatures.py +++ b/scripts/temperatures.py @@ -8,7 +8,7 @@ """ A clone of 'sensors' utility on Linux printing hardware temperatures. -$ python scripts/sensors.py +$ python3 scripts/sensors.py asus asus 47.0 °C (high = None °C, critical = None °C) diff --git a/scripts/top.py b/scripts/top.py index 0b17471d..3c297d9a 100755 --- a/scripts/top.py +++ b/scripts/top.py @@ -9,7 +9,7 @@ A clone of top / htop. Author: Giampaolo Rodola' <g.rodola@gmail.com> -$ python scripts/top.py +$ python3 scripts/top.py CPU0 [|||| ] 10.9% CPU1 [||||| ] 13.1% CPU2 [||||| ] 12.8% @@ -33,7 +33,6 @@ PID USER NI VIRT RES CPU% MEM% TIME+ NAME ... """ -import atexit import datetime import sys import time @@ -46,30 +45,28 @@ import psutil from psutil._common import bytes2human -# --- curses stuff - -def tear_down(): - win.keypad(0) - curses.nocbreak() - curses.echo() - curses.endwin() - - win = curses.initscr() -atexit.register(tear_down) -curses.endwin() lineno = 0 +colors_map = dict( + green=3, + red=10, + yellow=4, +) -def print_line(line, highlight=False): +def printl(line, color=None, bold=False, highlight=False): """A thin wrapper around curses's addstr().""" global lineno try: + flags = 0 + if color: + flags |= curses.color_pair(colors_map[color]) + if bold: + flags |= curses.A_BOLD if highlight: line += " " * (win.getmaxyx()[1] - len(line)) - win.addstr(lineno, 0, line, curses.A_REVERSE) - else: - win.addstr(lineno, 0, line, 0) + flags |= curses.A_STANDOUT + win.addstr(lineno, 0, line, flags) except curses.error: lineno = 0 win.refresh() @@ -105,6 +102,15 @@ def poll(interval): return (processes, procs_status) +def get_color(perc): + if perc <= 30: + return "green" + elif perc <= 80: + return "yellow" + else: + return "red" + + def print_header(procs_status, num_procs): """Print system-related info, above the process list.""" @@ -117,8 +123,8 @@ def print_header(procs_status, num_procs): percs = psutil.cpu_percent(interval=0, percpu=True) for cpu_num, perc in enumerate(percs): dashes, empty_dashes = get_dashes(perc) - print_line(" CPU%-2s [%s%s] %5s%%" % (cpu_num, dashes, empty_dashes, - perc)) + line = " CPU%-2s [%s%s] %5s%%" % (cpu_num, dashes, empty_dashes, perc) + printl(line, color=get_color(perc)) mem = psutil.virtual_memory() dashes, empty_dashes = get_dashes(mem.percent) line = " Mem [%s%s] %5s%% %6s / %s" % ( @@ -127,7 +133,7 @@ def print_header(procs_status, num_procs): str(int(mem.used / 1024 / 1024)) + "M", str(int(mem.total / 1024 / 1024)) + "M" ) - print_line(line) + printl(line, color=get_color(mem.percent)) # swap usage swap = psutil.swap_memory() @@ -138,7 +144,7 @@ def print_header(procs_status, num_procs): str(int(swap.used / 1024 / 1024)) + "M", str(int(swap.total / 1024 / 1024)) + "M" ) - print_line(line) + printl(line, color=get_color(swap.percent)) # processes number and status st = [] @@ -146,14 +152,14 @@ def print_header(procs_status, num_procs): if y: st.append("%s=%s" % (x, y)) st.sort(key=lambda x: x[:3] in ('run', 'sle'), reverse=1) - print_line(" Processes: %s (%s)" % (num_procs, ', '.join(st))) + printl(" Processes: %s (%s)" % (num_procs, ', '.join(st))) # load average, uptime uptime = datetime.datetime.now() - \ datetime.datetime.fromtimestamp(psutil.boot_time()) av1, av2, av3 = psutil.getloadavg() line = " Load average: %.2f %.2f %.2f Uptime: %s" \ % (av1, av2, av3, str(uptime).split('.')[0]) - print_line(line) + printl(line) def refresh_window(procs, procs_status): @@ -164,8 +170,8 @@ def refresh_window(procs, procs_status): header = templ % ("PID", "USER", "NI", "VIRT", "RES", "CPU%", "MEM%", "TIME+", "NAME") print_header(procs_status, len(procs)) - print_line("") - print_line(header, highlight=True) + printl("") + printl(header, bold=True, highlight=True) for p in procs: # TIME+ column shows process CPU cumulative time and it # is expressed as: "mm:ss.ms" @@ -197,21 +203,42 @@ def refresh_window(procs, procs_status): p.dict['name'] or '', ) try: - print_line(line) + printl(line) except curses.error: break win.refresh() +def setup(): + curses.start_color() + curses.use_default_colors() + for i in range(0, curses.COLORS): + curses.init_pair(i + 1, i, -1) + curses.endwin() + win.nodelay(1) + + +def tear_down(): + win.keypad(0) + curses.nocbreak() + curses.echo() + curses.endwin() + + def main(): + setup() try: interval = 0 while True: + if win.getch() == ord('q'): + break args = poll(interval) refresh_window(*args) interval = 1 except (KeyboardInterrupt, SystemExit): pass + finally: + tear_down() if __name__ == '__main__': diff --git a/scripts/who.py b/scripts/who.py index c2299eb0..c1e40729 100755 --- a/scripts/who.py +++ b/scripts/who.py @@ -8,7 +8,7 @@ A clone of 'who' command; print information about users who are currently logged in. -$ python scripts/who.py +$ python3 scripts/who.py giampaolo console 2017-03-25 22:24 loginwindow giampaolo ttys000 2017-03-25 23:28 (10.0.2.2) sshd """ diff --git a/scripts/winservices.py b/scripts/winservices.py index 8792f752..5c710159 100755 --- a/scripts/winservices.py +++ b/scripts/winservices.py @@ -7,7 +7,7 @@ r""" List all Windows services installed. -$ python scripts/winservices.py +$ python3 scripts/winservices.py AeLookupSvc (Application Experience) status: stopped, start: manual, username: localSystem, pid: None binpath: C:\Windows\system32\svchost.exe -k netsvcs |