Captured from feature/tracing-migrationf510940(rebased on master8284509), same Jaeger / request set / DB snapshot procedure as pre-migration. Result: names + semantic attributes preserved, traceparent intact, no orphaned spans, async/streaming paths now single traces. See post-migration/README.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
73523c6796
commit
e754e149d7
6 files changed
+1272
No files matched your search
@@ -0,0 +1,149 @@
|
||||
"""#143 parity analysis: after-capture Jaeger dump vs the #137 baseline.
|
||||
|
||||
Writes span-inventory.json (baseline schema + nesting columns) and prints a
|
||||
before/after diff plus targeted nesting / propagation checks.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
W = sys.argv[1]
|
||||
after = json.load(open(f"{W}/traces-after.json"))["data"]
|
||||
before_inv = {r["operation"]: r for r in json.load(open(f"{W}/inventory-before.json"))}
|
||||
|
||||
# Attributes tracing-opentelemetry adds to every span (location / thread /
|
||||
# timing metadata). Reported separately so the diff shows semantic changes.
|
||||
TRACING_EXTRAS = {
|
||||
"busy_ns", "idle_ns", "target", "thread.id", "thread.name",
|
||||
"code.file.path", "code.line.number", "code.module.name",
|
||||
}
|
||||
# Spans produced by my auth probes, not by the baseline request set.
|
||||
PROBE_NOISE = set()
|
||||
|
||||
spans = {}
|
||||
by_trace = defaultdict(list)
|
||||
for t in after:
|
||||
for s in t["spans"]:
|
||||
s["_tags"] = {kv["key"]: kv["value"] for kv in s["tags"]}
|
||||
spans[s["spanID"]] = s
|
||||
by_trace[s["traceID"]].append(s)
|
||||
|
||||
|
||||
def parent(s):
|
||||
for r in s.get("references", []):
|
||||
if r["refType"] == "CHILD_OF":
|
||||
return r["spanID"], r["traceID"]
|
||||
return None, None
|
||||
|
||||
|
||||
def op(sid):
|
||||
return spans[sid]["operationName"] if sid in spans else None
|
||||
|
||||
|
||||
# Inventory in the baseline's schema, plus nesting columns.
|
||||
inv = defaultdict(lambda: {"span_count": 0, "traces": set(), "kinds": set(), "scopes": set(),
|
||||
"statuses": set(), "keys": set(), "parents": defaultdict(int),
|
||||
"orphans": 0})
|
||||
for s in spans.values():
|
||||
r = inv[s["operationName"]]
|
||||
r["span_count"] += 1
|
||||
r["traces"].add(s["traceID"])
|
||||
r["kinds"].add(s["_tags"].get("span.kind", "internal"))
|
||||
r["scopes"].add(s["_tags"].get("otel.scope.name", "?"))
|
||||
r["statuses"].add(s["_tags"].get("otel.status_code", "(none)"))
|
||||
r["keys"].update(s["_tags"])
|
||||
pid, _ = parent(s)
|
||||
if pid is None:
|
||||
r["orphans"] += 1
|
||||
else:
|
||||
r["parents"][op(pid) or "(remote)"] += 1
|
||||
|
||||
inventory = []
|
||||
for name, r in sorted(inv.items(), key=lambda kv: -kv[1]["span_count"]):
|
||||
inventory.append({
|
||||
"operation": name,
|
||||
"span_count": r["span_count"],
|
||||
"trace_count": len(r["traces"]),
|
||||
"kind": "/".join(sorted(r["kinds"])),
|
||||
"scope": "/".join(sorted(r["scopes"])),
|
||||
"status": "/".join(sorted(r["statuses"])),
|
||||
"attribute_keys": sorted(r["keys"] - TRACING_EXTRAS),
|
||||
"parents": dict(r["parents"]),
|
||||
"root_count": r["orphans"],
|
||||
})
|
||||
json.dump(inventory, open(f"{W}/span-inventory.json", "w"), indent=2)
|
||||
|
||||
print("== Operations: before -> after ==")
|
||||
before_ops, after_ops = set(before_inv), set(inv)
|
||||
print("only before:", sorted(before_ops - after_ops))
|
||||
print("only after: ", sorted(after_ops - before_ops))
|
||||
print()
|
||||
print("== Common operations: kind / scope / attribute-key changes ==")
|
||||
for name in sorted(before_ops & after_ops):
|
||||
b, a = before_inv[name], next(x for x in inventory if x["operation"] == name)
|
||||
bk, ak = set(b["attribute_keys"]), set(a["attribute_keys"])
|
||||
changes = []
|
||||
if b["kind"] != a["kind"]:
|
||||
changes.append(f"kind {b['kind']}->{a['kind']}")
|
||||
if b["scope"] != a["scope"]:
|
||||
changes.append(f"scope {b['scope']}->{a['scope']}")
|
||||
if bk - ak:
|
||||
changes.append(f"LOST {sorted(bk - ak)}")
|
||||
if ak - bk:
|
||||
changes.append(f"added {sorted(ak - bk)}")
|
||||
nest = f"traces {b['trace_count']}/{b['span_count']} -> {a['trace_count']}/{a['span_count']}, roots={a['root_count']}"
|
||||
print(f"{name}: {'; '.join(changes) or 'same'} | {nest}")
|
||||
|
||||
print()
|
||||
print("== Roots in the after capture (span with no parent) ==")
|
||||
for s in spans.values():
|
||||
if parent(s)[0] is None:
|
||||
print(f" {s['operationName']} trace={s['traceID'][:8]} spans_in_trace={len(by_trace[s['traceID']])}")
|
||||
|
||||
|
||||
def chain(s):
|
||||
out = [s["operationName"]]
|
||||
pid, _ = parent(s)
|
||||
while pid:
|
||||
if pid not in spans:
|
||||
out.append(f"(remote {pid})")
|
||||
break
|
||||
out.append(spans[pid]["operationName"])
|
||||
pid, _ = parent(spans[pid])
|
||||
return " <- ".join(out)
|
||||
|
||||
|
||||
print()
|
||||
print("== Ancestry of key spans ==")
|
||||
seen = set()
|
||||
for s in spans.values():
|
||||
n = s["operationName"]
|
||||
key = (n, s["_tags"].get("llm.operation"))
|
||||
if n in ("llm.request", "ai.agentic.loop", "ai.insight.generate_agentic",
|
||||
"db.insert.store_insight", "db.update.update_training_messages",
|
||||
"ai.chat.turn.stream", "list_photos", "list_memories",
|
||||
"generate_video_thumbnail") and key not in seen:
|
||||
seen.add(key)
|
||||
extra = f" [llm.operation={key[1]}]" if key[1] else ""
|
||||
print(f" {chain(s)}{extra}")
|
||||
|
||||
print()
|
||||
print("== traceparent probe ==")
|
||||
probe = [s for s in spans.values() if s["traceID"].startswith("11111111")]
|
||||
for s in sorted(probe, key=lambda s: s["startTime"]):
|
||||
print(f" {chain(s)} trace={s['traceID']}")
|
||||
|
||||
print()
|
||||
print("== llm.request attribute presence ==")
|
||||
for s in spans.values():
|
||||
if s["operationName"] == "llm.request":
|
||||
t = s["_tags"]
|
||||
print(f" op={t.get('llm.operation')} model={t.get('llm.model')} provider={t.get('llm.provider')} "
|
||||
f"status={t.get('http.status_code')} tokens_in={t.get('llm.tokens.input')} "
|
||||
f"parent={op(parent(s)[0])}")
|
||||
print()
|
||||
print("== turn / insight spans: llm.backend + llm.model ==")
|
||||
for s in spans.values():
|
||||
if s["operationName"] in ("ai.chat.turn.stream", "ai.insight.generate_agentic"):
|
||||
t = s["_tags"]
|
||||
print(f" {s['operationName']}: llm.backend={t.get('llm.backend')} llm.model={t.get('llm.model')} backend={t.get('backend')} model={t.get('model')}")
|
||||
Reference in new issue
Block a user