Files
DroidDeck/tools/tests/test_proot_profile.py

92 lines
4.2 KiB
Python

import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
TOOLS = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(TOOLS))
from proot_profile import report
PROGRAM = r'''
#define _POSIX_C_SOURCE 200809L
#define PR_NB_SYSNUM 2
#define PR_void 0
static const char *stringify_sysnum(unsigned nr) { return nr ? "openat" : "void"; }
#include "profile.h"
int main(void) {
errno = EBUSY;
if (!dd_profile_enabled()) return errno != EBUSY;
if (errno != EBUSY) return 1;
for (int i = 0; i < 40; i++) {
uint64_t wall = dd_profile_clock(CLOCK_MONOTONIC);
uint64_t cpu = dd_profile_clock(CLOCK_THREAD_CPUTIME_ID);
struct timespec wait = {0, 2000000};
nanosleep(&wait, 0);
errno = ENOENT;
dd_profile_record(1, 1, 123, wall, cpu);
if (errno != ENOENT) return 2;
if (i == 9) dd_profile_flush();
}
dd_profile_next = 0;
uint64_t wall = dd_profile_clock(CLOCK_MONOTONIC);
uint64_t cpu = dd_profile_clock(CLOCK_THREAD_CPUTIME_ID);
struct timespec wait = {0, 2000000};
nanosleep(&wait, 0);
dd_profile_record(1, 0, 123, wall, cpu);
return 0;
}
'''
class ProotProfileTest(unittest.TestCase):
def test_snapshots_replace_instead_of_adding_and_tracers_remain_separate(self):
with tempfile.TemporaryDirectory() as work:
path = Path(work) / 'session.log'
records = [dict(type='total', tracer=1, id=2, syscall='statx', stage='enter',
calls=n, wall_ns=n * 100, cpu_ns=n * 10, max_ns=100) for n in (3, 8)]
records.append(dict(records[-1], tracer=2, calls=2))
records += [dict(type='total', tracer=1, id=3, syscall='openat', stage='enter',
calls=1, wall_ns=1000, cpu_ns=1000, max_ns=1000)]
path.write_text('unrelated log\n' + ''.join('prefix proot-profile ' + json.dumps(r) + '\n' for r in records)
+ 'proot-profile {truncated\n' + 'proot-profile []\n')
result = report(path)
self.assertEqual(11, result['handlerCalls'])
self.assertEqual(3, result['tracers'])
self.assertEqual(2, result['malformedRecords'])
self.assertEqual('statx', result['totals'][0]['syscall'])
self.assertEqual('openat', report(path, 'cpu')['totals'][0]['syscall'])
def test_empty_logs_do_not_claim_profiling_ran(self):
with tempfile.TemporaryDirectory() as work:
path = Path(work) / 'session.log'
path.write_text('ordinary log\n')
self.assertFalse(report(path)['hasCounters'])
@unittest.skipUnless(shutil.which('cc'), 'C compiler required')
def test_real_clocks_bounded_samples_errno_and_periodic_counters(self):
with tempfile.TemporaryDirectory() as work:
source = Path(work) / 'profile.c'
binary = Path(work) / 'profile'
source.write_text(PROGRAM)
subprocess.run(['cc', '-Wall', '-Wextra', '-Werror', '-std=c99', '-I', str(TOOLS / 'proot'),
str(source), '-o', str(binary)], check=True)
for setting in ('0', ''):
result = subprocess.run([binary], env=dict(os.environ, PROOT_PROFILE=setting), capture_output=True, check=True)
self.assertEqual(b'', result.stderr)
result = subprocess.run([binary], env=dict(os.environ, PROOT_PROFILE='1', PROOT_PROFILE_SLOW_US='1'),
capture_output=True, check=True)
rows = [json.loads(line.split(b'proot-profile ', 1)[1]) for line in result.stderr.splitlines()]
self.assertEqual(33, sum(row['type'] == 'slow' for row in rows))
self.assertEqual(32, sum(row['type'] == 'slow' and row['stage'] == 'enter' for row in rows))
self.assertGreater(sum(row['type'] == 'total' for row in rows), 2)
log = Path(work) / 'session.log'
log.write_bytes(result.stderr)
counters = report(log)
self.assertEqual(41, counters['handlerCalls'])
self.assertGreater(counters['handlerWallNs'], counters['handlerCpuNs'])
self.assertEqual(40, counters['totals'][0]['calls'])