Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions tooling/sync/docker_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@
"block_processing": "📦", "success": "🎉", "failed": "❌"
}

# Phase completion patterns for parsing sync logs
PHASE_COMPLETION_PATTERNS = {
"Block Headers": r"✓ BLOCK HEADERS complete: ([\d,]+) headers in (\d+:\d+:\d+)",
"Account Ranges": r"✓ ACCOUNT RANGES complete: ([\d,]+) accounts in (\d+:\d+:\d+)",
"Account Insertion": r"✓ ACCOUNT INSERTION complete: ([\d,]+) accounts inserted in (\d+:\d+:\d+)",
"Storage Ranges": r"✓ STORAGE RANGES complete: ([\d,]+) storage slots in (\d+:\d+:\d+)",
"Storage Insertion": r"✓ STORAGE INSERTION complete: ([\d,]+) storage slots inserted in (\d+:\d+:\d+)",
"State Healing": r"✓ STATE HEALING complete: ([\d,]+) state paths healed in (\d+:\d+:\d+)",
"Storage Healing": r"✓ STORAGE HEALING complete: ([\d,]+) storage accounts healed in (\d+:\d+:\d+)",
"Bytecodes": r"✓ BYTECODES complete: ([\d,]+) bytecodes in (\d+:\d+:\d+)",
Comment thread
pablodeymo marked this conversation as resolved.
Outdated
}


@dataclass
class Instance:
Expand Down Expand Up @@ -262,6 +274,30 @@ def rpc_call(url: str, method: str) -> Optional[Any]:
return None


def parse_phase_timings(run_id: str, container: str) -> list[tuple[str, str, str]]:
"""Parse phase completion times from saved container logs.

Returns list of (phase_name, count, duration) tuples.
"""
log_file = LOGS_DIR / f"run_{run_id}" / f"{container}.log"
if not log_file.exists():
return []

try:
logs = log_file.read_text()
except Exception:
return []

phases = []
for phase_name, pattern in PHASE_COMPLETION_PATTERNS.items():
match = re.search(pattern, logs)
if match:
count = match.group(1)
duration = match.group(2)
phases.append((phase_name, count, duration))
return phases


def slack_notify(run_id: str, run_count: int, instances: list, hostname: str, branch: str, commit: str, build_profile: str = ""):
"""Send a single summary Slack message for the run."""
all_success = all(i.status == "success" for i in instances)
Expand Down Expand Up @@ -319,6 +355,21 @@ def slack_notify(run_id: str, run_count: int, instances: list, hostname: str, br
if i.error:
line += f"\n Error: {i.error}"
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": line}})

# Add phase breakdown for each instance
for i in instances:
phases = parse_phase_timings(run_id, i.container)
if phases:
phase_lines = [f"📊 *Phase Breakdown — {i.name}*", "```"]
max_name_len = max(len(name) for name, _, _ in phases)
for name, count, duration in phases:
phase_lines.append(f"{name:<{max_name_len}} {duration} ({count})")
phase_lines.append("```")
blocks.append({
"type": "section",
"text": {"type": "mrkdwn", "text": "\n".join(phase_lines)}
})
Comment on lines +359 to +371

Copilot AI Feb 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phase breakdown sections are added in a separate loop (lines 360-371) from the instance status sections (lines 342-357). This means in the Slack notification, all instance statuses will be shown first, followed by all phase breakdowns. This differs from the text log format (lines 456-479) where each phase breakdown immediately follows its instance status. For better readability and consistency, consider moving the phase breakdown logic inside the first loop (after line 357) so each instance's breakdown appears immediately after its status, matching the text log format and the example in the PR description.

Copilot uses AI. Check for mistakes.

try:
requests.post(url, json={"blocks": blocks}, timeout=10)
except Exception:
Expand Down Expand Up @@ -417,6 +468,15 @@ def log_run_result(run_id: str, run_count: int, instances: list[Instance], hostn
if inst.error:
line += f"\n Error: {inst.error}"
lines.append(line)

# Add phase breakdown
phases = parse_phase_timings(run_id, inst.container)
if phases:
lines.append(f" Phase Breakdown:")
max_name_len = max(len(name) for name, _, _ in phases)
for name, count, duration in phases:
lines.append(f" {name:<{max_name_len}} {duration} ({count})")

lines.append("")
# Append to log file
with open(RUN_LOG_FILE, "a") as f:
Expand Down
Loading