Draw graph for performance history
This commit is contained in:
parent
4503246bcc
commit
65dc9a4d12
120
aocsync.py
120
aocsync.py
@ -832,6 +832,97 @@ class HTMLGenerator:
|
|||||||
|
|
||||||
logger.info(f"Generated HTML report: {output_file}")
|
logger.info(f"Generated HTML report: {output_file}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_svg_graph(data_points: List[dict]) -> str:
|
||||||
|
"""Generate an SVG line graph showing performance over time"""
|
||||||
|
if len(data_points) < 2:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Graph dimensions
|
||||||
|
width = 600
|
||||||
|
height = 200
|
||||||
|
padding = 40
|
||||||
|
graph_width = width - 2 * padding
|
||||||
|
graph_height = height - 2 * padding
|
||||||
|
|
||||||
|
# Extract time values
|
||||||
|
times = [dp['time_ns'] for dp in data_points]
|
||||||
|
min_time = min(times)
|
||||||
|
max_time = max(times)
|
||||||
|
# Add 10% padding to range for better visualization, or minimum 1% of max value
|
||||||
|
time_range = max_time - min_time if max_time > min_time else max(max_time * 0.01, 1)
|
||||||
|
if time_range > 0:
|
||||||
|
padding_amount = time_range * 0.1
|
||||||
|
min_time = max(0, min_time - padding_amount)
|
||||||
|
max_time = max_time + padding_amount
|
||||||
|
time_range = max_time - min_time
|
||||||
|
|
||||||
|
# Format time for display
|
||||||
|
def format_time(ns):
|
||||||
|
ms = ns / 1_000_000
|
||||||
|
us = ns / 1_000
|
||||||
|
if ms >= 1:
|
||||||
|
return f"{ms:.2f}ms"
|
||||||
|
elif us >= 1:
|
||||||
|
return f"{us:.2f}μs"
|
||||||
|
else:
|
||||||
|
return f"{ns}ns"
|
||||||
|
|
||||||
|
# Generate SVG
|
||||||
|
svg_parts = []
|
||||||
|
svg_parts.append(f'<svg width="{width}" height="{height}" style="border: 1px solid #ddd; background: #fafafa;">')
|
||||||
|
|
||||||
|
# Draw axes
|
||||||
|
svg_parts.append(f'<line x1="{padding}" y1="{padding}" x2="{padding}" y2="{height - padding}" stroke="#333" stroke-width="2"/>') # Y-axis
|
||||||
|
svg_parts.append(f'<line x1="{padding}" y1="{height - padding}" x2="{width - padding}" y2="{height - padding}" stroke="#333" stroke-width="2"/>') # X-axis
|
||||||
|
|
||||||
|
# Draw grid lines and labels
|
||||||
|
num_grid_lines = 5
|
||||||
|
for i in range(num_grid_lines + 1):
|
||||||
|
y_pos = padding + (graph_height * i / num_grid_lines)
|
||||||
|
time_val = max_time - (time_range * i / num_grid_lines)
|
||||||
|
|
||||||
|
# Grid line
|
||||||
|
if i < num_grid_lines:
|
||||||
|
svg_parts.append(f'<line x1="{padding}" y1="{y_pos}" x2="{width - padding}" y2="{y_pos}" stroke="#e0e0e0" stroke-width="1"/>')
|
||||||
|
|
||||||
|
# Y-axis label
|
||||||
|
svg_parts.append(f'<text x="{padding - 5}" y="{y_pos + 4}" text-anchor="end" font-size="10" fill="#666">{format_time(time_val)}</text>')
|
||||||
|
|
||||||
|
# Draw data points and line
|
||||||
|
points = []
|
||||||
|
for i, dp in enumerate(data_points):
|
||||||
|
x = padding + (graph_width * i / (len(data_points) - 1)) if len(data_points) > 1 else padding
|
||||||
|
y = padding + graph_height - (graph_height * (dp['time_ns'] - min_time) / time_range)
|
||||||
|
points.append((x, y))
|
||||||
|
|
||||||
|
# Draw line connecting points
|
||||||
|
if len(points) > 1:
|
||||||
|
path_d = f"M {points[0][0]} {points[0][1]}"
|
||||||
|
for x, y in points[1:]:
|
||||||
|
path_d += f" L {x} {y}"
|
||||||
|
svg_parts.append(f'<path d="{path_d}" fill="none" stroke="#667eea" stroke-width="2"/>')
|
||||||
|
|
||||||
|
# Draw points
|
||||||
|
for x, y in points:
|
||||||
|
svg_parts.append(f'<circle cx="{x}" cy="{y}" r="4" fill="#667eea" stroke="#fff" stroke-width="2"/>')
|
||||||
|
|
||||||
|
# X-axis labels (show first, middle, last)
|
||||||
|
if len(data_points) > 0:
|
||||||
|
indices_to_label = [0]
|
||||||
|
if len(data_points) > 2:
|
||||||
|
indices_to_label.append(len(data_points) // 2)
|
||||||
|
if len(data_points) > 1:
|
||||||
|
indices_to_label.append(len(data_points) - 1)
|
||||||
|
|
||||||
|
for idx in indices_to_label:
|
||||||
|
x = padding + (graph_width * idx / (len(data_points) - 1)) if len(data_points) > 1 else padding
|
||||||
|
date_str = data_points[idx]['date'][:10] if data_points[idx]['date'] else ''
|
||||||
|
svg_parts.append(f'<text x="{x}" y="{height - padding + 15}" text-anchor="middle" font-size="9" fill="#666">{date_str}</text>')
|
||||||
|
|
||||||
|
svg_parts.append('</svg>')
|
||||||
|
return ''.join(svg_parts)
|
||||||
|
|
||||||
def _generate_html(self, data: dict, years: List[int], users: List[str], db: Database) -> str:
|
def _generate_html(self, data: dict, years: List[int], users: List[str], db: Database) -> str:
|
||||||
"""Generate HTML content"""
|
"""Generate HTML content"""
|
||||||
# Sort years descending (most recent first)
|
# Sort years descending (most recent first)
|
||||||
@ -1119,6 +1210,13 @@ class HTMLGenerator:
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
.history-graph {{
|
||||||
|
margin: 15px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
}}
|
||||||
|
|
||||||
.summary {{
|
.summary {{
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@ -1347,7 +1445,8 @@ class HTMLGenerator:
|
|||||||
history_link = ""
|
history_link = ""
|
||||||
if len(historical) > 1:
|
if len(historical) > 1:
|
||||||
history_items = []
|
history_items = []
|
||||||
for hist in historical[:10]: # Show last 10 runs
|
hist_data_points = []
|
||||||
|
for hist in historical[:20]: # Show last 20 runs for graph
|
||||||
hist_total = hist['time_ns'] + hist.get('generator_time_ns', 0)
|
hist_total = hist['time_ns'] + hist.get('generator_time_ns', 0)
|
||||||
hist_ms = hist_total / 1_000_000
|
hist_ms = hist_total / 1_000_000
|
||||||
hist_us = hist_total / 1_000
|
hist_us = hist_total / 1_000
|
||||||
@ -1367,6 +1466,16 @@ class HTMLGenerator:
|
|||||||
hist_git_link = hist_git
|
hist_git_link = hist_git
|
||||||
history_items.append(f'<div class="history-item">{hist_date}: {hist_time_str} ({hist_git_link})</div>')
|
history_items.append(f'<div class="history-item">{hist_date}: {hist_time_str} ({hist_git_link})</div>')
|
||||||
|
|
||||||
|
# Store data for graph (reverse order for chronological display)
|
||||||
|
hist_data_points.insert(0, {
|
||||||
|
'time_ns': hist_total,
|
||||||
|
'timestamp': hist.get('timestamp', ''),
|
||||||
|
'date': hist_date
|
||||||
|
})
|
||||||
|
|
||||||
|
# Generate SVG graph
|
||||||
|
svg_graph = HTMLGenerator._generate_svg_graph(hist_data_points)
|
||||||
|
|
||||||
history_modal = f'''
|
history_modal = f'''
|
||||||
<div id="history-{user}-{year}-{day}-{part}" class="modal">
|
<div id="history-{user}-{year}-{day}-{part}" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@ -1375,7 +1484,14 @@ class HTMLGenerator:
|
|||||||
<span class="modal-close" onclick="closeHistory('{user}', {year}, {day}, {part})">×</span>
|
<span class="modal-close" onclick="closeHistory('{user}', {year}, {day}, {part})">×</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{''.join(history_items)}
|
<div class="history-graph">
|
||||||
|
<strong>Performance Trend:</strong>
|
||||||
|
{svg_graph}
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 20px;">
|
||||||
|
<strong>History Details:</strong>
|
||||||
|
{''.join(history_items)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user