Browse Source

[#85496] Use sockets for communication

main
Przemysław Barcicki 3 months ago
parent
commit
669c9bfebd
  1. 5
      README.md
  2. 6
      common.py
  3. 163
      sargraph.py
  4. 971
      watch.py

5
README.md

@ -1,6 +1,6 @@
# sargraph
Copyright (c) 2019-2023 [Antmicro](https://www.antmicro.com)
Copyright (c) 2019-2026 [Antmicro](https://www.antmicro.com)
This is a simple python tool that uses "sysstat" ("sar") to save information on CPU, RAM and disk usage.
The process runs in background and can be controlled with a set of sargraph sub-commands.
@ -10,7 +10,7 @@ Supported plot formats are PNG, SVG and ASCII, they are determined by filename e
# Install requirements
The sargraph requires `gnuplot`, `sysstat` (`sar`), `python3`, `coreutils` and `screen` to operate.
The sargraph requires `gnuplot`, `sysstat` (`sar`), `python3` and `coreutils` to operate.
In Debian you can install them with:
```
@ -22,7 +22,6 @@ apt-get install -qqy --no-install-recommends \
gnuplot-nox \
python3 \
python3-pip \
screen \
sysstat
# install Python dependencies

6
common.py

@ -39,7 +39,11 @@ def run_or_fail(*argv, **kwargs):
# Check if a process is running
def pid_running(pid):
return os.path.exists(f"/proc/{pid}")
return file_exists(f"/proc/{pid}")
# Check whether path exists
def file_exists(filename: str):
return os.path.exists(filename)
# Convert a string to float, also when the separator is a comma

163
sargraph.py

@ -11,12 +11,13 @@ import time
import graph
import watch
import warnings
from common import *
# Declare and parse command line flags
parser = argparse.ArgumentParser()
parser.add_argument('session', metavar='SESSION-NAME', type=str, nargs='?', default=None, help='sargraph session name')
parser.add_argument('session', metavar='SESSION-NAME', type=str, nargs='?', help='sargraph session name')
parser.add_argument('command', metavar='COMMAND', type=str, nargs='*', help='send command')
parser.add_argument('-f', metavar='DEVICE-NAME', type=str, nargs='?', default=None, dest='fsdev', help='observe a chosen filesystem')
parser.add_argument('-m', metavar='MOUNT-DIR', type=str, nargs='?', default=None, dest='fspath', help='observe a chosen filesystem')
@ -26,26 +27,19 @@ parser.add_argument('-t', metavar='TMPFS-COLOR', type=str, nargs='?', defa
parser.add_argument('-c', metavar='CACHE-COLOR', type=str, nargs='?', default='#ee7af0', dest='cache', help='set cache plot color' )
parser.add_argument('-u', metavar='UDP', type=str, nargs='?', default=None, dest='udp', help='set udp server address')
parser.add_argument('-C', metavar='UDP_COOKIE', type=str, nargs='?', default=None, dest='udp_cookie', help='set udp message cookie')
parser.add_argument('-p', action='store_true', dest='psutil', help='use psutil instead of sar')
parser.add_argument('-p', action='store_true', dest='psutil', help='use psutil instead of sar')
args = parser.parse_args()
def send(sid, msg):
p = subprocess.Popen(["screen", "-S", sid, "-X", "stuff", f"{msg}\n"])
while p.poll() is None:
time.sleep(0.1)
# Check if sar is available
if not is_darwin():
p = run_or_fail("sar", "-V", stdout=subprocess.PIPE)
def send(session: str, message: str):
sock, socket_path = watch.get_socket(session)
if not file_exists(socket_path):
fail(f"Session '{session}' does not exist")
# Check if screen is available
p = run_or_fail("screen", "-v", stdout=subprocess.PIPE)
version = scan("Screen version (\\d+)", int, p.stdout.readline().decode())
if version is None:
fail("'screen' tool returned unknown output")
sock.connect(socket_path)
sock.send(message.encode("utf-8"))
sock.close()
# If the script was run with no parameters, run in background and gather data
if args.session is None:
def create_session():
# Find requested disk device
if args.fspath:
args.fspath = os.path.realpath(args.fspath)
@ -53,69 +47,98 @@ if args.session is None:
while args.fsdev is None:
args.fsdev = scan(f"^(/dev/\\S+)\\s+{re.escape(args.fspath)}\\s+", str, f.readline())
if not args.fsdev:
fail(f"no device is mounted on {args.fspath}")
fail(f"No device is mounted on {args.fspath}")
watch.watch(args.name, args.fsdev, args.iface, args.tmpfs, args.cache, args.psutil, args.udp, args.udp_cookie)
params = (args.session, args.fsdev, args.iface, args.tmpfs, args.cache, args.udp, args.udp_cookie)
if is_darwin() or args.psutil:
watcher = watch.PsUtilWatcher(*params)
else:
watcher = watch.SarWatcher(*params)
watcher.start()
sys.exit(0)
# Now handle the commands
# Check if sar is available
if not is_darwin():
p = run_or_fail("sar", "-V", stdout=subprocess.PIPE)
# Check if a command was provided
if len(args.command) <= 0:
fail("command not provided")
if args.name != "data":
warnings.warn("'-o' is deprecated, session name is default output base name")
# Get session name and command name
sid = args.session
cmd = args.command
# Check if a command was provided, if that session exists, yell at user for lack of commands, else spawn
if len(args.command) == 0:
sock, socket_path = watch.get_socket(args.session)
if file_exists(socket_path):
fail("Command not provided")
else:
print(f"Starting sargraph session '{args.session}'")
create_session()
if args.command[0] == "start":
sock, socket_path = watch.get_socket(args.session)
if file_exists(socket_path):
fail("Session with this name already exists")
# Start watcher process
p = subprocess.Popen(
args=[sys.executable, os.path.realpath(__file__), args.session, *sys.argv[3:]],
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
start_new_session=True
)
# Spinloop to see whether the subprocess even starts
attempts = 5
while attempts:
time.sleep(0.1)
if file_exists(socket_path):
print(f"Session '{args.session}' started")
sys.exit(0)
attempts -= 1
fail("Session did not start")
elif args.command[0] == "stop":
_, socket_path = watch.get_socket(args.session)
print(f"Terminating sargraph session '{args.session}'")
if len(args.command) < 2:
send(args.session, "command:q:")
else:
send(args.session, f"command:q:{args.command[1]}")
if cmd[0] == "start":
print(f"Starting sargraph session '{sid}'")
# Spinloop to see whether the subprocess even dies
attempts = 5
while attempts:
time.sleep(0.5)
if not file_exists(socket_path):
print(f"Session '{args.session}' killed")
sys.exit(0)
attempts -= 1
fail("Session did not respond")
# Spawn watcher process, *sys.argv[3:] is all arguments after 'chart start' + '-o [log name]' if not given
if "-o" not in sys.argv:
sys.argv += ["-o", sid]
p = subprocess.Popen(["screen", "-Logfile", f"{sid}.log", "-dmSL", sid, os.path.realpath(__file__), *sys.argv[3:]])
while p.poll() is None:
time.sleep(0.1)
gpid = 0
j = 0
time.sleep(1)
print(f"Session '{sid}' started")
elif cmd[0] == "stop":
print(f"Terminating sargraph session '{sid}'")
try:
gpid = int(os.popen(f"screen -ls | grep '.{sid}' | tr -d ' \t' | cut -f 1 -d '.'").read())
except:
print("Warning: cannot find pid.")
gpid = -1
if len(cmd) < 2:
send(sid, "command:q:")
else:
send(sid, f"command:q:{cmd[1]}")
if gpid == -1:
print("Waiting 3 seconds.")
time.sleep(3)
else:
while pid_running(gpid):
time.sleep(0.25)
elif cmd[0] == "label":
elif args.command[0] == "label":
# Check if the label name was provided
if len(cmd) < 2:
if len(args.command) < 2:
fail("label command requires an additional parameter")
print(f"Adding label '{cmd[1]}' to sargraph session '{sid}'.")
send(sid, f"label:{cmd[1]}")
elif cmd[0] == 'save':
print(f"Saving graph from session '{sid}'.")
if len(cmd) < 2:
send(sid, "command:s:")
print(f"Adding label '{args.command[1]}' to sargraph session '{args.session}'.")
send(args.session, f"label:{args.command[1]}")
elif args.command[0] == 'save':
print(f"Saving graph from session '{args.session}'.")
if len(args.command) < 2:
send(args.session, "command:s:")
else:
send(sid, f"command:s:{cmd[1]}")
elif cmd[0] == 'plot':
if len(cmd) < 2:
graph.graph(sid, args.tmpfs, args.cache)
send(args.session, f"command:s:{args.command[1]}")
elif args.command[0] == 'plot':
if len(args.command) < 2:
graph.graph(args.session, args.tmpfs, args.cache)
else:
graph.graph(sid, args.tmpfs, args.cache, cmd[1])
graph.graph(args.session, args.tmpfs, args.cache, args.command[1])
else:
fail(f"unknown command '{cmd[0]}'")
fail(f"unknown command '{args.command[0]}'")

971
watch.py

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save