Repository navigation
Expand file tree
/
Copy pathprobe-bun-historical-linux.py
More file actions
141 lines (127 loc) · 6.55 KB
/
Copy pathprobe-bun-historical-linux.py
File metadata and controls
141 lines (127 loc) · 6.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env python3
"""Bounded diagnostics for historical Linux Bun crashes before lockfile edits.
Run after a failed binary matrix. This never changes its acceptance result or
downloads replacement tools. Each installed Bun gets a pristine package project
and a 30-second total budget, including optional strace and gdb reproductions.
"""
import argparse
import json
import os
from pathlib import Path
import platform
import shutil
import signal
import subprocess
import tempfile
import time
def read_context(path):
try:
return path.read_text().strip()
except OSError as error:
return {'error': str(error)}
def run(command, cwd, env, output, label, timeout):
started = time.monotonic()
row = {'command': [str(part) for part in command], 'timeoutSeconds': timeout}
try:
with (output / f'{label}.stdout').open('wb') as stdout, \
(output / f'{label}.stderr').open('wb') as stderr:
process = subprocess.Popen(command, cwd=cwd, env=env, stdin=subprocess.DEVNULL,
stdout=stdout, stderr=stderr, start_new_session=True)
try:
process.wait(timeout=timeout)
except subprocess.TimeoutExpired:
row['timedOut'] = True
os.killpg(process.pid, signal.SIGKILL)
process.wait(timeout=5)
row['returnCode'] = process.returncode
if process.returncode < 0:
row['signal'] = signal.Signals(-process.returncode).name
except (OSError, subprocess.SubprocessError) as error:
row['error'] = str(error)
row['elapsedSeconds'] = round(time.monotonic() - started, 3)
(output / f'{label}.json').write_text(json.dumps(row, indent=2) + '\n')
print(json.dumps({'probe': label, **row}), flush=True)
return row
def fixture(root, project_name='binary project café'):
# Match the acceptance test's Unicode/spaced path and isolated cache/temp.
project = root / project_name
project.mkdir(parents=True)
(project / 'package.json').write_text(json.dumps({
'name': 'native-binary-bun', 'version': '1.0.0', 'private': True,
'dependencies': {'minimist': '1.2.2', 'is-number': '7.0.0'},
}) + '\n')
(project / 'bunfig.toml').write_text('[install]\nsaveTextLockfile = false\n')
env = {key: value for key, value in os.environ.items()
if not key.startswith(('BUN_', 'SOCKET_', 'NPM_CONFIG_', 'npm_config_'))}
for key, name in {
'HOME': 'home', 'XDG_CONFIG_HOME': 'config', 'XDG_CACHE_HOME': 'cache',
'BUN_INSTALL': 'bun-home', 'BUN_INSTALL_CACHE_DIR': 'bun-cache',
'TMPDIR': 'temporary', 'TMP': 'temporary', 'TEMP': 'temporary',
'BUN_TMPDIR': 'temporary',
}.items():
path = root / name
path.mkdir(exist_ok=True)
env[key] = str(path)
return project, env
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--tools', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
output = args.output.resolve()
output.mkdir(parents=True, exist_ok=True)
if platform.system() != 'Linux':
print('Historical Linux diagnostics require Linux; no probes run.')
return
context = {'platform': platform.platform(), 'strace': shutil.which('strace'),
'gdb': shutil.which('gdb'),
'LD_LIBRARY_PATH': os.environ.get('LD_LIBRARY_PATH'),
'LD_PRELOAD': os.environ.get('LD_PRELOAD')}
for name in ['kernel/io_uring_disabled', 'kernel/io_uring_group', 'vm/mmap_rnd_bits']:
context[name] = read_context(Path('/proc/sys') / name)
for name in ['status', 'limits']:
value = read_context(Path('/proc/self') / name)
(output / f'process-{name}.txt').write_text(
value if isinstance(value, str) else json.dumps(value))
(output / 'context.json').write_text(json.dumps(context, indent=2) + '\n')
for executable, arguments in [('uname', ['-a']), ('lscpu', []), ('ldd', ['--version'])]:
if program := shutil.which(executable):
run([program, *arguments], output, os.environ.copy(), output, executable, 5)
summary = []
for version in ['0.5.9', '0.6.7', '0.6.8']:
matches = sorted((args.tools / version).glob('bun-linux-*/bun'))
if not matches:
summary.append({'version': version, 'error': 'downloaded Bun executable missing'})
continue
bun = matches[0].resolve()
deadline = time.monotonic() + 30
if ldd := shutil.which('ldd'):
run([ldd, bun], output, os.environ.copy(), output, f'{version}-libraries', 5)
with tempfile.TemporaryDirectory(prefix=f'bun-linux-probe-{version}-', dir=output) as temporary:
root = Path(temporary)
project, env = fixture(root / 'pristine')
plain = run([bun, 'install', '--ignore-scripts'], project, env, output,
f'{version}-pristine', min(10, deadline - time.monotonic()))
row = {'version': version, 'pristine': plain}
if plain.get('returnCode') != 0 and time.monotonic() < deadline:
project, env = fixture(root / 'ascii', 'binary-project')
row['ascii'] = run([bun, 'install', '--ignore-scripts'], project, env, output,
f'{version}-ascii', min(5, deadline - time.monotonic()))
if plain.get('returnCode') != 0 and context['strace'] and time.monotonic() < deadline:
project, env = fixture(root / 'traced')
row['strace'] = run([
context['strace'], '-f', '-tt', '-s', '160', '-o',
output / f'{version}.strace', bun, 'install', '--ignore-scripts',
], project, env, output, f'{version}-traced', max(1, deadline - time.monotonic()))
if plain.get('returnCode', 0) < 0 and context['gdb'] and time.monotonic() < deadline:
project, env = fixture(root / 'debugged')
row['gdb'] = run([
context['gdb'], '--batch', '-ex', 'set debuginfod enabled off',
'-ex', 'run', '-ex', 'thread apply all bt', '-ex', 'info registers',
'-ex', 'x/8i $pc', '-ex', 'info sharedlibrary',
'--args', bun, 'install', '--ignore-scripts',
], project, env, output, f'{version}-gdb', max(1, deadline - time.monotonic()))
summary.append(row)
(output / 'summary.json').write_text(json.dumps(summary, indent=2) + '\n')
if __name__ == '__main__':
main()