|
import matplotlib.pyplot as plt |
|
import pandas as pd |
|
from data import extract_model_data |
|
|
|
|
|
COLUMNS = 3 |
|
|
|
|
|
COLUMN_WIDTH = 100 / COLUMNS |
|
BAR_WIDTH = COLUMN_WIDTH * 0.8 |
|
BAR_MARGIN = COLUMN_WIDTH * 0.1 |
|
|
|
|
|
FIGURE_WIDTH = 22 |
|
MAX_HEIGHT = 14 |
|
MIN_HEIGHT_PER_ROW = 2.8 |
|
FIGURE_PADDING = 1 |
|
|
|
|
|
BAR_HEIGHT_RATIO = 0.22 |
|
VERTICAL_SPACING_RATIO = 0.2 |
|
AMD_BAR_OFFSET = 0.25 |
|
NVIDIA_BAR_OFFSET = 0.54 |
|
|
|
|
|
COLORS = { |
|
'passed': '#4CAF50', |
|
'failed': '#E53E3E', |
|
'skipped': '#FFD54F', |
|
'error': '#8B0000', |
|
'empty': "#5B5B5B" |
|
} |
|
|
|
|
|
MODEL_NAME_FONT_SIZE = 16 |
|
LABEL_FONT_SIZE = 14 |
|
LABEL_OFFSET = 1 |
|
FAILURE_RATE_FONT_SIZE = 28 |
|
|
|
|
|
def get_overall_stats(df: pd.DataFrame, available_models: list[str]) -> tuple[list[int], list[int]]: |
|
"""Calculate overall failure rates for AMD and NVIDIA across all models.""" |
|
if df.empty or not available_models: |
|
return 0.0, 0.0 |
|
|
|
total_amd_passed = 0 |
|
total_amd_failed = 0 |
|
total_amd_skipped = 0 |
|
total_nvidia_passed = 0 |
|
total_nvidia_failed = 0 |
|
total_nvidia_skipped = 0 |
|
|
|
for model_name in available_models: |
|
if model_name not in df.index: |
|
continue |
|
|
|
row = df.loc[model_name] |
|
amd_stats, nvidia_stats = extract_model_data(row)[:2] |
|
|
|
|
|
total_amd_passed += amd_stats['passed'] |
|
total_amd_failed += amd_stats['failed'] + amd_stats['error'] |
|
total_amd_skipped += amd_stats['skipped'] |
|
|
|
|
|
total_nvidia_passed += nvidia_stats['passed'] |
|
total_nvidia_failed += nvidia_stats['failed'] + nvidia_stats['error'] |
|
total_nvidia_skipped += nvidia_stats['skipped'] |
|
|
|
return [total_amd_passed, total_amd_failed, total_amd_skipped], [total_nvidia_passed, total_nvidia_failed, total_nvidia_skipped] |
|
|
|
|
|
def draw_text_and_bar( |
|
label: str, |
|
stats: dict[str, int], |
|
y_bar: float, |
|
column_left_position: float, |
|
bar_height: float, |
|
ax: plt.Axes, |
|
) -> None: |
|
"""Draw a horizontal bar chart for given stats and its label on the left.""" |
|
|
|
label_x = column_left_position - LABEL_OFFSET |
|
failures_present = any(stats[category] > 0 for category in ['failed', 'error']) |
|
if failures_present: |
|
props = dict(boxstyle='round', facecolor=COLORS['failed'], alpha=0.35) |
|
else: |
|
props = dict(alpha=0) |
|
ax.text( |
|
label_x, y_bar, label, ha='right', va='center', color='#CCCCCC', fontsize=LABEL_FONT_SIZE, |
|
fontfamily='monospace', fontweight='normal', bbox=props |
|
) |
|
|
|
total = sum(stats.values()) |
|
if total > 0: |
|
left = column_left_position |
|
for category in ['passed', 'failed', 'skipped', 'error']: |
|
if stats[category] > 0: |
|
width = stats[category] / total * BAR_WIDTH |
|
ax.barh(y_bar, width, left=left, height=bar_height, color=COLORS[category], alpha=0.9) |
|
left += width |
|
else: |
|
ax.barh(y_bar, BAR_WIDTH, left=column_left_position, height=bar_height, color=COLORS['empty'], alpha=0.9) |
|
|
|
def create_summary_page(df: pd.DataFrame, available_models: list[str]) -> plt.Figure: |
|
"""Create a summary page with model names and both AMD/NVIDIA test stats bars.""" |
|
if df.empty: |
|
fig, ax = plt.subplots(figsize=(16, 8), facecolor='#000000') |
|
ax.set_facecolor('#000000') |
|
ax.text(0.5, 0.5, 'No data available', |
|
horizontalalignment='center', verticalalignment='center', |
|
transform=ax.transAxes, fontsize=20, color='#888888', |
|
fontfamily='monospace', weight='normal') |
|
ax.axis('off') |
|
return fig |
|
|
|
|
|
amd_counts, nvidia_counts = get_overall_stats(df, available_models) |
|
|
|
amd_non_skipped = amd_counts[0] + amd_counts[1] |
|
amd_failure_rate = (amd_counts[1] / amd_non_skipped) if amd_non_skipped > 0 else 0.0 |
|
amd_failure_rate *= 100 |
|
nvidia_non_skipped = nvidia_counts[0] + nvidia_counts[1] |
|
nvidia_failure_rate = (nvidia_counts[1] / nvidia_non_skipped) if nvidia_non_skipped > 0 else 0.0 |
|
nvidia_failure_rate *= 100 |
|
|
|
|
|
model_count = len(available_models) |
|
rows = (model_count + COLUMNS - 1) // COLUMNS |
|
|
|
|
|
height_per_row = min(MIN_HEIGHT_PER_ROW, MAX_HEIGHT / max(rows, 1)) |
|
figure_height = min(MAX_HEIGHT, rows * height_per_row + FIGURE_PADDING) |
|
|
|
fig, ax = plt.subplots(figsize=(FIGURE_WIDTH, figure_height), facecolor='#000000') |
|
ax.set_facecolor('#000000') |
|
|
|
|
|
failure_text = f"Overall Failure Rates: AMD {amd_failure_rate:.1f}% | NVIDIA {nvidia_failure_rate:.1f}%" |
|
ax.text(50, -1.25, failure_text, ha='center', va='top', |
|
color='#FFFFFF', fontsize=FAILURE_RATE_FONT_SIZE, |
|
fontfamily='monospace', fontweight='bold') |
|
|
|
visible_model_count = 0 |
|
max_y = 0 |
|
|
|
for i, model_name in enumerate(available_models): |
|
if model_name not in df.index: |
|
continue |
|
|
|
row = df.loc[model_name] |
|
|
|
|
|
amd_stats, nvidia_stats = extract_model_data(row)[:2] |
|
|
|
|
|
col = visible_model_count % COLUMNS |
|
row = visible_model_count // COLUMNS |
|
|
|
|
|
col_left = col * COLUMN_WIDTH + BAR_MARGIN |
|
col_center = col * COLUMN_WIDTH + COLUMN_WIDTH / 2 |
|
|
|
|
|
vertical_spacing = height_per_row |
|
y_base = (VERTICAL_SPACING_RATIO + row) * vertical_spacing |
|
y_model_name = y_base |
|
y_amd_bar = y_base + vertical_spacing * AMD_BAR_OFFSET |
|
y_nvidia_bar = y_base + vertical_spacing * NVIDIA_BAR_OFFSET |
|
max_y = max(max_y, y_nvidia_bar + vertical_spacing * 0.3) |
|
|
|
|
|
ax.text(col_center, y_model_name, model_name.lower(), |
|
ha='center', va='center', color='#FFFFFF', |
|
fontsize=MODEL_NAME_FONT_SIZE, fontfamily='monospace', fontweight='bold') |
|
|
|
|
|
bar_height = min(0.4, vertical_spacing * BAR_HEIGHT_RATIO) |
|
|
|
draw_text_and_bar("amd", amd_stats, y_amd_bar, col_left, bar_height, ax) |
|
|
|
draw_text_and_bar("nvidia", nvidia_stats, y_nvidia_bar, col_left, bar_height, ax) |
|
|
|
|
|
visible_model_count += 1 |
|
|
|
|
|
|
|
|
|
line_height = 0.4 |
|
legend_y = max_y + 1 |
|
|
|
|
|
amd_y = legend_y - line_height / 2 |
|
nvidia_y = legend_y + line_height / 2 |
|
|
|
amd_totals_text = f"AMD Tests - Passed: {amd_counts[0]}, Failed: {amd_counts[1]}, Skipped: {amd_counts[2]}" |
|
nvidia_totals_text = f"NVIDIA Tests - Passed: {nvidia_counts[0]}, Failed: {nvidia_counts[1]}, Skipped: {nvidia_counts[2]}" |
|
|
|
ax.text(0, amd_y, amd_totals_text, |
|
ha='left', va='bottom', color='#CCCCCC', |
|
fontsize=14, fontfamily='monospace') |
|
|
|
ax.text(0, nvidia_y, nvidia_totals_text, |
|
ha='left', va='bottom', color='#CCCCCC', |
|
fontsize=14, fontfamily='monospace') |
|
|
|
|
|
patch_height = 0.3 |
|
patch_width = 3 |
|
|
|
legend_start_x = 68.7 |
|
legend_y = max_y + 1 |
|
legend_spacing = 10 |
|
legend_font_size = 15 |
|
|
|
|
|
legend_items = [ |
|
('passed', 'Passed'), |
|
('failed', 'Failed'), |
|
('skipped', 'Skipped'), |
|
] |
|
|
|
for i, (status, label) in enumerate(legend_items): |
|
x_pos = legend_start_x + i * legend_spacing |
|
|
|
ax.add_patch(plt.Rectangle((x_pos - 0.6, legend_y), patch_width, -patch_height, |
|
facecolor=COLORS[status], alpha=0.9)) |
|
|
|
ax.text(x_pos + patch_width, legend_y, label, |
|
ha='left', va='bottom', color='#CCCCCC', |
|
fontsize=legend_font_size, fontfamily='monospace') |
|
|
|
|
|
ax.set_xlim(-5, 105) |
|
ax.set_ylim(0, max_y + 1) |
|
ax.set_xlabel('') |
|
ax.set_ylabel('') |
|
ax.spines['bottom'].set_visible(False) |
|
ax.spines['left'].set_visible(False) |
|
ax.spines['top'].set_visible(False) |
|
ax.spines['right'].set_visible(False) |
|
ax.set_xticks([]) |
|
ax.set_yticks([]) |
|
ax.yaxis.set_inverted(True) |
|
|
|
|
|
plt.tight_layout() |
|
return fig |
|
|