comparison racc.py @ 3:3d7fc94a404a

a few more inputs; daemonize
author drewp@bigasterisk.com
date Wed, 08 Mar 2023 10:57:11 -0800
parents 0ecb388a0b90
children
comparison
equal deleted inserted replaced
2:0ecb388a0b90 3:3d7fc94a404a
1 from starlette.applications import Starlette
2 from starlette.exceptions import HTTPException
3 from starlette.requests import Request
4 from starlette.responses import JSONResponse, HTMLResponse
5 from starlette.staticfiles import StaticFiles
6 from starlette.routing import Route, Mount
7 from starlette.templating import Jinja2Templates
8 import uvicorn
9 import psutil
10 from starlette_exporter import PrometheusMiddleware, handle_metrics
11 from prometheus_client import Summary, Gauge
12 import background_loop
13 from typing import List, Optional
14 import logging 1 import logging
2 import os
15 import socket 3 import socket
16 import sys 4 import sys
5
6 import background_loop
7 import daemonocle
8 import psutil
9 import uvicorn
10 from prometheus_client import Gauge
11 from starlette.applications import Starlette
12 from starlette.requests import Request
13 from starlette.responses import HTMLResponse
14 from starlette.routing import Route
15 from starlette.staticfiles import StaticFiles
16 from starlette_exporter import PrometheusMiddleware, handle_metrics
17 ''
18 import progs_all as progs
19
17 if psutil.OSX: 20 if psutil.OSX:
21 import current_window_title_osx as current_window_title
18 import idle_osx as idle 22 import idle_osx as idle
23 import power_osx as power
19 import volume_osx as volume 24 import volume_osx as volume
20 import power_osx as power
21 elif psutil.LINUX: 25 elif psutil.LINUX:
26 import current_window_title_linux as current_window_title
22 import idle_linux as idle 27 import idle_linux as idle
28 import power_linux as power
23 import volume_linux as volume 29 import volume_linux as volume
24 import power_linux as power
25 else: 30 else:
26 raise NotImplementedError(repr(sys.implementation)) 31 raise NotImplementedError(repr(sys.implementation))
27 32
28 hostname = socket.gethostname().split('.')[0] 33 DEBUG = os.environ.get('RACC_DEBUG', False)
29 logging.basicConfig(level=logging.INFO) 34 logging.basicConfig(level=logging.INFO)
30 log = logging.getLogger() 35 log = logging.getLogger()
36 hostname = socket.gethostname().split('.')[0]
37 RACC_RUNNING = Gauge("racc_running", "program is running", ['host', 'prog'])
38 RACC_IDLE = Gauge("racc_idle", "desktop mouse/kb idle seconds", ['host'])
39 RACC_SCREEN = Gauge("racc_screen_on", "screen in unlocked/on mode", ['host'])
40 RACC_CURRENT_WINDOW = Gauge("racc_current_window",
41 "label carries title; site is last part",
42 ['host', 'title', 'site'])
31 43
32 44
33 def progname(cmdline: List[str]) -> Optional[str]: 45 def update(first_run):
34 if len(cmdline) < 1: 46 try:
35 return None 47 for p, val in progs.get_running_progs().items():
36 if cmdline[-1].endswith('/steam'): 48 RACC_RUNNING.labels(host=hostname, prog=p).set(val)
37 return 'steam' 49 except Exception:
38 if cmdline[0].endswith('/minecraft-launcher'): 50 if DEBUG: raise
39 return 'minecraft-launcher'
40 if cmdline[0].endswith('/java') and '--versionType' in cmdline:
41 return 'minecraft'
42 51
52 try:
53 RACC_IDLE.labels(host=hostname).set(idle.get_idle_seconds())
54 except Exception:
55 if DEBUG: raise
43 56
44 RACC_RUNNING = Gauge("racc_running", "program is running", ['host', 'prog']) 57 try:
45 RACC_IDLE = Gauge("racc_idle", "desktop mouse/kb idle seconds", ['host']) 58 RACC_SCREEN.labels(host=hostname).set(power.is_screen_on())
46 RACC_SCREEN = Gauge("racc_screen", "screen in unlocked/on mode", ['host']) 59 except Exception:
60 if DEBUG: raise
47 61
62 try:
63 title = current_window_title.get_current_window_title()
64 # chrome, at least on osx, adds icon to window title when it's playing audio
65 title = title.rstrip('🔊').strip()
66 # some websites choose to title like '<something> - <site name>'
67 last_section = title.split(' - ')[-1].strip()
68 if last_section in {'YouTube', 'Google Search', 'Google Chrome', 'Visual Studio Code', 'Roblox'}:
69 site = last_section.lower().replace(' ', '_')
70 else:
71 site = ''
72 RACC_CURRENT_WINDOW.clear()
73 RACC_CURRENT_WINDOW.labels(host=hostname, title=title,
74 site=site).set(1)
75 except Exception:
76 if DEBUG: raise
48 77
49 def update_progs(first_run):
50 out = []
51 progs = set()
52 for proc in psutil.process_iter(['pid', 'name']):
53 try:
54 prog = progname(proc.cmdline())
55 if prog:
56 progs.add(prog)
57 except (psutil.AccessDenied, psutil.NoSuchProcess):
58 pass
59
60 for p in [
61 'minecraft',
62 'minecraft-launcher',
63 'steam',
64 ]:
65 RACC_RUNNING.labels(host=hostname, prog=p).set(p in progs)
66 RACC_IDLE.labels(host=hostname).set(idle.get_idle_seconds())
67 RACC_SCREEN.labels(host=hostname).set(power.is_screen_on())
68 78
69 async def root(req: Request) -> HTMLResponse: 79 async def root(req: Request) -> HTMLResponse:
70 vol = await volume.get_volume() 80 vol = await volume.get_volume()
71 return HTMLResponse(f'''controls for {hostname} whose volume is {vol}''') 81 return HTMLResponse(f'''controls for {hostname} whose volume is {vol}''')
72 82
75 app = Starlette(debug=True, 85 app = Starlette(debug=True,
76 routes=[ 86 routes=[
77 Route('/', root), 87 Route('/', root),
78 ], 88 ],
79 on_startup=[ 89 on_startup=[
80 lambda: background_loop.loop_forever(update_progs, 3), 90 lambda: background_loop.loop_forever(update, 5),
81 ]) 91 ])
82 92
83 app.add_middleware(PrometheusMiddleware, app_name='racc') 93 app.add_middleware(PrometheusMiddleware, app_name='racc')
84 app.add_route("/metrics", handle_metrics) 94 app.add_route("/metrics", handle_metrics)
85 return app 95 uvicorn.run(app, host='0.0.0.0', port=5150)
86 96
87 97
88 if __name__ == "__main__": 98 if __name__ == "__main__":
89 uvicorn.run(main(), host='0.0.0.0', port=5150) 99 d = daemonocle.Daemon(
100 worker=main,
101 work_dir='.',
102 pid_file='/tmp/racc.pid',
103 detach=True,
104 )
105 d.stop(timeout=1, force=True)
106 if DEBUG:
107 main()
108 else:
109 d.cli()