Show bytes output counters

This commit is contained in:
Bill Thiede 2025-12-04 17:23:44 -08:00
parent c851648166
commit 171fdb4329

View File

@ -37,6 +37,7 @@ class PerformanceResult:
part: int
time_ns: int # Runner time in nanoseconds
generator_time_ns: int = 0 # Generator time in nanoseconds (optional)
output_bytes: int = 0 # Number of bytes in the output/answer
git_rev: str = "" # Git revision (short hash)
repo_url: str = "" # Repository URL
timestamp: str = ""
@ -121,6 +122,7 @@ class Database:
part INTEGER NOT NULL,
time_ns INTEGER NOT NULL,
generator_time_ns INTEGER NOT NULL DEFAULT 0,
output_bytes INTEGER NOT NULL DEFAULT 0,
git_rev TEXT NOT NULL DEFAULT '',
repo_url TEXT NOT NULL DEFAULT '',
timestamp TEXT NOT NULL,
@ -131,6 +133,7 @@ class Database:
# Add new columns if they don't exist (for existing databases)
for column, col_type in [
('generator_time_ns', 'INTEGER NOT NULL DEFAULT 0'),
('output_bytes', 'INTEGER NOT NULL DEFAULT 0'),
('git_rev', 'TEXT NOT NULL DEFAULT \'\''),
('repo_url', 'TEXT NOT NULL DEFAULT \'\'')
]:
@ -156,10 +159,10 @@ class Database:
try:
cursor.execute('''
INSERT OR REPLACE INTO results
(user, year, day, part, time_ns, generator_time_ns, git_rev, repo_url, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
(user, year, day, part, time_ns, generator_time_ns, output_bytes, git_rev, repo_url, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (result.user, result.year, result.day, result.part,
result.time_ns, result.generator_time_ns, result.git_rev,
result.time_ns, result.generator_time_ns, result.output_bytes, result.git_rev,
result.repo_url, result.timestamp))
conn.commit()
except sqlite3.IntegrityError:
@ -178,7 +181,7 @@ class Database:
cursor = conn.cursor()
query = '''
SELECT user, year, day, part, time_ns, generator_time_ns, git_rev, repo_url, timestamp
SELECT user, year, day, part, time_ns, generator_time_ns, output_bytes, git_rev, repo_url, timestamp
FROM results r1
WHERE timestamp = (
SELECT MAX(timestamp)
@ -220,9 +223,10 @@ class Database:
'part': row[3],
'time_ns': row[4],
'generator_time_ns': row[5] if len(row) > 5 else 0,
'git_rev': row[6] if len(row) > 6 else '',
'repo_url': row[7] if len(row) > 7 else '',
'timestamp': row[8] if len(row) > 8 else (row[6] if len(row) > 6 else '')
'output_bytes': row[6] if len(row) > 6 else 0,
'git_rev': row[7] if len(row) > 7 else '',
'repo_url': row[8] if len(row) > 8 else '',
'timestamp': row[9] if len(row) > 9 else (row[7] if len(row) > 7 else '')
}
for row in rows
]
@ -785,9 +789,12 @@ class CargoAOCRunner:
stdout_clean = CargoAOCRunner._strip_ansi_codes(result.stdout or "")
stderr_clean = CargoAOCRunner._strip_ansi_codes(result.stderr or "")
# Count bytes in stdout output (original, before ANSI stripping)
output_bytes = len(result.stdout.encode('utf-8')) if result.stdout else 0
# Parse output for runtime information
day_results = CargoAOCRunner._parse_runtime_output(
stdout_clean, stderr_clean, day, year, user, git_rev, repo_url
stdout_clean, stderr_clean, day, year, user, git_rev, repo_url, output_bytes
)
if day_results:
logger.info(f"Parsed {len(day_results)} runtime result(s) for {user} year {year} day {day}")
@ -816,7 +823,7 @@ class CargoAOCRunner:
@staticmethod
def _parse_runtime_output(stdout: str, stderr: str, day: int, year: int,
user: str, git_rev: str = "", repo_url: str = "") -> List[PerformanceResult]:
user: str, git_rev: str = "", repo_url: str = "", output_bytes: int = 0) -> List[PerformanceResult]:
"""Parse cargo-aoc runtime output
cargo aoc typically outputs timing information like:
@ -869,6 +876,7 @@ class CargoAOCRunner:
# First, try to parse the generator/runner format which is most common
# Look for "Day X - Part Y" lines and extract both generator and runner times
# output_bytes parameter contains the total stdout bytes for this day run
lines = output.split('\n')
current_day = None
current_part = None
@ -889,6 +897,7 @@ class CargoAOCRunner:
part=current_part,
time_ns=runner_time_ns,
generator_time_ns=generator_time_ns,
output_bytes=output_bytes,
git_rev=git_rev,
repo_url=repo_url,
timestamp=timestamp
@ -933,6 +942,7 @@ class CargoAOCRunner:
part=current_part,
time_ns=runner_time_ns,
generator_time_ns=generator_time_ns,
output_bytes=output_bytes,
git_rev=git_rev,
repo_url=repo_url,
timestamp=timestamp
@ -994,6 +1004,7 @@ class CargoAOCRunner:
day=actual_day,
part=part_num,
time_ns=time_ns,
output_bytes=output_bytes,
timestamp=timestamp
))
except ValueError:
@ -1023,6 +1034,7 @@ class CargoAOCRunner:
day=day,
part=1,
time_ns=time_ns,
output_bytes=output_bytes,
timestamp=timestamp
))
elif len(matches) == 2:
@ -1036,6 +1048,7 @@ class CargoAOCRunner:
day=day,
part=idx,
time_ns=time_ns,
output_bytes=output_bytes,
timestamp=timestamp
))
break
@ -1100,6 +1113,7 @@ class HTMLGenerator:
user = result['user']
runner_time_ns = result['time_ns']
generator_time_ns = result.get('generator_time_ns', 0)
output_bytes = result.get('output_bytes', 0)
git_rev = result.get('git_rev', '')
repo_url = result.get('repo_url', '')
@ -1111,11 +1125,12 @@ class HTMLGenerator:
'total': total_time_ns,
'runner': runner_time_ns,
'generator': generator_time_ns,
'output_bytes': output_bytes,
'git_rev': git_rev,
'repo_url': repo_url
}
html = self._generate_html(data, years, users, db, config)
html = self._generate_html(data, years, users, db, config, results)
output_file = self.output_dir / 'index.html'
with open(output_file, 'w') as f:
@ -1286,7 +1301,7 @@ class HTMLGenerator:
html += '</div>'
return html
def _generate_html(self, data: dict, years: List[int], users: List[str], db: Database, config: Config) -> str:
def _generate_html(self, data: dict, years: List[int], users: List[str], db: Database, config: Config, results: List[dict]) -> str:
"""Generate HTML content"""
# Get refresh interval from config (default 5 minutes = 300 seconds)
refresh_interval = config.config.get('html_refresh_interval', 300)
@ -1944,6 +1959,60 @@ class HTMLGenerator:
</div>
"""
# Add output bytes summary table
# Use the results passed to the method
html += """
<div class="summary">
<h3>Output Bytes Summary</h3>
<p style="font-size: 0.9em; color: #666; margin-bottom: 10px;">Number of bytes written to stdout for each day/part/user combination</p>
<table>
<thead>
<tr>
<th>Year</th>
<th>Day</th>
<th>Part</th>
"""
for user in sorted(users):
html += f" <th>{user}</th>\n"
html += """ </tr>
</thead>
<tbody>
"""
# Organize output bytes by year/day/part/user
output_bytes_data = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
for result in results: # results is passed as parameter to _generate_html
year = result['year']
day = result['day']
part = result['part']
user = result['user']
output_bytes = result.get('output_bytes', 0)
# Store output_bytes even if 0, but we'll show "-" for 0 in the table
output_bytes_data[year][day][part][user] = output_bytes
# Generate table rows - use the same structure as main data tables
for year in sorted_years:
if year not in data:
continue
for day in sorted(data[year].keys()):
for part in sorted(data[year][day].keys()):
html += f""" <tr>
<td>{year}</td>
<td>{day}</td>
<td>{part}</td>
"""
for user in sorted(users):
bytes_val = output_bytes_data[year][day][part].get(user, None)
if bytes_val is not None and bytes_val > 0:
html += f" <td>{bytes_val:,}</td>\n"
else:
html += " <td>-</td>\n"
html += " </tr>\n"
html += """ </tbody>
</table>
</div>
"""
# Add summary statistics at the bottom
html += f"""
<div class="summary">