flameox 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flameox/__init__.py +10 -0
- flameox/__main__.py +4 -0
- flameox/adapters/__init__.py +55 -0
- flameox/adapters/builtins.py +312 -0
- flameox/adapters/client_setup.py +485 -0
- flameox/adapters/compatibility.py +49 -0
- flameox/adapters/coverage.py +188 -0
- flameox/adapters/memray.py +231 -0
- flameox/adapters/observations.py +121 -0
- flameox/adapters/perfetto.py +573 -0
- flameox/adapters/pyperf.py +193 -0
- flameox/adapters/registry.py +225 -0
- flameox/adapters/setup_runtime.py +201 -0
- flameox/analysis/__init__.py +43 -0
- flameox/analysis/comparison.py +143 -0
- flameox/analysis/recipes.py +1438 -0
- flameox/application/__init__.py +201 -0
- flameox/application/analysis_provenance.py +115 -0
- flameox/application/analysis_records.py +299 -0
- flameox/application/analysis_rows.py +35 -0
- flameox/application/artifacts.py +141 -0
- flameox/application/async_work.py +30 -0
- flameox/application/capabilities.py +338 -0
- flameox/application/capture.py +1185 -0
- flameox/application/compaction.py +77 -0
- flameox/application/comparisons.py +746 -0
- flameox/application/drilldown.py +365 -0
- flameox/application/environment.py +42 -0
- flameox/application/evidence_lookup.py +110 -0
- flameox/application/evidence_query.py +159 -0
- flameox/application/evidence_rows.py +51 -0
- flameox/application/execution_policy.py +17 -0
- flameox/application/experiments.py +713 -0
- flameox/application/gc.py +425 -0
- flameox/application/imports.py +141 -0
- flameox/application/integrity.py +156 -0
- flameox/application/quarantine.py +317 -0
- flameox/application/records.py +353 -0
- flameox/application/recoverable_move.py +45 -0
- flameox/application/recovery.py +125 -0
- flameox/application/repair.py +140 -0
- flameox/application/run_rows.py +24 -0
- flameox/application/setup.py +556 -0
- flameox/application/source.py +222 -0
- flameox/application/status.py +132 -0
- flameox/application/viewers.py +157 -0
- flameox/application/workloads.py +368 -0
- flameox/catalog.py +398 -0
- flameox/cli.py +1767 -0
- flameox/collectors/__init__.py +1 -0
- flameox/collectors/torch_launcher.py +46 -0
- flameox/config.py +66 -0
- flameox/domain/__init__.py +98 -0
- flameox/domain/cursors.py +78 -0
- flameox/domain/errors.py +57 -0
- flameox/domain/identity.py +36 -0
- flameox/domain/models.py +576 -0
- flameox/evidence/__init__.py +11 -0
- flameox/evidence/publisher.py +279 -0
- flameox/evidence/schemas.py +386 -0
- flameox/evidence_scope.py +76 -0
- flameox/execution.py +350 -0
- flameox/mcp/__init__.py +3 -0
- flameox/mcp/server.py +1318 -0
- flameox/models.py +19 -0
- flameox/observability.py +107 -0
- flameox/py.typed +1 -0
- flameox/sdk.py +79 -0
- flameox/setup_ui.py +126 -0
- flameox/storage/__init__.py +27 -0
- flameox/storage/artifacts.py +204 -0
- flameox/storage/atomic.py +59 -0
- flameox/storage/corpus.py +141 -0
- flameox/storage/quotas.py +84 -0
- flameox/storage/records.py +135 -0
- flameox/storage/runs.py +92 -0
- flameox/storage/workspace.py +297 -0
- flameox/workers/__init__.py +1 -0
- flameox/workers/perfetto.py +191 -0
- flameox-0.1.1.dist-info/METADATA +420 -0
- flameox-0.1.1.dist-info/RECORD +83 -0
- flameox-0.1.1.dist-info/WHEEL +4 -0
- flameox-0.1.1.dist-info/entry_points.txt +2 -0
flameox/__init__.py
ADDED
flameox/__main__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from flameox.adapters.client_setup import (
|
|
2
|
+
ALL_SETUP_CLIENTS,
|
|
3
|
+
ClientConfigEdit,
|
|
4
|
+
ClientConfigRegistry,
|
|
5
|
+
ClientPlanAction,
|
|
6
|
+
Launcher,
|
|
7
|
+
SetupClient,
|
|
8
|
+
)
|
|
9
|
+
from flameox.adapters.coverage import CoverageExtractionResult, CoverageExtractor
|
|
10
|
+
from flameox.adapters.memray import MemrayExtractionResult, MemrayExtractor
|
|
11
|
+
from flameox.adapters.observations import (
|
|
12
|
+
ObservationExtractionResult,
|
|
13
|
+
ObservationExtractor,
|
|
14
|
+
)
|
|
15
|
+
from flameox.adapters.perfetto import (
|
|
16
|
+
PerfettoExtractionResult,
|
|
17
|
+
PerfettoExtractor,
|
|
18
|
+
TraceEvent,
|
|
19
|
+
TraceWindowResult,
|
|
20
|
+
)
|
|
21
|
+
from flameox.adapters.pyperf import PyPerfExtractionResult, PyPerfExtractor
|
|
22
|
+
from flameox.adapters.registry import (
|
|
23
|
+
AdapterApproval,
|
|
24
|
+
AdapterDescriptor,
|
|
25
|
+
AdapterDiscoveryResult,
|
|
26
|
+
AdapterRegistry,
|
|
27
|
+
)
|
|
28
|
+
from flameox.adapters.setup_runtime import ManagedRuntime, RuntimeInstallation
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"ALL_SETUP_CLIENTS",
|
|
32
|
+
"AdapterApproval",
|
|
33
|
+
"AdapterDescriptor",
|
|
34
|
+
"AdapterDiscoveryResult",
|
|
35
|
+
"AdapterRegistry",
|
|
36
|
+
"ClientConfigEdit",
|
|
37
|
+
"ClientConfigRegistry",
|
|
38
|
+
"ClientPlanAction",
|
|
39
|
+
"CoverageExtractionResult",
|
|
40
|
+
"CoverageExtractor",
|
|
41
|
+
"Launcher",
|
|
42
|
+
"ManagedRuntime",
|
|
43
|
+
"MemrayExtractionResult",
|
|
44
|
+
"MemrayExtractor",
|
|
45
|
+
"ObservationExtractionResult",
|
|
46
|
+
"ObservationExtractor",
|
|
47
|
+
"PerfettoExtractionResult",
|
|
48
|
+
"PerfettoExtractor",
|
|
49
|
+
"PyPerfExtractionResult",
|
|
50
|
+
"PyPerfExtractor",
|
|
51
|
+
"RuntimeInstallation",
|
|
52
|
+
"SetupClient",
|
|
53
|
+
"TraceEvent",
|
|
54
|
+
"TraceWindowResult",
|
|
55
|
+
]
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from flameox.domain import ArtifactKind, DomainError, ErrorCode
|
|
8
|
+
|
|
9
|
+
DependencyKind = Literal["internal", "executable", "package"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class BuiltinAdapter:
|
|
14
|
+
name: str
|
|
15
|
+
dependency_kind: DependencyKind
|
|
16
|
+
dependency: str | None
|
|
17
|
+
supported_modes: tuple[str, ...]
|
|
18
|
+
supported_formats: tuple[str, ...]
|
|
19
|
+
features: tuple[str, ...] = ()
|
|
20
|
+
remediation: tuple[str, ...] = ()
|
|
21
|
+
version_args: tuple[str, ...] = ()
|
|
22
|
+
permissions: tuple[str, ...] = ()
|
|
23
|
+
restriction_path: Path | None = None
|
|
24
|
+
supported_platforms: tuple[str, ...] | None = None
|
|
25
|
+
output_filename: str | None = None
|
|
26
|
+
artifact_kinds: tuple[ArtifactKind, ...] = ()
|
|
27
|
+
expected_overhead: str | None = None
|
|
28
|
+
capture_limitations: tuple[str, ...] = ()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class CaptureInvocation:
|
|
33
|
+
argv: tuple[str, ...]
|
|
34
|
+
artifact_kinds: tuple[ArtifactKind, ...]
|
|
35
|
+
expected_overhead: str
|
|
36
|
+
limitations: tuple[str, ...]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
BUILTIN_ADAPTERS = {
|
|
40
|
+
adapter.name: adapter
|
|
41
|
+
for adapter in (
|
|
42
|
+
BuiltinAdapter(
|
|
43
|
+
name="command",
|
|
44
|
+
dependency_kind="internal",
|
|
45
|
+
dependency=None,
|
|
46
|
+
supported_modes=("named_workload",),
|
|
47
|
+
supported_formats=("process-output",),
|
|
48
|
+
output_filename="capture.bin",
|
|
49
|
+
artifact_kinds=(ArtifactKind.PROCESS_OUTPUT,),
|
|
50
|
+
expected_overhead="No profiler overhead; process output only.",
|
|
51
|
+
capture_limitations=("No sampled stack or operator evidence is collected.",),
|
|
52
|
+
),
|
|
53
|
+
BuiltinAdapter(
|
|
54
|
+
name="py-spy",
|
|
55
|
+
dependency_kind="executable",
|
|
56
|
+
dependency="py-spy",
|
|
57
|
+
supported_modes=("record", "attach", "chrome"),
|
|
58
|
+
supported_formats=("speedscope", "chrome-trace", "raw"),
|
|
59
|
+
features=("sampled_stacks", "python_symbols"),
|
|
60
|
+
remediation=("Install py-spy and grant ptrace access for attach mode.",),
|
|
61
|
+
version_args=("--version",),
|
|
62
|
+
permissions=("ptrace",),
|
|
63
|
+
restriction_path=Path("/proc/sys/kernel/yama/ptrace_scope"),
|
|
64
|
+
output_filename="profile.json",
|
|
65
|
+
artifact_kinds=(ArtifactKind.SAMPLE_PROFILE,),
|
|
66
|
+
expected_overhead=(
|
|
67
|
+
"Sampling overhead; exact rate and native-frame scope are collector defaults."
|
|
68
|
+
),
|
|
69
|
+
capture_limitations=(
|
|
70
|
+
"GIL, native-frame, idle-thread, and subprocess modes are disabled.",
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
BuiltinAdapter(
|
|
74
|
+
name="perf",
|
|
75
|
+
dependency_kind="executable",
|
|
76
|
+
dependency="perf",
|
|
77
|
+
supported_modes=("record", "stat", "sched"),
|
|
78
|
+
supported_formats=("perf.data",),
|
|
79
|
+
features=("sampled_stacks", "native_symbols"),
|
|
80
|
+
remediation=("Install Linux perf matching the running kernel.",),
|
|
81
|
+
version_args=("--version",),
|
|
82
|
+
restriction_path=Path("/proc/sys/kernel/perf_event_paranoid"),
|
|
83
|
+
supported_platforms=("linux",),
|
|
84
|
+
output_filename="perf.data",
|
|
85
|
+
artifact_kinds=(ArtifactKind.SAMPLE_PROFILE,),
|
|
86
|
+
expected_overhead=(
|
|
87
|
+
"Kernel sampling overhead; symbol coverage depends on build identities."
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
BuiltinAdapter(
|
|
91
|
+
name="perfetto",
|
|
92
|
+
dependency_kind="executable",
|
|
93
|
+
dependency="trace_processor_shell",
|
|
94
|
+
supported_modes=("import", "query"),
|
|
95
|
+
supported_formats=("perfetto", "chrome-trace", "pprof", "perf.data"),
|
|
96
|
+
features=("trace_sql", "temporal_slices"),
|
|
97
|
+
remediation=("Install Perfetto Trace Processor or configure its local path.",),
|
|
98
|
+
version_args=("--version",),
|
|
99
|
+
),
|
|
100
|
+
BuiltinAdapter(
|
|
101
|
+
name="pyperf",
|
|
102
|
+
dependency_kind="package",
|
|
103
|
+
dependency="pyperf",
|
|
104
|
+
supported_modes=("import", "benchmark"),
|
|
105
|
+
supported_formats=("pyperf-json",),
|
|
106
|
+
features=("worker_hierarchy", "warmups", "raw_samples"),
|
|
107
|
+
output_filename="benchmark.json",
|
|
108
|
+
artifact_kinds=(ArtifactKind.BENCHMARK_SAMPLES,),
|
|
109
|
+
expected_overhead=(
|
|
110
|
+
"Repeated calibrated process execution; three workers, three values, "
|
|
111
|
+
"and one warm-up per worker."
|
|
112
|
+
),
|
|
113
|
+
capture_limitations=(
|
|
114
|
+
"Experiment-level treatment randomization is separate from "
|
|
115
|
+
"pyperf's worker hierarchy.",
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
BuiltinAdapter(
|
|
119
|
+
name="torch.profiler",
|
|
120
|
+
dependency_kind="package",
|
|
121
|
+
dependency="torch",
|
|
122
|
+
supported_modes=("trace_import", "launcher", "sdk"),
|
|
123
|
+
supported_formats=("chrome-trace",),
|
|
124
|
+
features=("operators", "input_shapes", "allocations", "stacks"),
|
|
125
|
+
output_filename="torch-trace.json",
|
|
126
|
+
artifact_kinds=(ArtifactKind.EXECUTION_TRACE,),
|
|
127
|
+
expected_overhead=(
|
|
128
|
+
"Operator tracing with shapes, memory, and Python stacks has substantial overhead."
|
|
129
|
+
),
|
|
130
|
+
capture_limitations=(
|
|
131
|
+
"Whole-entrypoint mode cannot distinguish application-specific "
|
|
132
|
+
"warm-up and steady-state phases.",
|
|
133
|
+
"FLOP estimates and module hierarchy are not enabled.",
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
BuiltinAdapter(
|
|
137
|
+
name="memray",
|
|
138
|
+
dependency_kind="package",
|
|
139
|
+
dependency="memray",
|
|
140
|
+
supported_modes=("import", "run"),
|
|
141
|
+
supported_formats=("memray",),
|
|
142
|
+
features=("allocations", "retained_memory", "stacks"),
|
|
143
|
+
output_filename="memory.bin",
|
|
144
|
+
artifact_kinds=(ArtifactKind.MEMORY_PROFILE,),
|
|
145
|
+
expected_overhead=(
|
|
146
|
+
"Allocation tracing overhead; native traces are disabled by default."
|
|
147
|
+
),
|
|
148
|
+
capture_limitations=(
|
|
149
|
+
"The capture records the main process unless follow-fork is "
|
|
150
|
+
"explicitly added in a future plan mode.",
|
|
151
|
+
),
|
|
152
|
+
),
|
|
153
|
+
BuiltinAdapter(
|
|
154
|
+
name="coverage",
|
|
155
|
+
dependency_kind="package",
|
|
156
|
+
dependency="coverage",
|
|
157
|
+
supported_modes=("import", "run"),
|
|
158
|
+
supported_formats=("coverage-data",),
|
|
159
|
+
features=("lines", "branches"),
|
|
160
|
+
output_filename=".coverage",
|
|
161
|
+
artifact_kinds=(ArtifactKind.EXECUTION_COVERAGE,),
|
|
162
|
+
expected_overhead="Tracing overhead; branch collection is enabled.",
|
|
163
|
+
capture_limitations=("Coverage records execution, not values or control-flow causes.",),
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def builtin_adapter(name: str) -> BuiltinAdapter | None:
|
|
170
|
+
return BUILTIN_ADAPTERS.get(name)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def build_capture_invocation(
|
|
174
|
+
adapter_name: str,
|
|
175
|
+
workload_argv: tuple[str, ...],
|
|
176
|
+
output_root: Path,
|
|
177
|
+
*,
|
|
178
|
+
executable: str | None,
|
|
179
|
+
) -> CaptureInvocation:
|
|
180
|
+
adapter = BUILTIN_ADAPTERS.get(adapter_name)
|
|
181
|
+
if (
|
|
182
|
+
adapter is None
|
|
183
|
+
or not adapter.artifact_kinds
|
|
184
|
+
or adapter.output_filename is None
|
|
185
|
+
or adapter.expected_overhead is None
|
|
186
|
+
):
|
|
187
|
+
raise DomainError(
|
|
188
|
+
ErrorCode.CAPABILITY_UNAVAILABLE,
|
|
189
|
+
f"Adapter {adapter_name!r} does not support capture planning.",
|
|
190
|
+
)
|
|
191
|
+
output = str(output_root / adapter.output_filename)
|
|
192
|
+
if adapter_name == "command":
|
|
193
|
+
argv = workload_argv
|
|
194
|
+
elif adapter_name == "py-spy":
|
|
195
|
+
argv = (
|
|
196
|
+
_require_executable(adapter_name, executable),
|
|
197
|
+
"record",
|
|
198
|
+
"--format",
|
|
199
|
+
"chrometrace",
|
|
200
|
+
"--output",
|
|
201
|
+
output,
|
|
202
|
+
"--",
|
|
203
|
+
*workload_argv,
|
|
204
|
+
)
|
|
205
|
+
elif adapter_name == "perf":
|
|
206
|
+
argv = (
|
|
207
|
+
_require_executable(adapter_name, executable),
|
|
208
|
+
"record",
|
|
209
|
+
"-o",
|
|
210
|
+
output,
|
|
211
|
+
"--",
|
|
212
|
+
*workload_argv,
|
|
213
|
+
)
|
|
214
|
+
elif adapter_name in {"coverage", "memray", "torch.profiler"}:
|
|
215
|
+
python, target = _python_target(workload_argv)
|
|
216
|
+
if adapter_name == "coverage":
|
|
217
|
+
argv = (
|
|
218
|
+
python,
|
|
219
|
+
"-m",
|
|
220
|
+
"coverage",
|
|
221
|
+
"run",
|
|
222
|
+
"--branch",
|
|
223
|
+
f"--data-file={output}",
|
|
224
|
+
*target,
|
|
225
|
+
)
|
|
226
|
+
elif adapter_name == "memray":
|
|
227
|
+
argv = (
|
|
228
|
+
python,
|
|
229
|
+
"-m",
|
|
230
|
+
"memray",
|
|
231
|
+
"run",
|
|
232
|
+
"--output",
|
|
233
|
+
output,
|
|
234
|
+
*target,
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
if target[0] == "-m":
|
|
238
|
+
if len(target) < 2:
|
|
239
|
+
raise DomainError(
|
|
240
|
+
ErrorCode.INVALID_CAPTURE_PLAN,
|
|
241
|
+
"The Python module name is missing.",
|
|
242
|
+
)
|
|
243
|
+
launcher_target = ("--module", target[1], *target[2:])
|
|
244
|
+
else:
|
|
245
|
+
launcher_target = ("--script", target[0], *target[1:])
|
|
246
|
+
argv = (
|
|
247
|
+
python,
|
|
248
|
+
"-m",
|
|
249
|
+
"flameox.collectors.torch_launcher",
|
|
250
|
+
"--output",
|
|
251
|
+
output,
|
|
252
|
+
*launcher_target,
|
|
253
|
+
)
|
|
254
|
+
elif adapter_name == "pyperf":
|
|
255
|
+
python, _ = _python_target(workload_argv)
|
|
256
|
+
argv = (
|
|
257
|
+
python,
|
|
258
|
+
"-m",
|
|
259
|
+
"pyperf",
|
|
260
|
+
"command",
|
|
261
|
+
"--output",
|
|
262
|
+
output,
|
|
263
|
+
"--processes",
|
|
264
|
+
"3",
|
|
265
|
+
"--values",
|
|
266
|
+
"3",
|
|
267
|
+
"--warmups",
|
|
268
|
+
"1",
|
|
269
|
+
"--name",
|
|
270
|
+
"workload",
|
|
271
|
+
"--",
|
|
272
|
+
*workload_argv,
|
|
273
|
+
)
|
|
274
|
+
else:
|
|
275
|
+
raise DomainError(
|
|
276
|
+
ErrorCode.CAPABILITY_UNAVAILABLE,
|
|
277
|
+
f"Adapter {adapter_name!r} does not support capture planning.",
|
|
278
|
+
)
|
|
279
|
+
return CaptureInvocation(
|
|
280
|
+
argv=argv,
|
|
281
|
+
artifact_kinds=adapter.artifact_kinds,
|
|
282
|
+
expected_overhead=adapter.expected_overhead,
|
|
283
|
+
limitations=adapter.capture_limitations,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _python_target(
|
|
288
|
+
workload_argv: tuple[str, ...],
|
|
289
|
+
) -> tuple[str, tuple[str, ...]]:
|
|
290
|
+
executable = Path(workload_argv[0]).name
|
|
291
|
+
if not executable.startswith("python"):
|
|
292
|
+
raise DomainError(
|
|
293
|
+
ErrorCode.INVALID_CAPTURE_PLAN,
|
|
294
|
+
"This adapter can launch only a declared Python script or module.",
|
|
295
|
+
)
|
|
296
|
+
arguments = workload_argv[1:]
|
|
297
|
+
if not arguments or arguments[0] == "-c":
|
|
298
|
+
raise DomainError(
|
|
299
|
+
ErrorCode.INVALID_CAPTURE_PLAN,
|
|
300
|
+
"Inline Python commands cannot be wrapped by this adapter.",
|
|
301
|
+
remediation=("Declare a script or `python -m module` workload.",),
|
|
302
|
+
)
|
|
303
|
+
return workload_argv[0], arguments
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _require_executable(adapter: str, executable: str | None) -> str:
|
|
307
|
+
if executable is None:
|
|
308
|
+
raise DomainError(
|
|
309
|
+
ErrorCode.CAPABILITY_UNAVAILABLE,
|
|
310
|
+
f"Adapter {adapter!r} has no resolved executable.",
|
|
311
|
+
)
|
|
312
|
+
return executable
|