cseq 0.0.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.
- cseq/__init__.py +9 -0
- cseq/acceptance.py +162 -0
- cseq/api.py +328 -0
- cseq/binary.py +122 -0
- cseq/cache.py +420 -0
- cseq/cli.py +895 -0
- cseq/compile_db.py +98 -0
- cseq/config.py +138 -0
- cseq/cpu_rules.py +48 -0
- cseq/dependencies.py +202 -0
- cseq/docs.py +21 -0
- cseq/dwarf.py +115 -0
- cseq/event_store.py +378 -0
- cseq/explain.py +113 -0
- cseq/fast_parser.py +286 -0
- cseq/html.py +485 -0
- cseq/html_bundle.py +464 -0
- cseq/hybrid_parser.py +120 -0
- cseq/linker.py +117 -0
- cseq/marker.py +294 -0
- cseq/model.py +367 -0
- cseq/parser.py +797 -0
- cseq/parser_contract_cases.json +69 -0
- cseq/parser_dependency_lock.json +59 -0
- cseq/parser_environment.py +186 -0
- cseq/parser_migration.py +126 -0
- cseq/plugin.py +216 -0
- cseq/project.py +305 -0
- cseq/query.py +62 -0
- cseq/resources/README.md +171 -0
- cseq/resources/design.md +6197 -0
- cseq/resources/verification_report.html +26 -0
- cseq/runtime.py +177 -0
- cseq/runtime_address.py +79 -0
- cseq/runtime_cpu.py +87 -0
- cseq/scanner.py +54 -0
- cseq/sequence.py +281 -0
- cseq/server.py +104 -0
- cseq/source_index.py +227 -0
- cseq/static_store.py +280 -0
- cseq/trace_analysis.py +336 -0
- cseq/trace_diff.py +56 -0
- cseq/tree_sitter_parser.py +468 -0
- cseq/valueflow.py +179 -0
- cseq-0.0.1.dist-info/METADATA +181 -0
- cseq-0.0.1.dist-info/RECORD +49 -0
- cseq-0.0.1.dist-info/WHEEL +5 -0
- cseq-0.0.1.dist-info/entry_points.txt +2 -0
- cseq-0.0.1.dist-info/top_level.txt +1 -0
cseq/cli.py
ADDED
|
@@ -0,0 +1,895 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .binary import load_binary_image
|
|
11
|
+
from .dwarf import load_dwarf_lines
|
|
12
|
+
from .runtime_address import discover_runtime_address_evidence
|
|
13
|
+
from .project import Project
|
|
14
|
+
from .config import CseqConfig, default_config_text, validate_config
|
|
15
|
+
from .sequence import build_sequence, render_plantuml
|
|
16
|
+
from .html import render_html
|
|
17
|
+
from .html_bundle import write_html_bundle, write_html_range_bundle, write_html_range_bundle_from_event_store
|
|
18
|
+
from .marker import apply_marker, apply_marker_overlay, audit_markers, undo_marker
|
|
19
|
+
from .scanner import scan_sources
|
|
20
|
+
from .runtime import RuntimePattern, build_trace_session, iter_runtime_files
|
|
21
|
+
from .runtime_cpu import apply_runtime_cpu_evidence
|
|
22
|
+
from .explain import explain_call
|
|
23
|
+
from .query import query_calls, query_unresolved, query_paths
|
|
24
|
+
from .event_store import EventStore
|
|
25
|
+
from .trace_diff import diff_sessions
|
|
26
|
+
from .trace_analysis import reconstruct_static_paths, score_marker_suggestions, ReconstructionState
|
|
27
|
+
from .server import make_server
|
|
28
|
+
from .parser_environment import probe_parser_environment
|
|
29
|
+
from .parser_migration import build_g2b_resume_report
|
|
30
|
+
from .docs import export_document, export_all
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
34
|
+
p = argparse.ArgumentParser(prog="cseq")
|
|
35
|
+
p.add_argument("--version", action="version", version=f"cseq {__version__}")
|
|
36
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
37
|
+
|
|
38
|
+
doctor = sub.add_parser("doctor", help="check local execution environment")
|
|
39
|
+
doctor.add_argument("--parser-wheel-dir", help="verify the locked Tree-sitter parser wheels without installing them")
|
|
40
|
+
doctor.set_defaults(handler=_doctor)
|
|
41
|
+
|
|
42
|
+
analyze = sub.add_parser("analyze", help="scan and parse a C source tree")
|
|
43
|
+
analyze.add_argument("path")
|
|
44
|
+
analyze.add_argument("--json", action="store_true", dest="as_json")
|
|
45
|
+
analyze.add_argument("--entry", default="main")
|
|
46
|
+
analyze.add_argument("--plantuml")
|
|
47
|
+
analyze.add_argument("--html")
|
|
48
|
+
analyze.add_argument("--html-mode", choices=["portable","bundle","range","auto"], default="auto")
|
|
49
|
+
analyze.add_argument("--bundle-threshold-events", type=int, default=10000)
|
|
50
|
+
analyze.add_argument("--range-threshold-events", type=int, default=100000)
|
|
51
|
+
analyze.add_argument("--viewport-size", type=int, default=200)
|
|
52
|
+
analyze.add_argument("--config")
|
|
53
|
+
analyze.add_argument("--compile-db")
|
|
54
|
+
analyze.add_argument("--linker-map")
|
|
55
|
+
analyze.add_argument("--binary")
|
|
56
|
+
analyze.add_argument("--dwarf")
|
|
57
|
+
analyze.add_argument("--configuration")
|
|
58
|
+
analyze.add_argument("--define", action="append", default=[])
|
|
59
|
+
analyze.add_argument("--runtime-log", action="append", default=[])
|
|
60
|
+
analyze.add_argument("--runtime-pattern")
|
|
61
|
+
analyze.add_argument("--runtime-time-format")
|
|
62
|
+
analyze.add_argument("--runtime-session", default="default")
|
|
63
|
+
analyze.add_argument("--compare-runtime-log", action="append", default=[])
|
|
64
|
+
analyze.add_argument("--compare-session", default="compare")
|
|
65
|
+
analyze.add_argument("--diagnose-start")
|
|
66
|
+
analyze.add_argument("--diagnose-end")
|
|
67
|
+
analyze.add_argument("--no-cache", action="store_true")
|
|
68
|
+
analyze.add_argument("--jobs", type=int, default=1, help="parallel clang translation units")
|
|
69
|
+
analyze.add_argument("--static-store", help="SQLite path for out-of-core function metadata, or 'auto' for .cseq/static/symbols.db")
|
|
70
|
+
analyze.add_argument("--summary-only", action="store_true", help="omit per-function rows from analyze output")
|
|
71
|
+
analyze.set_defaults(handler=_analyze)
|
|
72
|
+
|
|
73
|
+
html_cmd = sub.add_parser("html", help="generate HTML from an analyzed source tree")
|
|
74
|
+
html_cmd.add_argument("path")
|
|
75
|
+
html_cmd.add_argument("--entry", default="main")
|
|
76
|
+
html_cmd.add_argument("--output", required=True)
|
|
77
|
+
html_cmd.add_argument("--mode", choices=["portable","bundle","range","auto"], default="auto")
|
|
78
|
+
html_cmd.add_argument("--bundle-threshold-events", type=int, default=10000)
|
|
79
|
+
html_cmd.add_argument("--range-threshold-events", type=int, default=100000)
|
|
80
|
+
html_cmd.add_argument("--viewport-size", type=int, default=200)
|
|
81
|
+
html_cmd.add_argument("--config")
|
|
82
|
+
html_cmd.add_argument("--compile-db")
|
|
83
|
+
html_cmd.add_argument("--linker-map")
|
|
84
|
+
html_cmd.add_argument("--binary")
|
|
85
|
+
html_cmd.add_argument("--dwarf")
|
|
86
|
+
html_cmd.add_argument("--configuration")
|
|
87
|
+
html_cmd.add_argument("--define", action="append", default=[])
|
|
88
|
+
html_cmd.add_argument("--runtime-log", action="append", default=[])
|
|
89
|
+
html_cmd.add_argument("--runtime-pattern")
|
|
90
|
+
html_cmd.add_argument("--runtime-time-format")
|
|
91
|
+
html_cmd.add_argument("--runtime-session", default="default")
|
|
92
|
+
html_cmd.add_argument("--compare-runtime-log", action="append", default=[])
|
|
93
|
+
html_cmd.add_argument("--compare-session", default="compare")
|
|
94
|
+
html_cmd.add_argument("--diagnose-start")
|
|
95
|
+
html_cmd.add_argument("--diagnose-end")
|
|
96
|
+
html_cmd.add_argument("--jobs", type=int, default=1, help="parallel clang translation units")
|
|
97
|
+
html_cmd.add_argument("--static-store", help="SQLite path for out-of-core function metadata, or 'auto' for .cseq/static/symbols.db")
|
|
98
|
+
html_cmd.set_defaults(handler=_html_command)
|
|
99
|
+
|
|
100
|
+
puml_cmd = sub.add_parser("plantuml", help="generate PlantUML from an analyzed source tree")
|
|
101
|
+
puml_cmd.add_argument("path")
|
|
102
|
+
puml_cmd.add_argument("--entry", default="main")
|
|
103
|
+
puml_cmd.add_argument("--output", required=True)
|
|
104
|
+
puml_cmd.add_argument("--config")
|
|
105
|
+
puml_cmd.add_argument("--compile-db")
|
|
106
|
+
puml_cmd.add_argument("--linker-map")
|
|
107
|
+
puml_cmd.add_argument("--binary")
|
|
108
|
+
puml_cmd.add_argument("--dwarf")
|
|
109
|
+
puml_cmd.add_argument("--configuration")
|
|
110
|
+
puml_cmd.add_argument("--define", action="append", default=[])
|
|
111
|
+
puml_cmd.add_argument("--jobs", type=int, default=1, help="parallel clang translation units")
|
|
112
|
+
puml_cmd.add_argument("--static-store", help="SQLite path for out-of-core function metadata, or 'auto' for .cseq/static/symbols.db")
|
|
113
|
+
puml_cmd.set_defaults(handler=_plantuml_command)
|
|
114
|
+
|
|
115
|
+
cache_cmd = sub.add_parser("cache", help="inspect or clear parser cache")
|
|
116
|
+
cache_sub = cache_cmd.add_subparsers(dest="cache_command", required=True)
|
|
117
|
+
cache_info = cache_sub.add_parser("info")
|
|
118
|
+
cache_info.add_argument("path")
|
|
119
|
+
cache_info.set_defaults(handler=_cache_info)
|
|
120
|
+
cache_clear = cache_sub.add_parser("clear")
|
|
121
|
+
cache_clear.add_argument("path")
|
|
122
|
+
cache_clear.set_defaults(handler=_cache_clear)
|
|
123
|
+
|
|
124
|
+
config_cmd = sub.add_parser("config", help="initialize or validate cseq.toml")
|
|
125
|
+
config_sub = config_cmd.add_subparsers(dest="config_command", required=True)
|
|
126
|
+
config_init = config_sub.add_parser("init", help="create a conservative cseq.toml")
|
|
127
|
+
config_init.add_argument("path", nargs="?", default=".")
|
|
128
|
+
config_init.add_argument("--force", action="store_true")
|
|
129
|
+
config_init.set_defaults(handler=_config_init)
|
|
130
|
+
config_validate = config_sub.add_parser("validate", help="validate cseq.toml")
|
|
131
|
+
config_validate.add_argument("path", nargs="?", default="cseq.toml")
|
|
132
|
+
config_validate.set_defaults(handler=_config_validate)
|
|
133
|
+
|
|
134
|
+
marker = sub.add_parser("marker", help="manage runtime source markers")
|
|
135
|
+
marker_sub = marker.add_subparsers(dest="marker_command", required=True)
|
|
136
|
+
|
|
137
|
+
marker_apply = marker_sub.add_parser("apply", help="insert a marker into an existing string literal")
|
|
138
|
+
marker_apply.add_argument("path")
|
|
139
|
+
marker_apply.add_argument("--contains", required=True, dest="literal_contains")
|
|
140
|
+
marker_apply.add_argument("--occurrence", type=int, default=0)
|
|
141
|
+
marker_apply.add_argument("--id", dest="marker_id")
|
|
142
|
+
marker_apply.add_argument("--expected-hash")
|
|
143
|
+
marker_apply.add_argument("--project-root")
|
|
144
|
+
marker_apply.add_argument("--manifest", required=True)
|
|
145
|
+
marker_apply.add_argument(
|
|
146
|
+
"--overlay-output",
|
|
147
|
+
help="write the marker-injected source to this path and leave the original unchanged",
|
|
148
|
+
)
|
|
149
|
+
marker_apply.set_defaults(handler=_marker_apply)
|
|
150
|
+
|
|
151
|
+
marker_undo = marker_sub.add_parser("undo", help="undo a marker rewrite using its manifest")
|
|
152
|
+
marker_undo.add_argument("path")
|
|
153
|
+
marker_undo.add_argument("--manifest", required=True)
|
|
154
|
+
marker_undo.set_defaults(handler=_marker_undo)
|
|
155
|
+
|
|
156
|
+
marker_audit = marker_sub.add_parser("audit", help="audit marker duplicates and malformed IDs")
|
|
157
|
+
marker_audit.add_argument("path")
|
|
158
|
+
marker_audit.set_defaults(handler=_marker_audit)
|
|
159
|
+
|
|
160
|
+
trace = sub.add_parser("trace", help="import runtime marker logs")
|
|
161
|
+
trace_sub = trace.add_subparsers(dest="trace_command", required=True)
|
|
162
|
+
trace_import = trace_sub.add_parser("import", help="parse one or more runtime logs")
|
|
163
|
+
trace_import.add_argument("paths", nargs="+")
|
|
164
|
+
trace_import.add_argument("--session", default="default")
|
|
165
|
+
trace_import.add_argument("--pattern")
|
|
166
|
+
trace_import.add_argument("--time-format")
|
|
167
|
+
trace_import.add_argument("--binary")
|
|
168
|
+
trace_import.add_argument("--dwarf")
|
|
169
|
+
trace_import.add_argument("--load-bias", type=lambda x: int(x, 0), default=0)
|
|
170
|
+
trace_import.add_argument("--address-key", action="append", default=[])
|
|
171
|
+
trace_import.add_argument("--json", action="store_true", dest="as_json")
|
|
172
|
+
trace_import.add_argument("--store", help="persist session to SQLite Event Store")
|
|
173
|
+
trace_import.add_argument("--stream", action="store_true", help="stream logs directly into Event Store without materializing TraceSession")
|
|
174
|
+
trace_import.add_argument("--chunk-size", type=int, default=10000)
|
|
175
|
+
trace_import.add_argument("--batch-size", type=int, default=50000)
|
|
176
|
+
trace_import.add_argument("--commit-every", type=int, default=250000)
|
|
177
|
+
trace_import.set_defaults(handler=_trace_import)
|
|
178
|
+
|
|
179
|
+
diff = sub.add_parser("diff", help="compare two runtime sessions")
|
|
180
|
+
diff.add_argument("--left", action="append", required=True)
|
|
181
|
+
diff.add_argument("--right", action="append", required=True)
|
|
182
|
+
diff.add_argument("--left-session", default="left")
|
|
183
|
+
diff.add_argument("--right-session", default="right")
|
|
184
|
+
diff.add_argument("--pattern")
|
|
185
|
+
diff.add_argument("--time-format")
|
|
186
|
+
diff.add_argument("--json", action="store_true", dest="as_json")
|
|
187
|
+
diff.add_argument("--html")
|
|
188
|
+
diff.set_defaults(handler=_diff)
|
|
189
|
+
|
|
190
|
+
explain = sub.add_parser("explain", help="explain why a call target was resolved")
|
|
191
|
+
explain.add_argument("path")
|
|
192
|
+
explain.add_argument("--caller", required=True)
|
|
193
|
+
explain.add_argument("--callee")
|
|
194
|
+
explain.add_argument("--config")
|
|
195
|
+
explain.add_argument("--compile-db")
|
|
196
|
+
explain.add_argument("--linker-map")
|
|
197
|
+
explain.add_argument("--binary")
|
|
198
|
+
explain.add_argument("--dwarf")
|
|
199
|
+
explain.add_argument("--configuration")
|
|
200
|
+
explain.add_argument("--define", action="append", default=[])
|
|
201
|
+
explain.add_argument("--jobs", type=int, default=1, help="parallel clang translation units")
|
|
202
|
+
explain.add_argument("--static-store", help="SQLite path for out-of-core function metadata, or 'auto' for .cseq/static/symbols.db")
|
|
203
|
+
explain.set_defaults(handler=_explain)
|
|
204
|
+
|
|
205
|
+
query = sub.add_parser("query", help="query calls, unresolved sites, or static paths")
|
|
206
|
+
query.add_argument("path")
|
|
207
|
+
query.add_argument("kind", choices=["calls", "unresolved", "paths"])
|
|
208
|
+
query.add_argument("--function")
|
|
209
|
+
query.add_argument("--start")
|
|
210
|
+
query.add_argument("--end")
|
|
211
|
+
query.add_argument("--config")
|
|
212
|
+
query.add_argument("--compile-db")
|
|
213
|
+
query.add_argument("--linker-map")
|
|
214
|
+
query.add_argument("--binary")
|
|
215
|
+
query.add_argument("--dwarf")
|
|
216
|
+
query.add_argument("--configuration")
|
|
217
|
+
query.add_argument("--define", action="append", default=[])
|
|
218
|
+
query.add_argument("--jobs", type=int, default=1, help="parallel clang translation units")
|
|
219
|
+
query.add_argument("--static-store", help="SQLite path for out-of-core function metadata, or 'auto' for .cseq/static/symbols.db")
|
|
220
|
+
query.set_defaults(handler=_query)
|
|
221
|
+
|
|
222
|
+
docs_cmd = sub.add_parser("docs", help="show or export bundled README, design document, and verification report")
|
|
223
|
+
docs_sub = docs_cmd.add_subparsers(dest="docs_command", required=True)
|
|
224
|
+
for kind, help_text in (
|
|
225
|
+
("readme", "show/export README"),
|
|
226
|
+
("design", "show/export the cleaned design document"),
|
|
227
|
+
("report", "show/export the HTML implementation and verification report"),
|
|
228
|
+
):
|
|
229
|
+
d = docs_sub.add_parser(kind, help=help_text)
|
|
230
|
+
d.add_argument("--output", help="write to this path instead of stdout")
|
|
231
|
+
d.set_defaults(handler=_docs)
|
|
232
|
+
docs_all = docs_sub.add_parser("all", help="export all bundled documents")
|
|
233
|
+
docs_all.add_argument("--output-dir", default="cseq-docs", help="destination directory (default: cseq-docs)")
|
|
234
|
+
docs_all.set_defaults(handler=_docs)
|
|
235
|
+
|
|
236
|
+
serve = sub.add_parser("serve", help="serve generated HTML/bundle files over local HTTP")
|
|
237
|
+
serve.add_argument("path", nargs="?", default=".")
|
|
238
|
+
serve.add_argument("--host", default="127.0.0.1")
|
|
239
|
+
serve.add_argument("--port", type=int, default=8000)
|
|
240
|
+
serve.set_defaults(handler=_serve)
|
|
241
|
+
return p
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _docs(args: argparse.Namespace) -> int:
|
|
245
|
+
if args.docs_command == "all":
|
|
246
|
+
paths = export_all(args.output_dir)
|
|
247
|
+
for path in paths:
|
|
248
|
+
print(path)
|
|
249
|
+
return 0
|
|
250
|
+
path = export_document(args.docs_command, args.output)
|
|
251
|
+
if path is not None:
|
|
252
|
+
print(path)
|
|
253
|
+
return 0
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _serve(args: argparse.Namespace) -> int:
|
|
257
|
+
from pathlib import Path
|
|
258
|
+
root = Path(args.path).resolve()
|
|
259
|
+
if not root.is_dir():
|
|
260
|
+
print(f"cseq: not a directory: {root}", file=sys.stderr)
|
|
261
|
+
return 3
|
|
262
|
+
try:
|
|
263
|
+
server = make_server(root, args.host, args.port)
|
|
264
|
+
except OSError as exc:
|
|
265
|
+
print(f"cseq: {exc}", file=sys.stderr)
|
|
266
|
+
return 3
|
|
267
|
+
host, port = server.server_address[:2]
|
|
268
|
+
print(f"cseq serving {root} at http://{host}:{port}/", flush=True)
|
|
269
|
+
try:
|
|
270
|
+
server.serve_forever()
|
|
271
|
+
except KeyboardInterrupt:
|
|
272
|
+
pass
|
|
273
|
+
finally:
|
|
274
|
+
server.server_close()
|
|
275
|
+
return 0
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _doctor(args: argparse.Namespace) -> int:
|
|
279
|
+
clang = shutil.which("clang")
|
|
280
|
+
parser_env = probe_parser_environment()
|
|
281
|
+
payload = {
|
|
282
|
+
"cseq_version": __version__,
|
|
283
|
+
"python": sys.version.split()[0],
|
|
284
|
+
"clang": clang,
|
|
285
|
+
"semantic_backend_ready": bool(clang),
|
|
286
|
+
"canonical_fast_frontend": "tree-sitter-c",
|
|
287
|
+
"parser_environment": parser_env.to_dict(),
|
|
288
|
+
"g2b_resume": build_g2b_resume_report(getattr(args, "parser_wheel_dir", None)).to_dict(),
|
|
289
|
+
"current_execution_mode": (
|
|
290
|
+
"tree-sitter+clang"
|
|
291
|
+
if parser_env.canonical_fast_frontend_ready and clang
|
|
292
|
+
else "lexical-fallback+clang" if clang else "unavailable"
|
|
293
|
+
),
|
|
294
|
+
}
|
|
295
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
296
|
+
# `doctor` is a diagnostic/reporting command. Component readiness is
|
|
297
|
+
# represented explicitly in the JSON payload (semantic_backend_ready,
|
|
298
|
+
# canonical_fast_frontend_ready, g2b_resume.status, current_execution_mode)
|
|
299
|
+
# and must not be collapsed into an unrelated process exit code. A
|
|
300
|
+
# successfully produced diagnostic report therefore exits 0 even when an
|
|
301
|
+
# optional/currently-unavailable backend (for example clang) is absent.
|
|
302
|
+
return 0
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _advanced_html_inputs(index, args, runtime_session):
|
|
307
|
+
pattern = RuntimePattern(args.runtime_pattern, args.runtime_time_format) if getattr(args, "runtime_pattern", None) else None
|
|
308
|
+
trace_diff = None
|
|
309
|
+
compare_logs = getattr(args, "compare_runtime_log", []) or []
|
|
310
|
+
if runtime_session is not None and compare_logs:
|
|
311
|
+
other = build_trace_session(getattr(args, "compare_session", "compare"), compare_logs, pattern=pattern)
|
|
312
|
+
trace_diff = diff_sessions(runtime_session, other)
|
|
313
|
+
suggestions = []
|
|
314
|
+
start = getattr(args, "diagnose_start", None)
|
|
315
|
+
end = getattr(args, "diagnose_end", None)
|
|
316
|
+
if start and end:
|
|
317
|
+
reconstructed = reconstruct_static_paths(index, start, end)
|
|
318
|
+
if reconstructed.state == ReconstructionState.AMBIGUOUS_PATH:
|
|
319
|
+
suggestions = score_marker_suggestions(reconstructed.paths, count=5)
|
|
320
|
+
return trace_diff, suggestions
|
|
321
|
+
|
|
322
|
+
def _write_html_output(
|
|
323
|
+
output: str, model, *, runtime_session=None, cpu_evidence=(), explanations=None,
|
|
324
|
+
trace_diff=None, marker_suggestions=None, mode: str = "auto",
|
|
325
|
+
bundle_threshold_events: int = 10000, range_threshold_events: int = 100000, viewport_size: int = 200,
|
|
326
|
+
) -> dict[str, object]:
|
|
327
|
+
from pathlib import Path
|
|
328
|
+
event_count = len(runtime_session.events) if runtime_session else 0
|
|
329
|
+
selected = mode
|
|
330
|
+
if selected == "auto":
|
|
331
|
+
selected = "range" if event_count > max(0, int(range_threshold_events)) else ("bundle" if event_count > max(0, int(bundle_threshold_events)) else "portable")
|
|
332
|
+
kwargs = dict(
|
|
333
|
+
runtime_session=runtime_session, cpu_evidence=cpu_evidence,
|
|
334
|
+
explanations=explanations, trace_diff=trace_diff,
|
|
335
|
+
marker_suggestions=marker_suggestions, viewport_size=viewport_size,
|
|
336
|
+
)
|
|
337
|
+
if selected == "bundle":
|
|
338
|
+
html_path, data_path = write_html_bundle(output, model, **kwargs)
|
|
339
|
+
return {"mode": "bundle", "html": str(html_path), "data": str(data_path), "events": event_count}
|
|
340
|
+
if selected == "range":
|
|
341
|
+
if runtime_session is None:
|
|
342
|
+
Path(output).write_text(render_html(model, **kwargs), encoding="utf-8")
|
|
343
|
+
return {"mode": "portable", "html": str(output), "data": None, "events": 0}
|
|
344
|
+
html_path, data_path, index_path = write_html_range_bundle(output, model, **kwargs)
|
|
345
|
+
return {"mode": "range", "html": str(html_path), "data": str(data_path), "index": str(index_path), "events": event_count}
|
|
346
|
+
Path(output).write_text(render_html(model, **kwargs), encoding="utf-8")
|
|
347
|
+
return {"mode": "portable", "html": str(output), "data": None, "events": event_count}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _analyze(args: argparse.Namespace) -> int:
|
|
351
|
+
try:
|
|
352
|
+
project = Project(
|
|
353
|
+
args.path,
|
|
354
|
+
config=args.config,
|
|
355
|
+
configuration=args.configuration,
|
|
356
|
+
defines=tuple(args.define),
|
|
357
|
+
use_cache=not args.no_cache,
|
|
358
|
+
compile_db=args.compile_db,
|
|
359
|
+
linker_map=args.linker_map,
|
|
360
|
+
binary=args.binary,
|
|
361
|
+
dwarf=args.dwarf,
|
|
362
|
+
jobs=args.jobs,
|
|
363
|
+
static_store=args.static_store,
|
|
364
|
+
)
|
|
365
|
+
index = project.index()
|
|
366
|
+
except (OSError, RuntimeError) as exc:
|
|
367
|
+
print(f"cseq: {exc}", file=sys.stderr)
|
|
368
|
+
return 3
|
|
369
|
+
|
|
370
|
+
payload = {
|
|
371
|
+
"root": str(index.root),
|
|
372
|
+
"source_files": len(index.source_files),
|
|
373
|
+
"translation_units": len(index.parse_artifacts),
|
|
374
|
+
"cache": {
|
|
375
|
+
"parser": {"hits": project.cache_hits, "misses": project.cache_misses},
|
|
376
|
+
"analysis": {"hits": project.analysis_cache_hits, "misses": project.analysis_cache_misses},
|
|
377
|
+
"source_index": {
|
|
378
|
+
"hash_hits": project.source_index.hash_hits if project.source_index is not None else 0,
|
|
379
|
+
"hash_misses": project.source_index.hash_misses if project.source_index is not None else 0,
|
|
380
|
+
"dependency_hits": project.source_index.dependency_hits if project.source_index is not None else 0,
|
|
381
|
+
"dependency_misses": project.source_index.dependency_misses if project.source_index is not None else 0,
|
|
382
|
+
"path": str(project.source_index_path) if project.source_index is not None else None,
|
|
383
|
+
},
|
|
384
|
+
"enabled": not args.no_cache,
|
|
385
|
+
},
|
|
386
|
+
"parser_strategy": {
|
|
387
|
+
"fast_translation_units": project.fast_artifacts,
|
|
388
|
+
"semantic_translation_units": project.semantic_artifacts,
|
|
389
|
+
},
|
|
390
|
+
"static_store": str(project.static_symbol_store.path) if project.static_symbol_store is not None else None,
|
|
391
|
+
"static_store_stats": {
|
|
392
|
+
"hits": project.static_store_hits,
|
|
393
|
+
"misses": project.static_store_misses,
|
|
394
|
+
"removed_translation_units": project.static_store_removed_tus,
|
|
395
|
+
"translation_units": project.static_symbol_store.translation_unit_count() if project.static_symbol_store is not None else 0,
|
|
396
|
+
"artifact_sidecar_hits": project.static_artifact_hits,
|
|
397
|
+
"artifact_sidecar_misses": project.static_artifact_misses,
|
|
398
|
+
},
|
|
399
|
+
"function_count": index.function_count(),
|
|
400
|
+
"functions": [] if args.summary_only else [
|
|
401
|
+
{
|
|
402
|
+
"name": f.name,
|
|
403
|
+
"id": f.qualified_id,
|
|
404
|
+
"source": f.source_path,
|
|
405
|
+
"line": f.source_range.start_line,
|
|
406
|
+
"storage": f.storage_class,
|
|
407
|
+
}
|
|
408
|
+
for f in index.functions()
|
|
409
|
+
],
|
|
410
|
+
"callsites": [
|
|
411
|
+
{
|
|
412
|
+
"caller": c.caller_name,
|
|
413
|
+
"callee": c.callee_name,
|
|
414
|
+
"raw": c.raw_text,
|
|
415
|
+
"target_kind": c.target_kind.value,
|
|
416
|
+
"target_function_ids": list(c.target_function_ids),
|
|
417
|
+
"unknown_possible": c.unknown_possible,
|
|
418
|
+
"callee_slot": c.callee_slot_key,
|
|
419
|
+
}
|
|
420
|
+
for c in index.callsites
|
|
421
|
+
],
|
|
422
|
+
"cpu_evidence": [
|
|
423
|
+
{
|
|
424
|
+
"function_id": e.target_function_id,
|
|
425
|
+
"layer": e.layer.value,
|
|
426
|
+
"kind": e.value_kind.value,
|
|
427
|
+
"value": e.value,
|
|
428
|
+
"provenance": e.provenance,
|
|
429
|
+
"explicit_override": e.explicit_override,
|
|
430
|
+
}
|
|
431
|
+
for e in index.cpu_evidence
|
|
432
|
+
],
|
|
433
|
+
"linker_evidence": [
|
|
434
|
+
{
|
|
435
|
+
"symbol": e.symbol_name, "address": e.address,
|
|
436
|
+
"object_file": e.object_file, "section": e.section,
|
|
437
|
+
"matched_function_ids": list(e.matched_function_ids),
|
|
438
|
+
"provenance": e.provenance,
|
|
439
|
+
}
|
|
440
|
+
for e in index.linker_evidence
|
|
441
|
+
],
|
|
442
|
+
"binary_evidence": [
|
|
443
|
+
{
|
|
444
|
+
"symbol": e.symbol_name, "address": e.address, "size": e.size,
|
|
445
|
+
"kind": e.symbol_kind, "matched_function_ids": list(e.matched_function_ids),
|
|
446
|
+
"binary_hash": e.binary_hash, "provenance": e.provenance,
|
|
447
|
+
} for e in index.binary_evidence
|
|
448
|
+
],
|
|
449
|
+
"dwarf_evidence": [
|
|
450
|
+
{
|
|
451
|
+
"function_id": e.function_id, "source": e.source_path,
|
|
452
|
+
"line": e.line, "address": e.address, "dwarf_file": e.dwarf_file,
|
|
453
|
+
"provenance": e.provenance,
|
|
454
|
+
} for e in index.dwarf_evidence
|
|
455
|
+
],
|
|
456
|
+
"plugins": {
|
|
457
|
+
"enabled": list(project.config.plugins_enabled),
|
|
458
|
+
"loaded": [p.descriptor.name for p in project.plugins.plugins],
|
|
459
|
+
"evidence": [
|
|
460
|
+
{"plugin": e.plugin_name, "kind": e.kind.value, "target": e.target, "type": e.evidence_type, "value": e.value, "confidence": e.confidence}
|
|
461
|
+
for e in project.plugin_result.evidence
|
|
462
|
+
],
|
|
463
|
+
"diagnostics": [
|
|
464
|
+
{"plugin": d.plugin_name, "severity": d.severity, "code": d.code, "message": d.message}
|
|
465
|
+
for d in project.plugin_result.diagnostics
|
|
466
|
+
],
|
|
467
|
+
},
|
|
468
|
+
"artifacts": [
|
|
469
|
+
{
|
|
470
|
+
"source": a.translation_unit.source.project_relative_path,
|
|
471
|
+
"status": a.status.value,
|
|
472
|
+
"backend": a.parser_backend,
|
|
473
|
+
"diagnostics": [
|
|
474
|
+
{"severity": d.severity, "message": d.message} for d in a.diagnostics
|
|
475
|
+
],
|
|
476
|
+
}
|
|
477
|
+
for a in index.parse_artifacts
|
|
478
|
+
],
|
|
479
|
+
}
|
|
480
|
+
model = None
|
|
481
|
+
if args.plantuml or args.html:
|
|
482
|
+
model = build_sequence(index, args.entry)
|
|
483
|
+
if args.plantuml:
|
|
484
|
+
from pathlib import Path
|
|
485
|
+
Path(args.plantuml).write_text(render_plantuml(model), encoding="utf-8")
|
|
486
|
+
if args.html:
|
|
487
|
+
from pathlib import Path
|
|
488
|
+
runtime_session = None
|
|
489
|
+
if args.runtime_log:
|
|
490
|
+
runtime_pattern = RuntimePattern(args.runtime_pattern, args.runtime_time_format) if args.runtime_pattern else None
|
|
491
|
+
runtime_session = build_trace_session(args.runtime_session, args.runtime_log, pattern=runtime_pattern)
|
|
492
|
+
apply_runtime_cpu_evidence(index, runtime_session)
|
|
493
|
+
payload["cpu_evidence"] = [
|
|
494
|
+
{
|
|
495
|
+
"function_id": e.target_function_id,
|
|
496
|
+
"layer": e.layer.value,
|
|
497
|
+
"kind": e.value_kind.value,
|
|
498
|
+
"value": e.value,
|
|
499
|
+
"provenance": e.provenance,
|
|
500
|
+
"explicit_override": e.explicit_override,
|
|
501
|
+
}
|
|
502
|
+
for e in index.cpu_evidence
|
|
503
|
+
]
|
|
504
|
+
explanation_map = {}
|
|
505
|
+
for caller in sorted({c.caller_name for c in index.callsites}):
|
|
506
|
+
explanation_map[caller] = explain_call(index, caller=caller).get("calls", [])
|
|
507
|
+
trace_diff, suggestions = _advanced_html_inputs(index, args, runtime_session)
|
|
508
|
+
html_result = _write_html_output(
|
|
509
|
+
args.html, model, runtime_session=runtime_session, cpu_evidence=index.cpu_evidence,
|
|
510
|
+
explanations=explanation_map, trace_diff=trace_diff, marker_suggestions=suggestions,
|
|
511
|
+
mode=args.html_mode, bundle_threshold_events=args.bundle_threshold_events,
|
|
512
|
+
range_threshold_events=args.range_threshold_events, viewport_size=args.viewport_size,
|
|
513
|
+
)
|
|
514
|
+
payload["html_output"] = html_result
|
|
515
|
+
if args.as_json:
|
|
516
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
517
|
+
else:
|
|
518
|
+
print(f"root: {payload['root']}")
|
|
519
|
+
print(f"source files: {payload['source_files']}")
|
|
520
|
+
print(f"translation units: {payload['translation_units']}")
|
|
521
|
+
print(f"functions: {len(payload['functions'])}")
|
|
522
|
+
for f in payload["functions"]:
|
|
523
|
+
print(f" {f['source']}:{f['line'] or '?'} {f['name']}")
|
|
524
|
+
for a in payload["artifacts"]:
|
|
525
|
+
if a["status"] != "OK":
|
|
526
|
+
print(f" [{a['status']}] {a['source']}")
|
|
527
|
+
return 0
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _project_for_output(args: argparse.Namespace):
|
|
532
|
+
project = Project(
|
|
533
|
+
args.path, config=args.config, configuration=args.configuration,
|
|
534
|
+
defines=tuple(args.define), compile_db=getattr(args, "compile_db", None),
|
|
535
|
+
linker_map=getattr(args, "linker_map", None),
|
|
536
|
+
binary=getattr(args, "binary", None),
|
|
537
|
+
dwarf=getattr(args, "dwarf", None),
|
|
538
|
+
jobs=getattr(args, "jobs", 1),
|
|
539
|
+
static_store=getattr(args, "static_store", None),
|
|
540
|
+
)
|
|
541
|
+
return project, project.index()
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _html_command(args: argparse.Namespace) -> int:
|
|
545
|
+
from pathlib import Path
|
|
546
|
+
try:
|
|
547
|
+
project, index = _project_for_output(args)
|
|
548
|
+
model = build_sequence(index, args.entry)
|
|
549
|
+
session = None
|
|
550
|
+
if args.runtime_log:
|
|
551
|
+
pattern = RuntimePattern(args.runtime_pattern, args.runtime_time_format) if args.runtime_pattern else None
|
|
552
|
+
session = build_trace_session(args.runtime_session, args.runtime_log, pattern=pattern)
|
|
553
|
+
apply_runtime_cpu_evidence(index, session)
|
|
554
|
+
explanation_map = {
|
|
555
|
+
caller: explain_call(index, caller=caller).get("calls", [])
|
|
556
|
+
for caller in sorted({c.caller_name for c in index.callsites})
|
|
557
|
+
}
|
|
558
|
+
trace_diff, suggestions = _advanced_html_inputs(index, args, session)
|
|
559
|
+
html_result = _write_html_output(
|
|
560
|
+
args.output, model, runtime_session=session, cpu_evidence=index.cpu_evidence,
|
|
561
|
+
explanations=explanation_map, trace_diff=trace_diff, marker_suggestions=suggestions,
|
|
562
|
+
mode=args.mode, bundle_threshold_events=args.bundle_threshold_events,
|
|
563
|
+
range_threshold_events=args.range_threshold_events, viewport_size=args.viewport_size,
|
|
564
|
+
)
|
|
565
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
566
|
+
print(f"cseq: {exc}", file=sys.stderr); return 3
|
|
567
|
+
print(json.dumps({"output": args.output, "html": html_result, "cache_hits": project.cache_hits, "cache_misses": project.cache_misses, "analysis_cache_hits": project.analysis_cache_hits, "analysis_cache_misses": project.analysis_cache_misses, "fast_translation_units": project.fast_artifacts, "semantic_translation_units": project.semantic_artifacts, "source_index": {"hash_hits": project.source_index.hash_hits if project.source_index is not None else 0, "hash_misses": project.source_index.hash_misses if project.source_index is not None else 0, "dependency_hits": project.source_index.dependency_hits if project.source_index is not None else 0, "dependency_misses": project.source_index.dependency_misses if project.source_index is not None else 0}}, ensure_ascii=False))
|
|
568
|
+
return 0
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def _plantuml_command(args: argparse.Namespace) -> int:
|
|
572
|
+
from pathlib import Path
|
|
573
|
+
try:
|
|
574
|
+
project, index = _project_for_output(args)
|
|
575
|
+
model = build_sequence(index, args.entry)
|
|
576
|
+
Path(args.output).write_text(render_plantuml(model), encoding="utf-8")
|
|
577
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
578
|
+
print(f"cseq: {exc}", file=sys.stderr); return 3
|
|
579
|
+
print(json.dumps({"output": args.output, "cache_hits": project.cache_hits, "cache_misses": project.cache_misses, "analysis_cache_hits": project.analysis_cache_hits, "analysis_cache_misses": project.analysis_cache_misses, "fast_translation_units": project.fast_artifacts, "semantic_translation_units": project.semantic_artifacts, "source_index": {"hash_hits": project.source_index.hash_hits if project.source_index is not None else 0, "hash_misses": project.source_index.hash_misses if project.source_index is not None else 0, "dependency_hits": project.source_index.dependency_hits if project.source_index is not None else 0, "dependency_misses": project.source_index.dependency_misses if project.source_index is not None else 0}}, ensure_ascii=False))
|
|
580
|
+
return 0
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _cache_root(path: str) -> "Path":
|
|
584
|
+
from pathlib import Path
|
|
585
|
+
return Path(path).resolve() / ".cseq" / "cache"
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _cache_info(args: argparse.Namespace) -> int:
|
|
589
|
+
from pathlib import Path
|
|
590
|
+
root = _cache_root(args.path)
|
|
591
|
+
out = {}
|
|
592
|
+
for name in ("parser", "analysis", "static-artifact"):
|
|
593
|
+
part = root / name
|
|
594
|
+
files = list(part.rglob("*.json")) if part.exists() else []
|
|
595
|
+
out[name] = {"root": str(part), "entries": len(files), "size_bytes": sum(x.stat().st_size for x in files)}
|
|
596
|
+
source_db = Path(args.path).resolve() / ".cseq" / "source" / "index.db"
|
|
597
|
+
out["source-index"] = {
|
|
598
|
+
"root": str(source_db),
|
|
599
|
+
"entries": 1 if source_db.exists() else 0,
|
|
600
|
+
"size_bytes": source_db.stat().st_size if source_db.exists() else 0,
|
|
601
|
+
}
|
|
602
|
+
# Preserve the historical parser/analysis cache totals; Source Index is a
|
|
603
|
+
# separate persistent metadata accelerator and is reported explicitly.
|
|
604
|
+
out["entries"] = sum(out[name]["entries"] for name in ("parser", "analysis", "static-artifact"))
|
|
605
|
+
out["size_bytes"] = sum(out[name]["size_bytes"] for name in ("parser", "analysis", "static-artifact"))
|
|
606
|
+
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
607
|
+
return 0
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _cache_clear(args: argparse.Namespace) -> int:
|
|
611
|
+
import shutil as _shutil
|
|
612
|
+
from pathlib import Path
|
|
613
|
+
root = _cache_root(args.path)
|
|
614
|
+
entries = len(list(root.rglob("*.json"))) if root.exists() else 0
|
|
615
|
+
if root.exists():
|
|
616
|
+
_shutil.rmtree(root)
|
|
617
|
+
source_root = Path(args.path).resolve() / ".cseq" / "source"
|
|
618
|
+
source_removed = int((source_root / "index.db").exists())
|
|
619
|
+
if source_root.exists():
|
|
620
|
+
_shutil.rmtree(source_root)
|
|
621
|
+
print(json.dumps({"root": str(root), "removed_entries": entries, "source_index_removed": source_removed}, ensure_ascii=False))
|
|
622
|
+
return 0
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def _config_init(args: argparse.Namespace) -> int:
|
|
626
|
+
from pathlib import Path
|
|
627
|
+
target = Path(args.path)
|
|
628
|
+
if target.is_dir() or target.suffix == "":
|
|
629
|
+
target = target / "cseq.toml"
|
|
630
|
+
target = target.resolve()
|
|
631
|
+
if target.exists() and not args.force:
|
|
632
|
+
print(f"cseq: config already exists: {target}", file=sys.stderr)
|
|
633
|
+
return 2
|
|
634
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
635
|
+
compile_present = (target.parent / "compile_commands.json").is_file()
|
|
636
|
+
target.write_text(default_config_text(compile_commands_present=compile_present), encoding="utf-8")
|
|
637
|
+
print(json.dumps({"path": str(target), "compile_commands_detected": compile_present}, ensure_ascii=False))
|
|
638
|
+
return 0
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _config_validate(args: argparse.Namespace) -> int:
|
|
642
|
+
try:
|
|
643
|
+
cfg = CseqConfig.load(args.path)
|
|
644
|
+
issues = validate_config(cfg)
|
|
645
|
+
except (OSError, ValueError) as exc:
|
|
646
|
+
print(json.dumps({"valid": False, "issues": [{"severity": "error", "code": "LOAD_ERROR", "message": str(exc)}]}, ensure_ascii=False, indent=2))
|
|
647
|
+
return 2
|
|
648
|
+
errors = [x for x in issues if x["severity"] == "error"]
|
|
649
|
+
print(json.dumps({"valid": not errors, "issues": issues}, ensure_ascii=False, indent=2))
|
|
650
|
+
return 0 if not errors else 2
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _marker_apply(args: argparse.Namespace) -> int:
|
|
654
|
+
try:
|
|
655
|
+
if args.overlay_output:
|
|
656
|
+
manifest = apply_marker_overlay(
|
|
657
|
+
args.path, args.overlay_output,
|
|
658
|
+
literal_contains=args.literal_contains,
|
|
659
|
+
occurrence=args.occurrence,
|
|
660
|
+
expected_hash=args.expected_hash,
|
|
661
|
+
project_root=args.project_root,
|
|
662
|
+
marker_id=args.marker_id,
|
|
663
|
+
manifest_path=args.manifest,
|
|
664
|
+
)
|
|
665
|
+
else:
|
|
666
|
+
manifest = apply_marker(
|
|
667
|
+
args.path,
|
|
668
|
+
literal_contains=args.literal_contains,
|
|
669
|
+
occurrence=args.occurrence,
|
|
670
|
+
expected_hash=args.expected_hash,
|
|
671
|
+
project_root=args.project_root,
|
|
672
|
+
marker_id=args.marker_id,
|
|
673
|
+
manifest_path=args.manifest,
|
|
674
|
+
)
|
|
675
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
676
|
+
print(f"cseq: {exc}", file=sys.stderr)
|
|
677
|
+
return 6
|
|
678
|
+
print(manifest.to_json())
|
|
679
|
+
return 0
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _marker_undo(args: argparse.Namespace) -> int:
|
|
683
|
+
try:
|
|
684
|
+
undo_marker(args.path, args.manifest)
|
|
685
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
686
|
+
print(f"cseq: {exc}", file=sys.stderr)
|
|
687
|
+
return 6
|
|
688
|
+
return 0
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _marker_audit(args: argparse.Namespace) -> int:
|
|
692
|
+
from pathlib import Path
|
|
693
|
+
root = Path(args.path)
|
|
694
|
+
if root.is_dir():
|
|
695
|
+
paths = [x.path for x in scan_sources(root)]
|
|
696
|
+
else:
|
|
697
|
+
paths = [root]
|
|
698
|
+
result = audit_markers(paths)
|
|
699
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
700
|
+
return 5 if result["duplicates"] or result["malformed"] else 0
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _diff(args: argparse.Namespace) -> int:
|
|
705
|
+
try:
|
|
706
|
+
pattern = RuntimePattern(args.pattern, args.time_format) if args.pattern else None
|
|
707
|
+
left = build_trace_session(args.left_session, args.left, pattern=pattern)
|
|
708
|
+
right = build_trace_session(args.right_session, args.right, pattern=pattern)
|
|
709
|
+
result = diff_sessions(left, right)
|
|
710
|
+
except (OSError, ValueError) as exc:
|
|
711
|
+
print(f"cseq: {exc}", file=sys.stderr); return 3
|
|
712
|
+
payload = {
|
|
713
|
+
"left_session": result.left_session,
|
|
714
|
+
"right_session": result.right_session,
|
|
715
|
+
"added_markers": list(result.added_markers),
|
|
716
|
+
"missing_markers": list(result.missing_markers),
|
|
717
|
+
"changed_cpu": list(result.changed_cpu),
|
|
718
|
+
"changed_task": list(result.changed_task),
|
|
719
|
+
}
|
|
720
|
+
if args.html:
|
|
721
|
+
from pathlib import Path
|
|
722
|
+
from .sequence import SequenceModel
|
|
723
|
+
Path(args.html).write_text(render_html(SequenceModel("trace-diff", []), trace_diff=result), encoding="utf-8")
|
|
724
|
+
if args.as_json:
|
|
725
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
726
|
+
else:
|
|
727
|
+
print(f"{result.left_session} -> {result.right_session}")
|
|
728
|
+
print(f"added markers: {len(result.added_markers)}")
|
|
729
|
+
print(f"missing markers: {len(result.missing_markers)}")
|
|
730
|
+
print(f"CPU changes: {len(result.changed_cpu)}")
|
|
731
|
+
print(f"Task changes: {len(result.changed_task)}")
|
|
732
|
+
return 0
|
|
733
|
+
|
|
734
|
+
def _trace_import(args: argparse.Namespace) -> int:
|
|
735
|
+
pattern = RuntimePattern(args.pattern, args.time_format) if args.pattern else None
|
|
736
|
+
if args.stream and (args.binary or args.dwarf or args.address_key):
|
|
737
|
+
print("cseq: runtime address evidence currently requires non-stream trace import", file=sys.stderr)
|
|
738
|
+
return 2
|
|
739
|
+
if args.stream:
|
|
740
|
+
if not args.store:
|
|
741
|
+
print("cseq: trace import --stream requires --store", file=sys.stderr)
|
|
742
|
+
return 2
|
|
743
|
+
try:
|
|
744
|
+
with EventStore(args.store) as store:
|
|
745
|
+
count = store.replace_session_events_chunked(
|
|
746
|
+
args.session,
|
|
747
|
+
iter_runtime_files(args.paths, pattern=pattern),
|
|
748
|
+
chunk_size=args.chunk_size,
|
|
749
|
+
batch_size=args.batch_size,
|
|
750
|
+
commit_every=args.commit_every,
|
|
751
|
+
rebuild_indexes=True,
|
|
752
|
+
)
|
|
753
|
+
store_summary = store.summary(args.session)
|
|
754
|
+
status = store.session_status(args.session)
|
|
755
|
+
except (OSError, ValueError) as exc:
|
|
756
|
+
print(f"cseq: {exc}", file=sys.stderr)
|
|
757
|
+
return 3
|
|
758
|
+
payload = {
|
|
759
|
+
"session": args.session,
|
|
760
|
+
"status": status,
|
|
761
|
+
"streamed": True,
|
|
762
|
+
"event_store": store_summary,
|
|
763
|
+
"source_logs": [str(x) for x in args.paths],
|
|
764
|
+
"event_count": count,
|
|
765
|
+
}
|
|
766
|
+
if args.as_json:
|
|
767
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
768
|
+
else:
|
|
769
|
+
print(f"session: {args.session}")
|
|
770
|
+
print(f"events: {count}")
|
|
771
|
+
print(f"status: {status}")
|
|
772
|
+
return 0
|
|
773
|
+
|
|
774
|
+
try:
|
|
775
|
+
session = build_trace_session(args.session, args.paths, pattern=pattern)
|
|
776
|
+
except (OSError, ValueError) as exc:
|
|
777
|
+
print(f"cseq: {exc}", file=sys.stderr)
|
|
778
|
+
return 3
|
|
779
|
+
address_evidence = []
|
|
780
|
+
if args.binary:
|
|
781
|
+
try:
|
|
782
|
+
image = load_binary_image(args.binary)
|
|
783
|
+
dwarf = load_dwarf_lines(args.dwarf) if args.dwarf else None
|
|
784
|
+
address_evidence = discover_runtime_address_evidence(
|
|
785
|
+
session.events, image, dwarf=dwarf, load_bias=args.load_bias, trusted_keys=args.address_key,
|
|
786
|
+
)
|
|
787
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
788
|
+
print(f"cseq: {exc}", file=sys.stderr)
|
|
789
|
+
return 3
|
|
790
|
+
elif args.dwarf:
|
|
791
|
+
print("cseq: --dwarf requires --binary for runtime address resolution", file=sys.stderr)
|
|
792
|
+
return 2
|
|
793
|
+
|
|
794
|
+
if args.store:
|
|
795
|
+
with EventStore(args.store) as store:
|
|
796
|
+
store.replace_session(session)
|
|
797
|
+
store_summary = store.summary(session.name)
|
|
798
|
+
else:
|
|
799
|
+
store_summary = None
|
|
800
|
+
payload = {
|
|
801
|
+
"session": session.name,
|
|
802
|
+
"event_store": store_summary,
|
|
803
|
+
"source_logs": session.source_logs,
|
|
804
|
+
"runtime_address_evidence": [
|
|
805
|
+
{
|
|
806
|
+
"event_index": e.event_index, "marker_id": e.marker_id,
|
|
807
|
+
"key": e.payload_key, "value": e.payload_value,
|
|
808
|
+
"runtime_address": e.runtime_address, "linked_address": e.linked_address,
|
|
809
|
+
"symbol": e.symbol_name, "symbol_size": e.symbol_size,
|
|
810
|
+
"source_file": e.source_file, "source_line": e.source_line,
|
|
811
|
+
"confidence": e.confidence, "provenance": e.provenance,
|
|
812
|
+
} for e in address_evidence
|
|
813
|
+
],
|
|
814
|
+
"events": [
|
|
815
|
+
{
|
|
816
|
+
"event_index": e.event_index,
|
|
817
|
+
"marker_id": e.marker_id,
|
|
818
|
+
"timestamp": e.timestamp_raw,
|
|
819
|
+
"cpu": e.cpu_hint,
|
|
820
|
+
"task": e.task_hint,
|
|
821
|
+
"payload": e.payload_dict(),
|
|
822
|
+
"raw": e.raw_line,
|
|
823
|
+
}
|
|
824
|
+
for e in session.events
|
|
825
|
+
],
|
|
826
|
+
}
|
|
827
|
+
if args.as_json:
|
|
828
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
829
|
+
else:
|
|
830
|
+
print(f"session: {session.name}")
|
|
831
|
+
print(f"events: {len(session.events)}")
|
|
832
|
+
for e in session.events:
|
|
833
|
+
print(f" {e.event_index} {e.cpu_hint or '?'} {e.marker_id} {e.message or ''}")
|
|
834
|
+
return 0
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def _explain(args: argparse.Namespace) -> int:
|
|
838
|
+
try:
|
|
839
|
+
index = Project(
|
|
840
|
+
args.path,
|
|
841
|
+
config=args.config,
|
|
842
|
+
configuration=args.configuration,
|
|
843
|
+
defines=tuple(args.define),
|
|
844
|
+
compile_db=getattr(args, "compile_db", None),
|
|
845
|
+
linker_map=getattr(args, "linker_map", None),
|
|
846
|
+
binary=getattr(args, "binary", None),
|
|
847
|
+
dwarf=getattr(args, "dwarf", None),
|
|
848
|
+
jobs=getattr(args, "jobs", 1),
|
|
849
|
+
).index()
|
|
850
|
+
except (OSError, RuntimeError) as exc:
|
|
851
|
+
print(f"cseq: {exc}", file=sys.stderr)
|
|
852
|
+
return 3
|
|
853
|
+
payload = explain_call(index, caller=args.caller, callee_text=args.callee)
|
|
854
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
855
|
+
return 0 if payload["found"] else 5
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def _query(args: argparse.Namespace) -> int:
|
|
859
|
+
try:
|
|
860
|
+
index = Project(
|
|
861
|
+
args.path,
|
|
862
|
+
config=args.config,
|
|
863
|
+
configuration=args.configuration,
|
|
864
|
+
defines=tuple(args.define),
|
|
865
|
+
compile_db=getattr(args, "compile_db", None),
|
|
866
|
+
linker_map=getattr(args, "linker_map", None),
|
|
867
|
+
binary=getattr(args, "binary", None),
|
|
868
|
+
dwarf=getattr(args, "dwarf", None),
|
|
869
|
+
jobs=getattr(args, "jobs", 1),
|
|
870
|
+
).index()
|
|
871
|
+
except (OSError, RuntimeError) as exc:
|
|
872
|
+
print(f"cseq: {exc}", file=sys.stderr)
|
|
873
|
+
return 3
|
|
874
|
+
if args.kind == "calls":
|
|
875
|
+
if not args.function:
|
|
876
|
+
print("cseq: query calls requires --function", file=sys.stderr); return 2
|
|
877
|
+
payload = query_calls(index, args.function)
|
|
878
|
+
elif args.kind == "unresolved":
|
|
879
|
+
payload = query_unresolved(index)
|
|
880
|
+
else:
|
|
881
|
+
if not args.start or not args.end:
|
|
882
|
+
print("cseq: query paths requires --start and --end", file=sys.stderr); return 2
|
|
883
|
+
payload = query_paths(index, args.start, args.end)
|
|
884
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
885
|
+
return 0
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def main(argv: list[str] | None = None) -> int:
|
|
889
|
+
parser = _build_parser()
|
|
890
|
+
args = parser.parse_args(argv)
|
|
891
|
+
return int(args.handler(args))
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
if __name__ == "__main__":
|
|
895
|
+
raise SystemExit(main())
|