From 8cc5c85c4fb3907e59cbd37231b048434fae6b20 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 13 Jan 2026 10:52:45 -0800 Subject: [PATCH 1/2] Fix extract_block_proposal_trace.sh Signed-off-by: Jacinta Ferrant --- signer/extract_block_proposal_trace.sh | 337 ++++++++++++-------- signer/extract_block_proposal_trace_fast.sh | 18 -- 2 files changed, 209 insertions(+), 146 deletions(-) delete mode 100755 signer/extract_block_proposal_trace_fast.sh diff --git a/signer/extract_block_proposal_trace.sh b/signer/extract_block_proposal_trace.sh index fdfc1b5..7015649 100755 --- a/signer/extract_block_proposal_trace.sh +++ b/signer/extract_block_proposal_trace.sh @@ -1,128 +1,209 @@ -#!/bin/bash -set -euo pipefail - -# Import all the helper functions -script_dir="$(cd "$(dirname "$0")" && pwd)" -source "$script_dir/../common.sh" - -# Read from file if provided, else stdin -if [[ -n "${1-}" && -f "$1" ]]; then - input_source="$1" -elif [ ! -t 0 ]; then - input_source="/dev/stdin" -else - echo "Usage: $0 or pipe log data into stdin" - exit 1 -fi - -# Clean up our garbage! -tmp_dir=$(mktemp -d) -trap "rm -rf $tmp_dir" EXIT - -# Track all signer_signature_hash's we encounter -all_signers_file="$tmp_dir/all_signers.txt" -touch "$all_signers_file" - -while IFS= read -r line; do - timestamp=$(extract_timestamp "$line") - [[ -z "$timestamp" ]] && continue - - # MAKE SURE YOU UPDATE THESE IF LOGGING HAS CHANGED... - if echo "$line" | grep -q "received a block proposal"; then - signer_hash=$(extract_signer_signature_hash "$line") - [[ -n "$signer_hash" ]] && { - save_time "$tmp_dir" proposal "$signer_hash" "$timestamp" - echo "$signer_hash" >> "$all_signers_file" - } - elif echo "$line" | grep -q "submitting block proposal for validation"; then - signer_hash=$(extract_signer_signature_hash "$line") - [[ -n "$signer_hash" ]] && save_time "$tmp_dir" validation "$signer_hash" "$timestamp" - elif echo "$line" | grep -q "Broadcasting block pre-commit to stacks node for"; then - signer_hash=$(echo "$line" | sed -n 's/.*for \([0-9a-fA-F]\{64\}\).*/\1/p') - [[ -n "$signer_hash" ]] && save_time "$tmp_dir" precommit "$signer_hash" "$timestamp" - elif echo "$line" | grep -q "block response to stacks node: Accepted"; then - signer_hash=$(extract_signer_signature_hash "$line") - [[ -n "$signer_hash" ]] && save_time "$tmp_dir" block_accepted "$signer_hash" "$timestamp" - elif echo "$line" | grep -q "block response to stacks node: Rejected"; then - signer_hash=$(extract_signer_signature_hash "$line") - [[ -n "$signer_hash" ]] && save_time "$tmp_dir" block_rejected "$signer_hash" "$timestamp" - elif echo "$line" | grep -q "Received block acceptance and have reached"; then - signer_hash=$(extract_signer_signature_hash "$line") - [[ -n "$signer_hash" ]] && save_time "$tmp_dir" global_approval "$signer_hash" "$timestamp" - elif echo "$line" | grep -q "Received block rejection and have reached"; then - signer_hash=$(extract_signer_signature_hash "$line") - [[ -n "$signer_hash" ]] && save_time "$tmp_dir" global_rejection "$signer_hash" "$timestamp" - #we should treat either a block pushed or a block new event as our Push time (take the earliest of the two) - elif echo "$line" | grep -q "Got block pushed message"; then - signer_hash=$(extract_signer_signature_hash "$line") - [[ -n "$signer_hash" ]] && maybe_save_earlier_time "$tmp_dir" push "$signer_hash" "$timestamp" - elif echo "$line" | grep -q "Received a new block event"; then - signer_hash=$(extract_signer_signature_hash "$line") - [[ -n "$signer_hash" ]] && maybe_save_earlier_time "$tmp_dir" push "$signer_hash" "$timestamp" - fi -done < "$input_source" - -# Deduplicate signer signature hashes -sort -u "$all_signers_file" -o "$all_signers_file" - -# Column headers -columns=("Signer Signature Hash" "Proposal" "Validation" "Pre-Commit" "Block Accepted" "Block Rejected" "Global Approval" "Global Rejection" "ΔProposal→Push (s)") - -# Fixed widths (adjusted for readability) -widths=(64 20 20 20 20 20 20 20 20) - -# Compute total table width -total_width=0 -for w in "${widths[@]}"; do - total_width=$((total_width + w)) -done -total_width=$((total_width + ${#widths[@]}*3 - 1)) # account for separators " | " - -# Print header -header_line="" -for i in "${!columns[@]}"; do - header_line+=$(printf "%-${widths[i]}s" "${columns[i]}") - [[ $i -lt $((${#columns[@]}-1)) ]] && header_line+=" | " -done -echo "$header_line" - -# Print separator -printf '%*s\n' "$total_width" '' | tr ' ' '-' - -# Initialize totals -total=0 -count=0 - -# Print data rows -while IFS= read -r signer_hash; do - prop=$(read_time "$tmp_dir" proposal "$signer_hash") - val=$(read_time "$tmp_dir" validation "$signer_hash") - pre=$(read_time "$tmp_dir" precommit "$signer_hash") - acc=$(read_time "$tmp_dir" block_accepted "$signer_hash") - rej=$(read_time "$tmp_dir" block_rejected "$signer_hash") - glob_app=$(read_time "$tmp_dir" global_approval "$signer_hash") - glob_rej=$(read_time "$tmp_dir" global_rejection "$signer_hash") - push=$(read_time "$tmp_dir" push "$signer_hash") - - if [[ "$prop" != "-" && "$push" != "-" ]]; then - delta=$(echo "$push - $prop" | bc -l) - delta_fmt=$(printf "%.3f" "$delta") - total=$(echo "$total + $delta_fmt" | bc -l) - ((count++)) - else - delta_fmt="N/A" - fi - - printf "%-${widths[0]}s | %-${widths[1]}s | %-${widths[2]}s | %-${widths[3]}s | %-${widths[4]}s | %-${widths[5]}s | %-${widths[6]}s | %-${widths[7]}s | %-${widths[8]}s\n" \ - "$signer_hash" "$prop" "$val" "$pre" "$acc" "$rej" "$glob_app" "$glob_rej" "$delta_fmt" -done < "$all_signers_file" - -# Print average -echo -if (( count > 0 )); then - avg=$(echo "$total / $count" | bc -l) - avg_fmt=$(printf "%.3f" "$avg") - echo "Average ΔProposal→Push (s): $avg_fmt" -else - echo "Average ΔProposal→Push (s): N/A (no complete proposal→push pairs)" -fi +#!/usr/bin/env python3 +import sys +import re +from datetime import datetime, timezone + +# ---------------------------- +# Config: patterns (update if logging changes) +# ---------------------------- +EVENT_PATTERNS = [ + ("proposal", "received a block proposal"), + ("validation", "submitting block proposal for validation"), + ("precommit", "Broadcasting block pre-commit to stacks node for"), + ("block_accepted", "block response to stacks node: Accepted"), + ("block_rejected", "block response to stacks node: Rejected"), + ("global_approval", "acceptance and have reached"), + ("global_rejection", "rejection and have reached"), + ("push", "Got block pushed message"), + ("push", "Received a new block event"), +] + +# ---------------------------- +# Regex helpers +# ---------------------------- +# Timestamp formats: +# [2026-01-12 07:27:37] +# [12345.678] (mac-style float seconds) +TS_HUMAN_RE = re.compile(r"\[(20\d{2}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]") +TS_MAC_RE = re.compile(r"\[(\d+\.\d+)\]") + +# signer_signature_hash: <64hex> OR signer_signature_hash=<64hex> +SIGNER_HASH_RE = re.compile(r"signer_signature_hash\s*[:=]\s*([0-9a-fA-F]{64})") + +# precommit line: "... for <64hex>" +PRECOMMIT_FOR_RE = re.compile(r"\bfor\s+([0-9a-fA-F]{64})\b") + +def parse_timestamp(line: str): + """ + Return timestamp as float seconds since epoch (or float seconds if mac format), + or None if no timestamp. + """ + m = TS_MAC_RE.search(line) + if m: + try: + return float(m.group(1)) + except ValueError: + return None + + m = TS_HUMAN_RE.search(line) + if not m: + return None + + ts_str = m.group(1) + # Interpret as local time? Your log is wall-clock; use naive->epoch as local. + # If you want strict UTC, adjust here. + try: + dt = datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S") + # Treat as local time (naive). Convert by timestamp() assuming local tz. + return dt.timestamp() + except Exception: + return None + +def extract_signer_hash(line: str): + m = SIGNER_HASH_RE.search(line) + return m.group(1) if m else None + +def extract_precommit_hash(line: str): + m = PRECOMMIT_FOR_RE.search(line) + return m.group(1) if m else None + +def set_time(times_by_event, event: str, h: str, ts: float): + # Keep first observed time for that event/hash (matches prior behavior) + d = times_by_event[event] + if h not in d: + d[h] = ts + +def set_earliest_time(times_by_event, event: str, h: str, ts: float): + # Keep earliest observed time (push event wants earliest of "new block" and "pushed") + d = times_by_event[event] + cur = d.get(h) + if cur is None or ts < cur: + d[h] = ts + +def fmt_ts(ts): + if ts is None: + return "-" + # Match previous output style: seconds since epoch (integer-ish) + # Keep as integer string for readability + return str(int(ts)) + +def main(): + # Input: file if provided, else stdin + if len(sys.argv) >= 2 and sys.argv[1] != "-" : + path = sys.argv[1] + f = open(path, "r", errors="replace") + else: + f = sys.stdin + + # event -> {hash -> ts} + times_by_event = { + "proposal": {}, + "validation": {}, + "precommit": {}, + "block_accepted": {}, + "block_rejected": {}, + "global_approval": {}, + "global_rejection": {}, + "push": {}, + } + + seen_hashes = set() + + # Precompute substring checks (fast) + # We’ll scan line and only do regex extraction when one of these substrings matches. + event_substrings = EVENT_PATTERNS + + for line in f: + ts = parse_timestamp(line) + if ts is None: + continue + + # Fast substring dispatch + for event, needle in event_substrings: + if needle in line: + if event == "precommit": + h = extract_precommit_hash(line) + else: + h = extract_signer_hash(line) + + if not h: + break + + seen_hashes.add(h) + + if event == "push": + set_earliest_time(times_by_event, "push", h, ts) + else: + set_time(times_by_event, event, h, ts) + break # only one event per line expected + + if f is not sys.stdin: + f.close() + + # Output formatting + columns = [ + "Signer Signature Hash", + "Proposal", + "Validation", + "Pre-Commit", + "Block Accepted", + "Block Rejected", + "Global Approval", + "Global Rejection", + "ΔProposal→Push (s)", + ] + widths = [64, 20, 20, 20, 20, 20, 20, 20, 20] + + # Header + header = " | ".join(f"{c:<{widths[i]}}" for i, c in enumerate(columns)) + print(header) + total_width = sum(widths) + (len(widths) * 3 - 1) + print("-" * total_width) + + total_delta = 0.0 + count = 0 + + for h in sorted(seen_hashes): + prop = times_by_event["proposal"].get(h) + val = times_by_event["validation"].get(h) + pre = times_by_event["precommit"].get(h) + acc = times_by_event["block_accepted"].get(h) + rej = times_by_event["block_rejected"].get(h) + ga = times_by_event["global_approval"].get(h) + gr = times_by_event["global_rejection"].get(h) + push = times_by_event["push"].get(h) + + if prop is not None and push is not None: + delta = push - prop + delta_fmt = f"{delta:.3f}" + total_delta += delta + count += 1 + else: + delta_fmt = "N/A" + + row = [ + h, + fmt_ts(prop), + fmt_ts(val), + fmt_ts(pre), + fmt_ts(acc), + fmt_ts(rej), + fmt_ts(ga), + fmt_ts(gr), + delta_fmt, + ] + print(" | ".join(f"{row[i]:<{widths[i]}}" for i in range(len(row)))) + + print() + if count > 0: + avg = total_delta / count + print(f"Average ΔProposal→Push (s): {avg:.3f}") + else: + print("Average ΔProposal→Push (s): N/A (no complete proposal→push pairs)") + +if __name__ == "__main__": + try: + main() + except BrokenPipeError: + # allow piping to head, etc. + sys.exit(0) diff --git a/signer/extract_block_proposal_trace_fast.sh b/signer/extract_block_proposal_trace_fast.sh deleted file mode 100755 index 61aa487..0000000 --- a/signer/extract_block_proposal_trace_fast.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Determine this script's directory -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# MAKE SURE YOU UPDATE THESE IF LOGGING HAS CHANGED... -patterns="received a block proposal|submitting block proposal for validation|Broadcasting block pre-commit to stacks node for|block response to stacks node:|Received block acceptance and have reached|Received block rejection and have reached|Got block pushed message|Received a new block event" - -# Read from file if provided, else stdin -if [[ $# -ge 1 && -f "$1" ]]; then - grep -E "$patterns" "$1" | "$script_dir/extract_block_proposal_trace.sh" -elif [ ! -t 0 ]; then - grep -E "$patterns" | "$script_dir/extract_block_proposal_trace.sh" -else - echo "Usage: $0 or pipe log data into stdin" - exit 1 -fi From 7a3a294a10d7a1d26d1958975ab3fdabda4b21a1 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 27 Jan 2026 14:17:49 -0800 Subject: [PATCH 2/2] Cleanup the filters to be faster and account for global approval and rejection times Signed-off-by: Jacinta Ferrant --- extract_rejection_logs_fast.sh | 22 ++++++ extract_rejection_reasons.sh | 21 ++++++ find_newblock_after_rejection.sh | 100 +++++++++++++++++++++++++ signer/extract_block_proposal_trace.sh | 100 +++++++++++++------------ 4 files changed, 194 insertions(+), 49 deletions(-) create mode 100755 extract_rejection_logs_fast.sh create mode 100755 extract_rejection_reasons.sh create mode 100755 find_newblock_after_rejection.sh diff --git a/extract_rejection_logs_fast.sh b/extract_rejection_logs_fast.sh new file mode 100755 index 0000000..831481e --- /dev/null +++ b/extract_rejection_logs_fast.sh @@ -0,0 +1,22 @@ +#!/bin/zsh + +# First, extract all rejection lines from signer.log into a temporary file +# This is much faster than running rg for each hash +rg "Broadcasting block response to stacks node: Rejected" ~/signer.log > /tmp/all_rejections.txt + +# Now read hashes and find matches +while IFS= read -r hash; do + # Search in the pre-filtered file + result=$(grep "$hash" /tmp/all_rejections.txt | head -1) + + # Print hash and result + if [ -n "$result" ]; then + echo "$hash | $result" + else + echo "$hash | " + fi +done < locally_rejected_hashes.txt > locally_rejected_hashes_with_logs.txt + +rm /tmp/all_rejections.txt +echo "Done! Results saved to locally_rejected_hashes_with_logs.txt" +wc -l locally_rejected_hashes_with_logs.txt diff --git a/extract_rejection_reasons.sh b/extract_rejection_reasons.sh new file mode 100755 index 0000000..98fd050 --- /dev/null +++ b/extract_rejection_reasons.sh @@ -0,0 +1,21 @@ +#!/bin/zsh + +# First, extract all rejection lines from signer.log into a temporary file +rg "Broadcasting block response to stacks node: Rejected" ~/signer.log > /tmp/all_rejections.txt + +# Now read hashes and find matches, extracting just the reason field +while IFS= read -r hash; do + # Search in the pre-filtered file and extract reason field using sed + result=$(grep "$hash" /tmp/all_rejections.txt | head -1 | sed -n 's/.*reason: "\([^"]*\)".*/\1/p') + + # Print hash and reason + if [ -n "$result" ]; then + echo "$hash | \"$result\"" + else + echo "$hash | " + fi +done < locally_rejected_hashes.txt > locally_rejected_hashes_with_logs.txt + +rm /tmp/all_rejections.txt +echo "Done! Results saved to locally_rejected_hashes_with_logs.txt" +wc -l locally_rejected_hashes_with_logs.txt diff --git a/find_newblock_after_rejection.sh b/find_newblock_after_rejection.sh new file mode 100755 index 0000000..46761d0 --- /dev/null +++ b/find_newblock_after_rejection.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +# Script to find NewBlock events that occur AFTER block rejections for each signer_sighash +# Usage: ./find_newblock_after_rejection.sh + +SIGNER_LOG="$HOME/signer.log" +INPUT_FILE="locally_rejected_hashes_with_logs.txt" +OUTPUT_FILE="newblock_after_rejection.txt" + +# Check if files exist +if [[ ! -f "$SIGNER_LOG" ]]; then + echo "Error: $SIGNER_LOG not found" + exit 1 +fi + +if [[ ! -f "$INPUT_FILE" ]]; then + echo "Error: $INPUT_FILE not found" + exit 1 +fi + +# Clear output file +> "$OUTPUT_FILE" + +echo "Processing signer signature hashes..." +echo "" + +# Counter for progress +count=0 +total=$(wc -l < "$INPUT_FILE") + +# Read each line from the input file +while IFS='|' read -r sighash reason; do + # Trim whitespace + sighash=$(echo "$sighash" | xargs) + reason=$(echo "$reason" | xargs) + + ((count++)) + echo -n "Processing $count/$total: $sighash..." + + # Use rg to find all lines with this sighash, then filter for NewBlock and Rejected + all_events=$(rg -F -e "$sighash" "$SIGNER_LOG" | grep -e "Processing event: Some(NewBlock" -e "Broadcasting block response to stacks node: Rejected") + + if [[ -z "$all_events" ]]; then + echo " (not found)" + echo "=== $sighash ===" >> "$OUTPUT_FILE" + echo "Rejection reason: $reason" >> "$OUTPUT_FILE" + echo "ERROR: No events found in signer.log" >> "$OUTPUT_FILE" + echo "" >> "$OUTPUT_FILE" + echo "----------------------------------------" >> "$OUTPUT_FILE" + echo "" >> "$OUTPUT_FILE" + continue + fi + + # Find the rejection line + rejection_found=$(echo "$all_events" | grep -n "Broadcasting block response to stacks node: Rejected" | head -1) + + if [[ -z "$rejection_found" ]]; then + echo " (no rejection found)" + echo "=== $sighash ===" >> "$OUTPUT_FILE" + echo "Rejection reason: $reason" >> "$OUTPUT_FILE" + echo "ERROR: No rejection found" >> "$OUTPUT_FILE" + echo "" >> "$OUTPUT_FILE" + echo "----------------------------------------" >> "$OUTPUT_FILE" + echo "" >> "$OUTPUT_FILE" + continue + fi + + # Get the line number of the rejection within the filtered events + rejection_line_num=$(echo "$rejection_found" | cut -d: -f1) + + # Get NewBlock events that appear AFTER the rejection + newblock_events=$(echo "$all_events" | tail -n +$((rejection_line_num + 1)) | grep "Processing event: Some(NewBlock") + + # Write results + echo "=== $sighash ===" >> "$OUTPUT_FILE" + echo "Rejection reason: $reason" >> "$OUTPUT_FILE" + echo "" >> "$OUTPUT_FILE" + echo "Rejection:" >> "$OUTPUT_FILE" + echo "$rejection_found" | cut -d: -f2- >> "$OUTPUT_FILE" + echo "" >> "$OUTPUT_FILE" + + if [[ -z "$newblock_events" ]]; then + echo "No NewBlock events found after rejection" >> "$OUTPUT_FILE" + echo " (no NewBlock after)" + else + echo "NewBlock events after rejection:" >> "$OUTPUT_FILE" + echo "$newblock_events" >> "$OUTPUT_FILE" + newblock_count=$(echo "$newblock_events" | wc -l) + echo " (found $newblock_count)" + fi + + echo "" >> "$OUTPUT_FILE" + echo "----------------------------------------" >> "$OUTPUT_FILE" + echo "" >> "$OUTPUT_FILE" + +done < "$INPUT_FILE" + +echo "" +echo "Done! Results written to $OUTPUT_FILE" +echo "Total: $count hashes" diff --git a/signer/extract_block_proposal_trace.sh b/signer/extract_block_proposal_trace.sh index 7015649..17f8930 100755 --- a/signer/extract_block_proposal_trace.sh +++ b/signer/extract_block_proposal_trace.sh @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import sys import re -from datetime import datetime, timezone +from datetime import datetime # ---------------------------- # Config: patterns (update if logging changes) @@ -12,8 +12,8 @@ EVENT_PATTERNS = [ ("precommit", "Broadcasting block pre-commit to stacks node for"), ("block_accepted", "block response to stacks node: Accepted"), ("block_rejected", "block response to stacks node: Rejected"), - ("global_approval", "acceptance and have reached"), - ("global_rejection", "rejection and have reached"), + ("global_approval", "Received block acceptance and have reached"), + ("global_rejection", "Received block rejection and have reached"), ("push", "Got block pushed message"), ("push", "Received a new block event"), ] @@ -67,36 +67,29 @@ def extract_precommit_hash(line: str): m = PRECOMMIT_FOR_RE.search(line) return m.group(1) if m else None -def set_time(times_by_event, event: str, h: str, ts: float): - # Keep first observed time for that event/hash (matches prior behavior) - d = times_by_event[event] +def set_first(d, h, ts): + """Set first observed timestamp only.""" if h not in d: d[h] = ts -def set_earliest_time(times_by_event, event: str, h: str, ts: float): - # Keep earliest observed time (push event wants earliest of "new block" and "pushed") - d = times_by_event[event] +def set_earliest(d, h, ts): + """Keep earliest timestamp.""" cur = d.get(h) if cur is None or ts < cur: d[h] = ts def fmt_ts(ts): - if ts is None: - return "-" - # Match previous output style: seconds since epoch (integer-ish) - # Keep as integer string for readability - return str(int(ts)) + return "-" if ts is None else str(int(ts)) def main(): # Input: file if provided, else stdin - if len(sys.argv) >= 2 and sys.argv[1] != "-" : - path = sys.argv[1] - f = open(path, "r", errors="replace") + if len(sys.argv) >= 2 and sys.argv[1] != "-": + f = open(sys.argv[1], "r", errors="replace") else: f = sys.stdin # event -> {hash -> ts} - times_by_event = { + times = { "proposal": {}, "validation": {}, "precommit": {}, @@ -107,35 +100,43 @@ def main(): "push": {}, } - seen_hashes = set() - - # Precompute substring checks (fast) - # We’ll scan line and only do regex extraction when one of these substrings matches. - event_substrings = EVENT_PATTERNS + seen = set() for line in f: ts = parse_timestamp(line) if ts is None: continue - # Fast substring dispatch - for event, needle in event_substrings: - if needle in line: - if event == "precommit": - h = extract_precommit_hash(line) - else: - h = extract_signer_hash(line) + # Allow multiple events per line (no early break) + for event, needle in EVENT_PATTERNS: + if needle not in line: + continue + + if event == "precommit": + h = extract_precommit_hash(line) + else: + h = extract_signer_hash(line) + + if not h: + continue + + seen.add(h) + + if event == "push": + # Push time = earliest of pushed/new-block events + set_earliest(times["push"], h, ts) - if not h: - break + # Global Approval time should also be populated by push lines. + # Take the earliest between actual global-approval threshold and push visibility. + set_earliest(times["global_approval"], h, ts) - seen_hashes.add(h) + elif event == "global_approval": + # Global Approval = earliest (not first), because we might see multiple. + set_earliest(times["global_approval"], h, ts) - if event == "push": - set_earliest_time(times_by_event, "push", h, ts) - else: - set_time(times_by_event, event, h, ts) - break # only one event per line expected + else: + # All other events: first time observed is good enough + set_first(times[event], h, ts) if f is not sys.stdin: f.close() @@ -150,11 +151,11 @@ def main(): "Block Rejected", "Global Approval", "Global Rejection", + "Push", "ΔProposal→Push (s)", ] - widths = [64, 20, 20, 20, 20, 20, 20, 20, 20] + widths = [64, 20, 20, 20, 20, 20, 20, 20, 20, 20] - # Header header = " | ".join(f"{c:<{widths[i]}}" for i, c in enumerate(columns)) print(header) total_width = sum(widths) + (len(widths) * 3 - 1) @@ -163,15 +164,15 @@ def main(): total_delta = 0.0 count = 0 - for h in sorted(seen_hashes): - prop = times_by_event["proposal"].get(h) - val = times_by_event["validation"].get(h) - pre = times_by_event["precommit"].get(h) - acc = times_by_event["block_accepted"].get(h) - rej = times_by_event["block_rejected"].get(h) - ga = times_by_event["global_approval"].get(h) - gr = times_by_event["global_rejection"].get(h) - push = times_by_event["push"].get(h) + for h in sorted(seen): + prop = times["proposal"].get(h) + val = times["validation"].get(h) + pre = times["precommit"].get(h) + acc = times["block_accepted"].get(h) + rej = times["block_rejected"].get(h) + ga = times["global_approval"].get(h) + gr = times["global_rejection"].get(h) + push = times["push"].get(h) if prop is not None and push is not None: delta = push - prop @@ -190,6 +191,7 @@ def main(): fmt_ts(rej), fmt_ts(ga), fmt_ts(gr), + fmt_ts(push), delta_fmt, ] print(" | ".join(f"{row[i]:<{widths[i]}}" for i in range(len(row))))