"""Exercise the real Linux reader through the real loopback HTTP adapter.""" import http.client import json import tempfile import threading import unittest from pathlib import Path from types import SimpleNamespace from openpilot.starpilot.galaxy.access import GalaxyAccessOwner from openpilot.starpilot.galaxy.server import make_server from openpilot.starpilot.galaxy.system_monitor import SystemMonitor class SystemMonitorRuntimeTest(unittest.TestCase): def setUp(self): self.directory = tempfile.TemporaryDirectory() self.addCleanup(self.directory.cleanup) self.root = Path(self.directory.name) self.time = 10.0 self.monitor = SystemMonitor(self.root, clock=lambda: self.time, wall_clock=lambda: 1_000_000 + self.time, disk_usage=lambda _: SimpleNamespace(used=2 ** 30, total=4 * 2 ** 30), page_size=4096) self.write_snapshot(100, 100, 10) def write_snapshot(self, user, idle, ticks, *, start=1000): (self.root / 'stat').write_text(f'cpu {user} 0 0 {idle} 0 0 0 0\ncpu0 {user} 0 0 {idle} 0 0 0 0\n') (self.root / 'meminfo').write_text('MemTotal: 4096000 kB\nMemAvailable: 1024000 kB\n') (self.root / 'uptime').write_text('300 0\n') process = self.root / '42' process.mkdir(exist_ok=True) fields = ['0'] * 22 fields[0], fields[11], fields[19], fields[21] = 'S', str(ticks), str(start), '256' (process / 'stat').write_text(f'42 (worker (test)) {" ".join(fields)}') (process / 'cmdline').write_bytes(b'python\0-m\0openpilot.selfdrive.controls.controlsd\0--private-argument\0') def test_cpu_capacity_pid_reuse_and_monotonic_reset(self): first = self.monitor.sample() self.assertIsNone(first['cpuPercent']) self.assertIsNone(first['processes'][0]['cpu']) self.assertEqual(first['processes'][0]['name'], 'openpilot.selfdrive.controls.controlsd') self.assertNotIn('private-argument', json.dumps(first)) self.time += 2 self.write_snapshot(160, 140, 30) second = self.monitor.sample() self.assertEqual(second['cpuPercent'], 60) self.assertEqual(second['processes'][0]['cpu'], 20) self.assertEqual(second['processes'][0]['memoryMiB'], 1) self.time += 2 self.write_snapshot(220, 180, 40, start=2000) self.assertIsNone(self.monitor.sample()['processes'][0]['cpu']) self.time = 1 self.write_snapshot(280, 220, 60, start=2000) self.assertIsNone(self.monitor.sample()['cpuPercent']) def test_unavailable_fields_and_failed_interval_are_not_zero_or_stale(self): self.monitor.sample() (self.root / 'meminfo').write_text('MemTotal: 4096000 kB\nMemFree: 1 kB\n') (self.root / '53').mkdir() # Process disappears during enumeration. self.time += 2 sample = self.monitor.sample() self.assertIsNone(sample['memory']['usedMiB']) self.assertIsNone(sample['memory']['percent']) self.assertEqual(sample['processCount'], 1) self.assertNotIn('vitals', sample) (self.root / 'stat').unlink() with self.assertRaises(OSError): self.monitor.sample() self.time += 2 self.write_snapshot(300, 200, 50) self.assertIsNone(self.monitor.sample()['cpuPercent']) def test_real_http_source_origin_static_paths_and_read_only_boundary(self): owner = GalaxyAccessOwner(self.root / 'access') self.assertTrue(owner.configure('test-password', lambda: True)) server = make_server(port=0, monitor=self.monitor, owner=owner) worker = threading.Thread(target=server.serve_forever, kwargs={'poll_interval': 0.01}, daemon=True) worker.start() try: def request(path, *, method='GET', headers=None, body=None): connection = http.client.HTTPConnection('127.0.0.1', server.server_port, timeout=2) try: connection.request(method, path, body=body, headers=headers or {}) response = connection.getresponse() return response.status, response.read(), dict(response.getheaders()) finally: connection.close() self.assertEqual(request('/api/system/monitor')[0], 401) origin = f'http://127.0.0.1:{server.server_port}' status, body, login_headers = request('/api/auth/login', method='POST', headers={ 'Content-Type': 'application/json', 'Origin': origin}, body='{"password":"test-password"}') self.assertEqual(status, 200) cookie = login_headers['Set-Cookie'].split(';', 1)[0] auth = {'Cookie': cookie} status, body, headers = request('/api/system/monitor', headers=auth) self.assertEqual(status, 200) self.assertEqual(json.loads(body)['mode'], 'local-runtime') self.assertEqual(headers['Cache-Control'], 'no-store') self.assertNotIn('Access-Control-Allow-Origin', headers) self.assertEqual(json.loads(request('/data/runtime.json')[1])['monitor'], 'local') self.assertIn(b'galaxy-app', request('/')[1]) for path in ('/../server.py', '/%2e%2e/server.py'): self.assertEqual(request(path)[0], 404) for path in ('/api/params', '/api/vehicle'): self.assertEqual(request(path, headers=auth)[0], 404) self.assertEqual(request('/api/system/monitor', headers={'Host': 'other.example'})[0], 403) self.assertEqual(request('/api/system/monitor', headers={'Origin': 'https://other.example'})[0], 403) self.assertEqual(request('/api/system/monitor', method='POST', headers={'Origin': origin})[0], 405) (self.root / 'stat').unlink() status, body, _ = request('/api/system/monitor', headers=auth) self.assertEqual(status, 503) self.assertNotIn('sampledAt', json.loads(body)) finally: server.shutdown() worker.join(timeout=2) server.server_close()