bobframes 0.1.0__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.
- bobframes/__init__.py +3 -0
- bobframes/_version.py +1 -0
- bobframes/catalog.py +154 -0
- bobframes/cli.py +266 -0
- bobframes/derive_post_merge.py +365 -0
- bobframes/derives/__init__.py +0 -0
- bobframes/derives/pass_class_breakdown.py +102 -0
- bobframes/derives/texture_usage.py +121 -0
- bobframes/discovery.py +132 -0
- bobframes/global_entities.py +99 -0
- bobframes/html/__init__.py +0 -0
- bobframes/html/template.py +1056 -0
- bobframes/lint.py +114 -0
- bobframes/manifest.py +127 -0
- bobframes/parquetize.py +282 -0
- bobframes/parsers/__init__.py +0 -0
- bobframes/parsers/derive_program_transitions.py +73 -0
- bobframes/parsers/parse_init_state.py +675 -0
- bobframes/paths.py +111 -0
- bobframes/probes/__init__.py +0 -0
- bobframes/probes/whatif.py +165 -0
- bobframes/qrd_harness.py +119 -0
- bobframes/query_examples.py +222 -0
- bobframes/rdcmd.py +72 -0
- bobframes/replay/__init__.py +26 -0
- bobframes/replay/replay_main.py +2305 -0
- bobframes/reports/__init__.py +0 -0
- bobframes/reports/_dashboard.py +425 -0
- bobframes/reports/ab.py +88 -0
- bobframes/reports/base.py +114 -0
- bobframes/reports/cache.py +147 -0
- bobframes/reports/chrome.py +1306 -0
- bobframes/reports/cli.py +99 -0
- bobframes/reports/delta.py +167 -0
- bobframes/reports/discovery.py +118 -0
- bobframes/reports/draws_by_class.py +165 -0
- bobframes/reports/formatters.py +122 -0
- bobframes/reports/instancing_opportunities.py +276 -0
- bobframes/reports/orchestrator.py +59 -0
- bobframes/reports/overdraw.py +293 -0
- bobframes/reports/pass_gpu.py +190 -0
- bobframes/reports/shader_hotlist.py +240 -0
- bobframes/reports/trend_table.py +444 -0
- bobframes/resource_labels.py +162 -0
- bobframes/run.py +480 -0
- bobframes/schemas.py +426 -0
- bobframes/stable_keys.py +83 -0
- bobframes/tests/__init__.py +0 -0
- bobframes/tests/_render_util.py +84 -0
- bobframes/tests/data/golden/_reports/draws_by_class.html +323 -0
- bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/index.html +1560 -0
- bobframes/tests/data/golden/_reports/index.html +264 -0
- bobframes/tests/data/golden/_reports/instancing_opportunities.html +266 -0
- bobframes/tests/data/golden/_reports/overdraw.html +275 -0
- bobframes/tests/data/golden/_reports/pass_gpu.html +277 -0
- bobframes/tests/data/golden/_reports/shader_hotlist.html +265 -0
- bobframes/tests/data/golden/_reports/trend_table.html +390 -0
- bobframes/tests/data/golden/index.html +1175 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/_manifest.json +51 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/buffers.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/clears.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/counters_per_event.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/descriptor_access.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/dispatches.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/draw_bindings.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/draws.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/events.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/fbos.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/frame_totals.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/ibo_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/indirect_args.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/passes.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/pixel_history.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/post_vs_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/program_transitions.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/programs.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/render_targets.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/resource_creation.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/rt_event_timeline.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/samplers.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/shaders.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/state_change_events.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/texture_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/textures.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/vbo_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/vertex_inputs.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/_manifest.json +51 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/buffers.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/clears.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/counters_per_event.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/descriptor_access.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/dispatches.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/draw_bindings.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/draws.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/events.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/fbos.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/frame_totals.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/ibo_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/indirect_args.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/passes.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/pixel_history.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/post_vs_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/program_transitions.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/programs.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/render_targets.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/resource_creation.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/rt_event_timeline.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/samplers.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/shaders.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/state_change_events.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/texture_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/textures.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/vbo_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/vertex_inputs.parquet +0 -0
- bobframes/tests/make_synthetic.py +171 -0
- bobframes/tests/smoke.py +199 -0
- bobframes/tests/test_determinism.py +19 -0
- bobframes/tests/test_discovery.py +97 -0
- bobframes/tests/test_hardening.py +142 -0
- bobframes/tests/test_parity.py +22 -0
- bobframes/tests/test_perf.py +18 -0
- bobframes/tests/test_replay_drift.py +115 -0
- bobframes/tests/test_schemas.py +26 -0
- bobframes/tests/test_schemas_unit.py +55 -0
- bobframes/tests/test_stable_keys.py +61 -0
- bobframes-0.1.0.dist-info/METADATA +144 -0
- bobframes-0.1.0.dist-info/RECORD +130 -0
- bobframes-0.1.0.dist-info/WHEEL +4 -0
- bobframes-0.1.0.dist-info/entry_points.txt +2 -0
- bobframes-0.1.0.dist-info/licenses/LICENSE +21 -0
bobframes/discovery.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Area + dated-drop walking.
|
|
2
|
+
|
|
3
|
+
Layout:
|
|
4
|
+
<root>/
|
|
5
|
+
<Area>/
|
|
6
|
+
<YYYY-MM-DD>[_<label>]/
|
|
7
|
+
<capture>.rdc, .xml, .zip.xml, .zip
|
|
8
|
+
|
|
9
|
+
Finds the newest dated drop per area; filters by --area / --label / --capture.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
|
|
18
|
+
DATED_RE = re.compile(r'^(\d{4}-\d{2}-\d{2})(?:_(.*))?$')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class Drop:
|
|
23
|
+
area: str
|
|
24
|
+
drop_date: str # 'YYYY-MM-DD'
|
|
25
|
+
drop_label: str # text after the date, or '' if absent
|
|
26
|
+
drop_dir: str # absolute path to the dated folder
|
|
27
|
+
captures: tuple[str, ...] # rdc filenames without .rdc extension, sorted
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _list_areas(root: str) -> list[tuple[str, str]]:
|
|
31
|
+
out = []
|
|
32
|
+
for entry in sorted(os.listdir(root)):
|
|
33
|
+
if entry.startswith('_') or entry.startswith('.'):
|
|
34
|
+
continue
|
|
35
|
+
full = os.path.join(root, entry)
|
|
36
|
+
if os.path.isdir(full):
|
|
37
|
+
out.append((entry, full))
|
|
38
|
+
return out
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _latest_drop_dir(area_dir: str) -> tuple[str, str, str] | None:
|
|
42
|
+
"""Return (drop_date, drop_label, drop_dir) for newest dated sub-dir, else None."""
|
|
43
|
+
dated = []
|
|
44
|
+
for entry in os.listdir(area_dir):
|
|
45
|
+
m = DATED_RE.match(entry)
|
|
46
|
+
if not m:
|
|
47
|
+
continue
|
|
48
|
+
full = os.path.join(area_dir, entry)
|
|
49
|
+
if not os.path.isdir(full):
|
|
50
|
+
continue
|
|
51
|
+
dated.append((m.group(1), m.group(2) or '', full))
|
|
52
|
+
if not dated:
|
|
53
|
+
return None
|
|
54
|
+
dated.sort(reverse=True)
|
|
55
|
+
return dated[0]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _captures(drop_dir: str) -> tuple[str, ...]:
|
|
59
|
+
names = []
|
|
60
|
+
for entry in sorted(os.listdir(drop_dir)):
|
|
61
|
+
if entry.endswith('.rdc') and os.path.isfile(os.path.join(drop_dir, entry)):
|
|
62
|
+
names.append(entry[:-4])
|
|
63
|
+
names.sort(key=lambda s: (len(s), s))
|
|
64
|
+
return tuple(names)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def find_drops(
|
|
68
|
+
root: str,
|
|
69
|
+
area_filter: str | None = None,
|
|
70
|
+
label_filter: str | None = None,
|
|
71
|
+
capture_filter: str | None = None,
|
|
72
|
+
) -> list[Drop]:
|
|
73
|
+
"""Return drops to process. One per area (newest dated drop).
|
|
74
|
+
|
|
75
|
+
area_filter: exact area folder name to restrict to.
|
|
76
|
+
label_filter: only return drops whose drop_label matches.
|
|
77
|
+
capture_filter: restricts each drop's captures tuple to just this name.
|
|
78
|
+
"""
|
|
79
|
+
root = os.path.abspath(root)
|
|
80
|
+
if not os.path.isdir(root):
|
|
81
|
+
raise FileNotFoundError(f'root not found: {root}')
|
|
82
|
+
|
|
83
|
+
drops: list[Drop] = []
|
|
84
|
+
for area, area_dir in _list_areas(root):
|
|
85
|
+
if area_filter and area != area_filter:
|
|
86
|
+
continue
|
|
87
|
+
latest = _latest_drop_dir(area_dir)
|
|
88
|
+
if latest is None:
|
|
89
|
+
continue
|
|
90
|
+
drop_date, drop_label, drop_dir = latest
|
|
91
|
+
if label_filter and drop_label != label_filter:
|
|
92
|
+
continue
|
|
93
|
+
captures = _captures(drop_dir)
|
|
94
|
+
if not captures:
|
|
95
|
+
continue
|
|
96
|
+
if capture_filter:
|
|
97
|
+
if capture_filter not in captures:
|
|
98
|
+
continue
|
|
99
|
+
captures = (capture_filter,)
|
|
100
|
+
drops.append(Drop(
|
|
101
|
+
area=area,
|
|
102
|
+
drop_date=drop_date,
|
|
103
|
+
drop_label=drop_label,
|
|
104
|
+
drop_dir=drop_dir,
|
|
105
|
+
captures=captures,
|
|
106
|
+
))
|
|
107
|
+
return drops
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def parse_single_drop_arg(arg: str, root: str) -> Drop:
|
|
111
|
+
"""Parse a positional argument like 'Chor bazar/2026-05-27_r110565/' into a Drop.
|
|
112
|
+
|
|
113
|
+
Used when the user passes a specific drop directory rather than --area.
|
|
114
|
+
"""
|
|
115
|
+
arg = arg.rstrip('/\\')
|
|
116
|
+
parts = arg.replace('\\', '/').split('/')
|
|
117
|
+
if len(parts) < 2:
|
|
118
|
+
raise ValueError(f'expected <area>/<dated_drop>, got {arg!r}')
|
|
119
|
+
area, dated = parts[-2], parts[-1]
|
|
120
|
+
m = DATED_RE.match(dated)
|
|
121
|
+
if not m:
|
|
122
|
+
raise ValueError(f'not a dated drop folder: {dated!r}')
|
|
123
|
+
drop_dir = os.path.join(root, area, dated)
|
|
124
|
+
if not os.path.isdir(drop_dir):
|
|
125
|
+
raise FileNotFoundError(f'drop dir does not exist: {drop_dir}')
|
|
126
|
+
return Drop(
|
|
127
|
+
area=area,
|
|
128
|
+
drop_date=m.group(1),
|
|
129
|
+
drop_label=m.group(2) or '',
|
|
130
|
+
drop_dir=drop_dir,
|
|
131
|
+
captures=_captures(drop_dir),
|
|
132
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Emit <root>/_global_entities.parquet.
|
|
2
|
+
|
|
3
|
+
One row per (stable_key, kind, area, drop_date, drop_label, capture, local_id).
|
|
4
|
+
This is the canonical index for cross-drop entity joins. Layer 2 reports
|
|
5
|
+
join into draws/passes/events tables using stable_key directly:
|
|
6
|
+
|
|
7
|
+
SELECT *
|
|
8
|
+
FROM read_parquet('**/draws.parquet') d
|
|
9
|
+
JOIN read_parquet('_global_entities.parquet') g
|
|
10
|
+
ON g.area = d.area
|
|
11
|
+
AND g.drop_date = d.drop_date
|
|
12
|
+
AND g.drop_label = d.drop_label
|
|
13
|
+
AND g.capture = d.capture
|
|
14
|
+
AND g.local_id = d.fs_shader_id
|
|
15
|
+
AND g.kind = 'shader'
|
|
16
|
+
WHERE g.stable_key = 'abc123...';
|
|
17
|
+
|
|
18
|
+
The (kind, local_id) pair lets reports resolve any per-capture resource ID
|
|
19
|
+
back to its drop-spanning identity.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import glob
|
|
25
|
+
import os
|
|
26
|
+
|
|
27
|
+
import pyarrow as pa
|
|
28
|
+
import pyarrow.csv as pacsv
|
|
29
|
+
import pyarrow.parquet as papq
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_ENTITY_TABLES = [
|
|
33
|
+
('shaders', 'shader_id', 'shader'),
|
|
34
|
+
('textures', 'tex_id', 'texture'),
|
|
35
|
+
('render_targets', 'rt_id', 'texture'), # RTs are textures
|
|
36
|
+
('programs', 'program_id', 'program'),
|
|
37
|
+
('samplers', 'sampler_id', 'sampler'),
|
|
38
|
+
('fbos', 'fbo_id', 'fbo'),
|
|
39
|
+
('buffers', 'buffer_id', 'buffer'),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_OUT_COLS = ('stable_key', 'kind', 'area', 'drop_date', 'drop_label',
|
|
44
|
+
'capture', 'local_id')
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_global_entities(root: str) -> int:
|
|
48
|
+
"""Walk every entity parquet under <root>; emit _global_entities.parquet.
|
|
49
|
+
|
|
50
|
+
Returns the row count written.
|
|
51
|
+
"""
|
|
52
|
+
cols: dict[str, list] = {c: [] for c in _OUT_COLS}
|
|
53
|
+
|
|
54
|
+
from . import paths as _paths
|
|
55
|
+
data_root = _paths.data_root(root)
|
|
56
|
+
for table, id_col, kind in _ENTITY_TABLES:
|
|
57
|
+
for path in sorted(glob.glob(os.path.join(data_root, '*', '*',
|
|
58
|
+
f'{table}.parquet'))):
|
|
59
|
+
try:
|
|
60
|
+
t = papq.read_table(path, columns=['stable_key', 'area', 'drop_date',
|
|
61
|
+
'drop_label', 'capture', id_col])
|
|
62
|
+
except Exception:
|
|
63
|
+
continue
|
|
64
|
+
sk = t.column('stable_key').to_pylist()
|
|
65
|
+
ar = t.column('area').to_pylist()
|
|
66
|
+
dd = t.column('drop_date').to_pylist()
|
|
67
|
+
dl = t.column('drop_label').to_pylist()
|
|
68
|
+
cp = t.column('capture').to_pylist()
|
|
69
|
+
lid = t.column(id_col).to_pylist()
|
|
70
|
+
for i in range(len(sk)):
|
|
71
|
+
if not sk[i]:
|
|
72
|
+
continue # skip entities without a stable_key (best-effort only)
|
|
73
|
+
cols['stable_key'].append(sk[i])
|
|
74
|
+
cols['kind'].append(kind)
|
|
75
|
+
cols['area'].append(ar[i])
|
|
76
|
+
cols['drop_date'].append(dd[i])
|
|
77
|
+
cols['drop_label'].append(dl[i])
|
|
78
|
+
cols['capture'].append(cp[i])
|
|
79
|
+
cols['local_id'].append(int(lid[i]) if lid[i] is not None else 0)
|
|
80
|
+
|
|
81
|
+
arrays: dict[str, pa.Array] = {}
|
|
82
|
+
for c in _OUT_COLS:
|
|
83
|
+
if c == 'local_id':
|
|
84
|
+
arrays[c] = pa.array(cols[c], type=pa.int64())
|
|
85
|
+
else:
|
|
86
|
+
arrays[c] = pa.array(cols[c], type=pa.string())
|
|
87
|
+
table = pa.table(arrays)
|
|
88
|
+
|
|
89
|
+
os.makedirs(_paths.data_root(root), exist_ok=True)
|
|
90
|
+
papq.write_table(table, _paths.global_entities_parquet(root), compression='snappy')
|
|
91
|
+
pacsv.write_csv(table, _paths.global_entities_csv(root))
|
|
92
|
+
return table.num_rows
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == '__main__':
|
|
96
|
+
import sys
|
|
97
|
+
root = sys.argv[1] if len(sys.argv) > 1 else '.'
|
|
98
|
+
n = build_global_entities(root)
|
|
99
|
+
print(f'wrote _global_entities.parquet: {n} rows')
|
|
File without changes
|