manueldeprada HF Staff commited on
Commit
be26939
·
verified ·
1 Parent(s): d87fc8a

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. app.py +185 -33
  2. data.py +130 -2
  3. styles.css +104 -0
  4. time_series.py +251 -0
app.py CHANGED
@@ -7,6 +7,7 @@ from data import CIResults
7
  from utils import logger
8
  from summary_page import create_summary_page
9
  from model_page import plot_model_stats
 
10
 
11
 
12
  # Configure matplotlib to prevent memory warnings and set dark background
@@ -91,7 +92,47 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
91
  description_text = get_description_text()
92
  description_display = gr.Markdown(description_text, elem_classes=["sidebar-description"])
93
 
94
- # Summary button at the top
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  summary_button = gr.Button(
96
  "summary\n📊",
97
  variant="primary",
@@ -134,45 +175,64 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
134
 
135
  # Main content area
136
  with gr.Column(scale=4, elem_classes=["main-content"]):
137
- # Summary display (default view)
138
- summary_display = gr.Plot(
139
- value=create_summary_page(Ci_results.df, Ci_results.available_models),
140
- label="",
141
- format="png",
142
- elem_classes=["plot-container"],
143
- visible=True
144
- )
 
 
145
 
146
- # Detailed view components (hidden by default)
147
- with gr.Column(visible=False, elem_classes=["detail-view"]) as detail_view:
 
 
 
 
 
 
148
 
149
- # Create the plot output
150
- plot_output = gr.Plot(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  label="",
152
  format="png",
153
  elem_classes=["plot-container"]
154
  )
155
 
156
- # Create two separate failed tests displays in a row layout
157
- with gr.Row():
158
- with gr.Column(scale=1):
159
- amd_failed_tests_output = gr.Textbox(
160
- value="",
161
- lines=8,
162
- max_lines=8,
163
- interactive=False,
164
- container=False,
165
- elem_classes=["failed-tests"]
166
- )
167
- with gr.Column(scale=1):
168
- nvidia_failed_tests_output = gr.Textbox(
169
- value="",
170
- lines=8,
171
- max_lines=8,
172
- interactive=False,
173
- container=False,
174
- elem_classes=["failed-tests"]
175
- )
176
 
177
  # Set up click handlers for model buttons
178
  for i, btn in enumerate(model_buttons):
@@ -293,6 +353,98 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
293
  return "🔗 **CI Jobs:** *Error loading links*\n\n❓ **[FAQ](README.md)**"
294
 
295
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  # Auto-update CI links when the interface loads
297
  demo.load(
298
  fn=get_ci_links,
 
7
  from utils import logger
8
  from summary_page import create_summary_page
9
  from model_page import plot_model_stats
10
+ from time_series import create_time_series_summary, create_model_time_series
11
 
12
 
13
  # Configure matplotlib to prevent memory warnings and set dark background
 
92
  description_text = get_description_text()
93
  description_display = gr.Markdown(description_text, elem_classes=["sidebar-description"])
94
 
95
+ # View toggle buttons
96
+ with gr.Row(elem_classes=["view-toggle-row"]):
97
+ current_view_button = gr.Button(
98
+ "current\n📊",
99
+ variant="primary",
100
+ size="lg",
101
+ elem_classes=["view-toggle-button", "view-toggle-active"]
102
+ )
103
+ historical_view_button = gr.Button(
104
+ "history\n📈",
105
+ variant="secondary",
106
+ size="lg",
107
+ elem_classes=["view-toggle-button"]
108
+ )
109
+
110
+ # Date selection (initially hidden)
111
+ with gr.Column(visible=False, elem_classes=["date-selection"]) as date_selection:
112
+ gr.Markdown("**📅 Date Range Selection**", elem_classes=["date-header"])
113
+
114
+ with gr.Row():
115
+ start_date = gr.Dropdown(
116
+ choices=Ci_results.available_dates,
117
+ value=Ci_results.available_dates[0] if Ci_results.available_dates else None,
118
+ label="Start Date",
119
+ elem_classes=["date-dropdown"]
120
+ )
121
+ end_date = gr.Dropdown(
122
+ choices=Ci_results.available_dates,
123
+ value=Ci_results.available_dates[0] if Ci_results.available_dates else None,
124
+ label="End Date",
125
+ elem_classes=["date-dropdown"]
126
+ )
127
+
128
+ load_historical_button = gr.Button(
129
+ "Load Historical Data",
130
+ variant="primary",
131
+ size="sm",
132
+ elem_classes=["load-historical-button"]
133
+ )
134
+
135
+ # Summary button (for current view)
136
  summary_button = gr.Button(
137
  "summary\n📊",
138
  variant="primary",
 
175
 
176
  # Main content area
177
  with gr.Column(scale=4, elem_classes=["main-content"]):
178
+ # Current view components
179
+ with gr.Column(visible=True, elem_classes=["current-view"]) as current_view:
180
+ # Summary display (default view)
181
+ summary_display = gr.Plot(
182
+ value=create_summary_page(Ci_results.df, Ci_results.available_models),
183
+ label="",
184
+ format="png",
185
+ elem_classes=["plot-container"],
186
+ visible=True
187
+ )
188
 
189
+ # Detailed view components (hidden by default)
190
+ with gr.Column(visible=False, elem_classes=["detail-view"]) as detail_view:
191
+ # Create the plot output
192
+ plot_output = gr.Plot(
193
+ label="",
194
+ format="png",
195
+ elem_classes=["plot-container"]
196
+ )
197
 
198
+ # Create two separate failed tests displays in a row layout
199
+ with gr.Row():
200
+ with gr.Column(scale=1):
201
+ amd_failed_tests_output = gr.Textbox(
202
+ value="",
203
+ lines=8,
204
+ max_lines=8,
205
+ interactive=False,
206
+ container=False,
207
+ elem_classes=["failed-tests"]
208
+ )
209
+ with gr.Column(scale=1):
210
+ nvidia_failed_tests_output = gr.Textbox(
211
+ value="",
212
+ lines=8,
213
+ max_lines=8,
214
+ interactive=False,
215
+ container=False,
216
+ elem_classes=["failed-tests"]
217
+ )
218
+
219
+ # Historical view components (hidden by default)
220
+ with gr.Column(visible=False, elem_classes=["historical-view"]) as historical_view:
221
+ # Time-series summary display
222
+ time_series_summary_display = gr.Plot(
223
  label="",
224
  format="png",
225
  elem_classes=["plot-container"]
226
  )
227
 
228
+ # Time-series model view (hidden by default)
229
+ with gr.Column(visible=False, elem_classes=["time-series-detail-view"]) as time_series_detail_view:
230
+ # Create the time-series plot output
231
+ time_series_plot_output = gr.Plot(
232
+ label="",
233
+ format="png",
234
+ elem_classes=["plot-container"]
235
+ )
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
  # Set up click handlers for model buttons
238
  for i, btn in enumerate(model_buttons):
 
353
  return "🔗 **CI Jobs:** *Error loading links*\n\n❓ **[FAQ](README.md)**"
354
 
355
 
356
+ # View toggle functionality
357
+ def toggle_to_current_view():
358
+ """Switch to current view."""
359
+ return [
360
+ gr.update(visible=True), # current_view
361
+ gr.update(visible=False), # historical_view
362
+ gr.update(visible=False), # date_selection
363
+ gr.update(visible=True), # summary_button
364
+ gr.update(variant="primary", elem_classes=["view-toggle-button", "view-toggle-active"]), # current_view_button
365
+ gr.update(variant="secondary", elem_classes=["view-toggle-button"]) # historical_view_button
366
+ ]
367
+
368
+ def toggle_to_historical_view():
369
+ """Switch to historical view."""
370
+ return [
371
+ gr.update(visible=False), # current_view
372
+ gr.update(visible=True), # historical_view
373
+ gr.update(visible=True), # date_selection
374
+ gr.update(visible=False), # summary_button
375
+ gr.update(variant="secondary", elem_classes=["view-toggle-button"]), # current_view_button
376
+ gr.update(variant="primary", elem_classes=["view-toggle-button", "view-toggle-active"]) # historical_view_button
377
+ ]
378
+
379
+ current_view_button.click(
380
+ fn=toggle_to_current_view,
381
+ outputs=[current_view, historical_view, date_selection, summary_button, current_view_button, historical_view_button]
382
+ )
383
+
384
+ historical_view_button.click(
385
+ fn=toggle_to_historical_view,
386
+ outputs=[current_view, historical_view, date_selection, summary_button, current_view_button, historical_view_button]
387
+ )
388
+
389
+ # Historical data loading functionality
390
+ def load_historical_data(start_date, end_date):
391
+ """Load and display historical data."""
392
+ if not start_date or not end_date:
393
+ return gr.update(), "Please select both start and end dates."
394
+
395
+ try:
396
+ Ci_results.load_historical_data(start_date, end_date)
397
+ if Ci_results.historical_df.empty:
398
+ return gr.update(), "No historical data found for the selected date range."
399
+
400
+ # Create time-series summary plot
401
+ time_series_plot = create_time_series_summary(Ci_results.historical_df)
402
+ return time_series_plot, f"Loaded historical data from {start_date} to {end_date}"
403
+ except Exception as e:
404
+ logger.error(f"Error loading historical data: {e}")
405
+ return gr.update(), f"Error loading historical data: {str(e)}"
406
+
407
+ load_historical_button.click(
408
+ fn=load_historical_data,
409
+ inputs=[start_date, end_date],
410
+ outputs=[time_series_summary_display, description_display]
411
+ )
412
+
413
+ # Time-series model selection functionality
414
+ def show_time_series_model(selected_model):
415
+ """Show time-series view for a specific model."""
416
+ if Ci_results.historical_df.empty:
417
+ return gr.update(), "No historical data loaded. Please load historical data first."
418
+
419
+ try:
420
+ time_series_plot = create_model_time_series(Ci_results.historical_df, selected_model)
421
+ return time_series_plot
422
+ except Exception as e:
423
+ logger.error(f"Error creating time-series for model {selected_model}: {e}")
424
+ return gr.update()
425
+
426
+ # Update model button handlers to work with both views
427
+ for i, btn in enumerate(model_buttons):
428
+ model_name = model_choices[i]
429
+
430
+ # Current view handler (existing functionality)
431
+ btn.click(
432
+ fn=lambda selected_model=model_name: plot_model_stats(Ci_results.df, selected_model),
433
+ outputs=[plot_output, amd_failed_tests_output, nvidia_failed_tests_output]
434
+ ).then(
435
+ fn=lambda: [gr.update(visible=False), gr.update(visible=True)],
436
+ outputs=[summary_display, detail_view]
437
+ )
438
+
439
+ # Historical view handler (new functionality)
440
+ btn.click(
441
+ fn=lambda selected_model=model_name: show_time_series_model(selected_model),
442
+ outputs=[time_series_plot_output]
443
+ ).then(
444
+ fn=lambda: [gr.update(visible=False), gr.update(visible=True)],
445
+ outputs=[time_series_summary_display, time_series_detail_view]
446
+ )
447
+
448
  # Auto-update CI links when the interface loads
449
  demo.load(
450
  fn=get_ci_links,
data.py CHANGED
@@ -1,11 +1,12 @@
1
  from huggingface_hub import HfFileSystem
2
  import pandas as pd
3
  from utils import logger
4
- from datetime import datetime
5
  import threading
6
  import traceback
7
  import json
8
  import re
 
9
 
10
  # NOTE: if caching is an issue, try adding `use_listings_cache=False`
11
  fs = HfFileSystem()
@@ -92,12 +93,118 @@ def infer_latest_update_msg(date_df_amd: str, date_df_nvidia: str) -> str:
92
 
93
  def read_one_dataframe(json_path: str, device_label: str) -> tuple[pd.DataFrame, str]:
94
  df_upload_date = log_dataframe_link(json_path)
95
- df = pd.read_json(json_path, orient="index", encoding_errors="ignore")
96
  df.index.name = "model_name"
97
  df[f"failed_multi_no_{device_label}"] = df["failures"].apply(lambda x: len(x["multi"]) if "multi" in x else 0)
98
  df[f"failed_single_no_{device_label}"] = df["failures"].apply(lambda x: len(x["single"]) if "single" in x else 0)
99
  return df, df_upload_date
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  def get_distant_data() -> tuple[pd.DataFrame, str]:
102
  # Retrieve AMD dataframe
103
  amd_src = "hf://datasets/optimum-amd/transformers_daily_ci/**/runs/**/ci_results_run_models_gpu/model_results.json"
@@ -184,6 +291,8 @@ class CIResults:
184
  self.df = pd.DataFrame()
185
  self.available_models = []
186
  self.latest_update_msg = ""
 
 
187
 
188
  def load_data(self) -> None:
189
  """Load data from the data source."""
@@ -203,6 +312,15 @@ class CIResults:
203
  logger.error("\n".join(error_msg))
204
  new_df, latest_update_msg = get_sample_data()
205
  self.latest_update_msg = latest_update_msg
 
 
 
 
 
 
 
 
 
206
  # Update attributes
207
  self.df = new_df
208
  self.available_models = new_df.index.tolist()
@@ -223,6 +341,16 @@ class CIResults:
223
  msg[model][col] = value
224
  logger.info(json.dumps(msg, indent=4))
225
 
 
 
 
 
 
 
 
 
 
 
226
  def schedule_data_reload(self):
227
  """Schedule the next data reload."""
228
  def reload_data():
 
1
  from huggingface_hub import HfFileSystem
2
  import pandas as pd
3
  from utils import logger
4
+ from datetime import datetime, timedelta
5
  import threading
6
  import traceback
7
  import json
8
  import re
9
+ from typing import List, Tuple, Optional
10
 
11
  # NOTE: if caching is an issue, try adding `use_listings_cache=False`
12
  fs = HfFileSystem()
 
93
 
94
  def read_one_dataframe(json_path: str, device_label: str) -> tuple[pd.DataFrame, str]:
95
  df_upload_date = log_dataframe_link(json_path)
96
+ df = pd.read_json(json_path, orient="index")
97
  df.index.name = "model_name"
98
  df[f"failed_multi_no_{device_label}"] = df["failures"].apply(lambda x: len(x["multi"]) if "multi" in x else 0)
99
  df[f"failed_single_no_{device_label}"] = df["failures"].apply(lambda x: len(x["single"]) if "single" in x else 0)
100
  return df, df_upload_date
101
 
102
+ def get_available_dates() -> List[str]:
103
+ """Get list of available dates from both AMD and NVIDIA datasets."""
104
+ try:
105
+ # Get AMD dates
106
+ amd_src = "hf://datasets/optimum-amd/transformers_daily_ci/**/runs/**/ci_results_run_models_gpu/model_results.json"
107
+ files_amd = sorted(fs.glob(amd_src, refresh=True), reverse=True)
108
+
109
+ # Get NVIDIA dates
110
+ nvidia_src = "hf://datasets/hf-internal-testing/transformers_daily_ci/*/ci_results_run_models_gpu/model_results.json"
111
+ files_nvidia = sorted(fs.glob(nvidia_src, refresh=True), reverse=True)
112
+
113
+ # Extract dates from file paths
114
+ amd_dates = set()
115
+ for file_path in files_amd:
116
+ pattern = r'transformers_daily_ci(.*?)/(\d{4}-\d{2}-\d{2})'
117
+ match = re.search(pattern, file_path)
118
+ if match:
119
+ amd_dates.add(match.group(2))
120
+
121
+ nvidia_dates = set()
122
+ for file_path in files_nvidia:
123
+ pattern = r'transformers_daily_ci/(\d{4}-\d{2}-\d{2})'
124
+ match = re.search(pattern, file_path)
125
+ if match:
126
+ nvidia_dates.add(match.group(1))
127
+
128
+ # Return intersection of both datasets (dates where both have data)
129
+ common_dates = sorted(amd_dates.intersection(nvidia_dates), reverse=True)
130
+ return common_dates[:30] # Limit to last 30 days for performance
131
+
132
+ except Exception as e:
133
+ logger.error(f"Error getting available dates: {e}")
134
+ # Return sample dates for fallback
135
+ today = datetime.now()
136
+ return [(today - timedelta(days=i)).strftime("%Y-%m-%d") for i in range(7)]
137
+
138
+
139
+ def get_data_for_date(target_date: str) -> tuple[pd.DataFrame, str]:
140
+ """Get data for a specific date."""
141
+ try:
142
+ # Construct paths for specific date
143
+ amd_src = f"hf://datasets/optimum-amd/transformers_daily_ci/**/runs/{target_date}/**/ci_results_run_models_gpu/model_results.json"
144
+ nvidia_src = f"hf://datasets/hf-internal-testing/transformers_daily_ci/{target_date}/ci_results_run_models_gpu/model_results.json"
145
+
146
+ # Find matching files
147
+ files_amd = fs.glob(amd_src, refresh=True)
148
+ files_nvidia = fs.glob(nvidia_src, refresh=True)
149
+
150
+ if not files_amd or not files_nvidia:
151
+ raise FileNotFoundError(f"No data found for date {target_date}")
152
+
153
+ # Use the first matching file for each
154
+ df_amd, _ = read_one_dataframe(f"hf://{files_amd[0]}", "amd")
155
+ df_nvidia, _ = read_one_dataframe(f"https://huggingface.co/datasets/hf-internal-testing/transformers_daily_ci/raw/main/{target_date}/ci_results_run_models_gpu/model_results.json", "nvidia")
156
+
157
+ # Join both dataframes
158
+ joined = df_amd.join(df_nvidia, rsuffix="_nvidia", lsuffix="_amd", how="outer")
159
+ joined = joined[KEYS_TO_KEEP]
160
+ joined.index = joined.index.str.replace("^models_", "", regex=True)
161
+
162
+ # Filter out all but important models
163
+ important_models_lower = [model.lower() for model in IMPORTANT_MODELS]
164
+ filtered_joined = joined[joined.index.str.lower().isin(important_models_lower)]
165
+
166
+ return filtered_joined, target_date
167
+
168
+ except Exception as e:
169
+ logger.error(f"Error getting data for date {target_date}: {e}")
170
+ # Fallback to sample data
171
+ return get_sample_data()
172
+
173
+
174
+ def get_historical_data(start_date: str, end_date: str) -> pd.DataFrame:
175
+ """Get historical data for a date range."""
176
+ try:
177
+ start_dt = datetime.strptime(start_date, "%Y-%m-%d")
178
+ end_dt = datetime.strptime(end_date, "%Y-%m-%d")
179
+
180
+ historical_data = []
181
+ current_dt = start_dt
182
+
183
+ while current_dt <= end_dt:
184
+ date_str = current_dt.strftime("%Y-%m-%d")
185
+ try:
186
+ df, _ = get_data_for_date(date_str)
187
+ df['date'] = date_str
188
+ historical_data.append(df)
189
+ logger.info(f"Loaded data for {date_str}")
190
+ except Exception as e:
191
+ logger.warning(f"Could not load data for {date_str}: {e}")
192
+
193
+ current_dt += timedelta(days=1)
194
+
195
+ if not historical_data:
196
+ raise ValueError("No historical data found for the specified range")
197
+
198
+ # Combine all dataframes
199
+ combined_df = pd.concat(historical_data, ignore_index=False)
200
+ return combined_df
201
+
202
+ except Exception as e:
203
+ logger.error(f"Error getting historical data: {e}")
204
+ # Return empty dataframe with proper structure
205
+ return pd.DataFrame()
206
+
207
+
208
  def get_distant_data() -> tuple[pd.DataFrame, str]:
209
  # Retrieve AMD dataframe
210
  amd_src = "hf://datasets/optimum-amd/transformers_daily_ci/**/runs/**/ci_results_run_models_gpu/model_results.json"
 
291
  self.df = pd.DataFrame()
292
  self.available_models = []
293
  self.latest_update_msg = ""
294
+ self.available_dates = []
295
+ self.historical_df = pd.DataFrame()
296
 
297
  def load_data(self) -> None:
298
  """Load data from the data source."""
 
312
  logger.error("\n".join(error_msg))
313
  new_df, latest_update_msg = get_sample_data()
314
  self.latest_update_msg = latest_update_msg
315
+
316
+ # Load available dates
317
+ try:
318
+ self.available_dates = get_available_dates()
319
+ logger.info(f"Available dates: {len(self.available_dates)} dates")
320
+ except Exception as e:
321
+ logger.error(f"Error loading available dates: {e}")
322
+ self.available_dates = []
323
+
324
  # Update attributes
325
  self.df = new_df
326
  self.available_models = new_df.index.tolist()
 
341
  msg[model][col] = value
342
  logger.info(json.dumps(msg, indent=4))
343
 
344
+ def load_historical_data(self, start_date: str, end_date: str) -> None:
345
+ """Load historical data for a date range."""
346
+ try:
347
+ logger.info(f"Loading historical data from {start_date} to {end_date}")
348
+ self.historical_df = get_historical_data(start_date, end_date)
349
+ logger.info(f"Historical data loaded: {len(self.historical_df)} records")
350
+ except Exception as e:
351
+ logger.error(f"Error loading historical data: {e}")
352
+ self.historical_df = pd.DataFrame()
353
+
354
  def schedule_data_reload(self):
355
  """Schedule the next data reload."""
356
  def reload_data():
styles.css CHANGED
@@ -667,3 +667,107 @@ h1, h2, h3, p, .markdown {
667
  100% { scroll-behavior: auto; }
668
  }
669
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  100% { scroll-behavior: auto; }
668
  }
669
 
670
+ /* View toggle buttons */
671
+ .view-toggle-row {
672
+ display: flex !important;
673
+ gap: 5px !important;
674
+ margin-bottom: 15px !important;
675
+ }
676
+
677
+ .view-toggle-button {
678
+ flex: 1 !important;
679
+ background: linear-gradient(135deg, #2a2a2a, #1e1e1e) !important;
680
+ color: white !important;
681
+ border: 1px solid #333 !important;
682
+ border-radius: 5px !important;
683
+ padding: 8px 6px !important;
684
+ transition: all 0.3s ease !important;
685
+ font-weight: 600 !important;
686
+ font-size: 12px !important;
687
+ text-transform: uppercase !important;
688
+ letter-spacing: 0.3px !important;
689
+ font-family: monospace !important;
690
+ height: 50px !important;
691
+ display: flex !important;
692
+ flex-direction: column !important;
693
+ justify-content: center !important;
694
+ align-items: center !important;
695
+ line-height: 1.2 !important;
696
+ cursor: pointer !important;
697
+ }
698
+
699
+ .view-toggle-button:hover {
700
+ background: linear-gradient(135deg, #3a3a3a, #2e2e2e) !important;
701
+ border-color: #555 !important;
702
+ }
703
+
704
+ .view-toggle-active {
705
+ background: linear-gradient(135deg, #4a4a4a, #3e3e3e) !important;
706
+ border: 2px solid #555555 !important;
707
+ box-shadow:
708
+ 0 4px 15px rgba(0, 0, 0, 0.3),
709
+ inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
710
+ }
711
+
712
+ /* Date selection styling */
713
+ .date-selection {
714
+ background: linear-gradient(145deg, #0f0f0f, #1a1a1a) !important;
715
+ border: 1px solid #333 !important;
716
+ border-radius: 8px !important;
717
+ padding: 15px !important;
718
+ margin-bottom: 15px !important;
719
+ }
720
+
721
+ .date-header {
722
+ margin-bottom: 10px !important;
723
+ text-align: center !important;
724
+ color: #74b9ff !important;
725
+ font-family: monospace !important;
726
+ font-size: 14px !important;
727
+ }
728
+
729
+ .date-dropdown {
730
+ background-color: #222222 !important;
731
+ color: white !important;
732
+ border: 1px solid #444444 !important;
733
+ border-radius: 5px !important;
734
+ font-family: monospace !important;
735
+ font-size: 12px !important;
736
+ }
737
+
738
+ .date-dropdown .gr-dropdown {
739
+ background-color: #222222 !important;
740
+ color: white !important;
741
+ border: 1px solid #444444 !important;
742
+ }
743
+
744
+ .load-historical-button {
745
+ background: linear-gradient(135deg, #2d5aa0, #1e3f73) !important;
746
+ color: white !important;
747
+ border: 1px solid #3a6bc7 !important;
748
+ border-radius: 5px !important;
749
+ padding: 8px 12px !important;
750
+ transition: all 0.3s ease !important;
751
+ font-weight: 500 !important;
752
+ font-size: 12px !important;
753
+ text-transform: uppercase !important;
754
+ letter-spacing: 0.1px !important;
755
+ font-family: monospace !important;
756
+ width: 100% !important;
757
+ margin-top: 10px !important;
758
+ }
759
+
760
+ .load-historical-button:hover {
761
+ background: linear-gradient(135deg, #3a6bc7, #2d5aa0) !important;
762
+ border-color: #4a7bd9 !important;
763
+ }
764
+
765
+ /* Historical view styling */
766
+ .historical-view {
767
+ background-color: #000000 !important;
768
+ }
769
+
770
+ .time-series-detail-view {
771
+ background-color: #000000 !important;
772
+ }
773
+
time_series.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import matplotlib.pyplot as plt
2
+ import pandas as pd
3
+ import numpy as np
4
+ from datetime import datetime
5
+ from data import extract_model_data
6
+
7
+ # Colors matching the existing theme
8
+ COLORS = {
9
+ 'passed': '#4CAF50',
10
+ 'failed': '#E53E3E',
11
+ 'skipped': '#FFD54F',
12
+ 'error': '#8B0000'
13
+ }
14
+
15
+ # Figure dimensions
16
+ FIGURE_WIDTH = 20
17
+ FIGURE_HEIGHT = 12
18
+
19
+ # Styling constants
20
+ BLACK = '#000000'
21
+ LABEL_COLOR = '#CCCCCC'
22
+ TITLE_COLOR = '#FFFFFF'
23
+ GRID_COLOR = '#333333'
24
+
25
+ # Font sizes
26
+ TITLE_FONT_SIZE = 24
27
+ LABEL_FONT_SIZE = 14
28
+ LEGEND_FONT_SIZE = 12
29
+
30
+
31
+ def create_time_series_summary(historical_df: pd.DataFrame) -> plt.Figure:
32
+ """Create time-series visualization for overall failure rates over time."""
33
+ if historical_df.empty or 'date' not in historical_df.columns:
34
+ fig, ax = plt.subplots(figsize=(FIGURE_WIDTH, FIGURE_HEIGHT), facecolor=BLACK)
35
+ ax.set_facecolor(BLACK)
36
+ ax.text(0.5, 0.5, 'No historical data available',
37
+ horizontalalignment='center', verticalalignment='center',
38
+ transform=ax.transAxes, fontsize=20, color='#888888',
39
+ fontfamily='monospace', weight='normal')
40
+ ax.axis('off')
41
+ return fig
42
+
43
+ # Convert date column to datetime
44
+ historical_df['date_dt'] = pd.to_datetime(historical_df['date'])
45
+ historical_df = historical_df.sort_values('date_dt')
46
+
47
+ # Group by date and calculate overall statistics
48
+ daily_stats = []
49
+ dates = []
50
+
51
+ for date in historical_df['date_dt'].unique():
52
+ date_data = historical_df[historical_df['date_dt'] == date]
53
+
54
+ total_amd_passed = 0
55
+ total_amd_failed = 0
56
+ total_amd_skipped = 0
57
+ total_nvidia_passed = 0
58
+ total_nvidia_failed = 0
59
+ total_nvidia_skipped = 0
60
+
61
+ for _, row in date_data.iterrows():
62
+ amd_stats, nvidia_stats = extract_model_data(row)[:2]
63
+
64
+ total_amd_passed += amd_stats['passed']
65
+ total_amd_failed += amd_stats['failed']
66
+ total_amd_skipped += amd_stats['skipped']
67
+
68
+ total_nvidia_passed += nvidia_stats['passed']
69
+ total_nvidia_failed += nvidia_stats['failed']
70
+ total_nvidia_skipped += nvidia_stats['skipped']
71
+
72
+ # Calculate failure rates
73
+ amd_total = total_amd_passed + total_amd_failed
74
+ nvidia_total = total_nvidia_passed + total_nvidia_failed
75
+
76
+ amd_failure_rate = (total_amd_failed / amd_total * 100) if amd_total > 0 else 0
77
+ nvidia_failure_rate = (total_nvidia_failed / nvidia_total * 100) if nvidia_total > 0 else 0
78
+
79
+ daily_stats.append({
80
+ 'amd_failure_rate': amd_failure_rate,
81
+ 'nvidia_failure_rate': nvidia_failure_rate,
82
+ 'amd_passed': total_amd_passed,
83
+ 'amd_failed': total_amd_failed,
84
+ 'amd_skipped': total_amd_skipped,
85
+ 'nvidia_passed': total_nvidia_passed,
86
+ 'nvidia_failed': total_nvidia_failed,
87
+ 'nvidia_skipped': total_nvidia_skipped
88
+ })
89
+ dates.append(date)
90
+
91
+ # Create the plot
92
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(FIGURE_WIDTH, FIGURE_HEIGHT), facecolor=BLACK)
93
+ ax1.set_facecolor(BLACK)
94
+ ax2.set_facecolor(BLACK)
95
+
96
+ # Plot 1: Failure rates over time
97
+ dates_array = np.array(dates)
98
+ amd_rates = [stat['amd_failure_rate'] for stat in daily_stats]
99
+ nvidia_rates = [stat['nvidia_failure_rate'] for stat in daily_stats]
100
+
101
+ ax1.plot(dates_array, amd_rates, color='#FF6B6B', linewidth=3, label='AMD', marker='o', markersize=6)
102
+ ax1.plot(dates_array, nvidia_rates, color='#4ECDC4', linewidth=3, label='NVIDIA', marker='s', markersize=6)
103
+
104
+ ax1.set_title('Overall Failure Rates Over Time', fontsize=TITLE_FONT_SIZE, color=TITLE_COLOR,
105
+ fontfamily='monospace', fontweight='bold', pad=20)
106
+ ax1.set_ylabel('Failure Rate (%)', fontsize=LABEL_FONT_SIZE, color=LABEL_COLOR, fontfamily='monospace')
107
+ ax1.grid(True, color=GRID_COLOR, alpha=0.3, linestyle='-', linewidth=0.5)
108
+ ax1.legend(fontsize=LEGEND_FONT_SIZE, loc='upper right', frameon=False,
109
+ labelcolor=LABEL_COLOR, prop={'family': 'monospace'})
110
+
111
+ # Format x-axis
112
+ ax1.tick_params(colors=LABEL_COLOR, labelsize=LABEL_FONT_SIZE)
113
+ ax1.xaxis.label.set_color(LABEL_COLOR)
114
+ ax1.yaxis.label.set_color(LABEL_COLOR)
115
+
116
+ # Plot 2: Test counts over time (stacked area chart)
117
+ amd_passed = [stat['amd_passed'] for stat in daily_stats]
118
+ amd_failed = [stat['amd_failed'] for stat in daily_stats]
119
+ amd_skipped = [stat['amd_skipped'] for stat in daily_stats]
120
+
121
+ nvidia_passed = [stat['nvidia_passed'] for stat in daily_stats]
122
+ nvidia_failed = [stat['nvidia_failed'] for stat in daily_stats]
123
+ nvidia_skipped = [stat['nvidia_skipped'] for stat in daily_stats]
124
+
125
+ # AMD stacked area
126
+ ax2.fill_between(dates_array, 0, amd_passed, color=COLORS['passed'], alpha=0.7, label='AMD Passed')
127
+ ax2.fill_between(dates_array, amd_passed, np.array(amd_passed) + np.array(amd_failed),
128
+ color=COLORS['failed'], alpha=0.7, label='AMD Failed')
129
+ ax2.fill_between(dates_array, np.array(amd_passed) + np.array(amd_failed),
130
+ np.array(amd_passed) + np.array(amd_failed) + np.array(amd_skipped),
131
+ color=COLORS['skipped'], alpha=0.7, label='AMD Skipped')
132
+
133
+ # NVIDIA stacked area (offset to the right)
134
+ offset = 0.4 # Offset in days
135
+ dates_offset = dates_array + pd.Timedelta(days=offset)
136
+
137
+ ax2.fill_between(dates_offset, 0, nvidia_passed, color=COLORS['passed'], alpha=0.4, label='NVIDIA Passed')
138
+ ax2.fill_between(dates_offset, nvidia_passed, np.array(nvidia_passed) + np.array(nvidia_failed),
139
+ color=COLORS['failed'], alpha=0.4, label='NVIDIA Failed')
140
+ ax2.fill_between(dates_offset, np.array(nvidia_passed) + np.array(nvidia_failed),
141
+ np.array(nvidia_passed) + np.array(nvidia_failed) + np.array(nvidia_skipped),
142
+ color=COLORS['skipped'], alpha=0.4, label='NVIDIA Skipped')
143
+
144
+ ax2.set_title('Test Results Over Time (Stacked)', fontsize=TITLE_FONT_SIZE, color=TITLE_COLOR,
145
+ fontfamily='monospace', fontweight='bold', pad=20)
146
+ ax2.set_ylabel('Number of Tests', fontsize=LABEL_FONT_SIZE, color=LABEL_COLOR, fontfamily='monospace')
147
+ ax2.set_xlabel('Date', fontsize=LABEL_FONT_SIZE, color=LABEL_COLOR, fontfamily='monospace')
148
+ ax2.grid(True, color=GRID_COLOR, alpha=0.3, linestyle='-', linewidth=0.5)
149
+ ax2.legend(fontsize=LEGEND_FONT_SIZE, loc='upper right', frameon=False,
150
+ labelcolor=LABEL_COLOR, prop={'family': 'monospace'})
151
+
152
+ # Format x-axis
153
+ ax2.tick_params(colors=LABEL_COLOR, labelsize=LABEL_FONT_SIZE)
154
+ ax2.xaxis.label.set_color(LABEL_COLOR)
155
+ ax2.yaxis.label.set_color(LABEL_COLOR)
156
+
157
+ # Rotate x-axis labels for better readability
158
+ for ax in [ax1, ax2]:
159
+ ax.tick_params(axis='x', rotation=45)
160
+
161
+ plt.tight_layout()
162
+ return fig
163
+
164
+
165
+ def create_model_time_series(historical_df: pd.DataFrame, model_name: str) -> plt.Figure:
166
+ """Create time-series visualization for a specific model."""
167
+ if historical_df.empty or 'date' not in historical_df.columns:
168
+ fig, ax = plt.subplots(figsize=(FIGURE_WIDTH, FIGURE_HEIGHT), facecolor=BLACK)
169
+ ax.set_facecolor(BLACK)
170
+ ax.text(0.5, 0.5, f'No historical data available for {model_name}',
171
+ horizontalalignment='center', verticalalignment='center',
172
+ transform=ax.transAxes, fontsize=20, color='#888888',
173
+ fontfamily='monospace', weight='normal')
174
+ ax.axis('off')
175
+ return fig
176
+
177
+ # Filter data for the specific model
178
+ model_data = historical_df[historical_df.index.str.lower() == model_name.lower()]
179
+
180
+ if model_data.empty:
181
+ fig, ax = plt.subplots(figsize=(FIGURE_WIDTH, FIGURE_HEIGHT), facecolor=BLACK)
182
+ ax.set_facecolor(BLACK)
183
+ ax.text(0.5, 0.5, f'No data found for model: {model_name}',
184
+ horizontalalignment='center', verticalalignment='center',
185
+ transform=ax.transAxes, fontsize=20, color='#888888',
186
+ fontfamily='monospace', weight='normal')
187
+ ax.axis('off')
188
+ return fig
189
+
190
+ # Convert date column to datetime and sort
191
+ model_data = model_data.copy()
192
+ model_data['date_dt'] = pd.to_datetime(model_data['date'])
193
+ model_data = model_data.sort_values('date_dt')
194
+
195
+ # Extract statistics for each date
196
+ dates = model_data['date_dt'].values
197
+ amd_stats_list = []
198
+ nvidia_stats_list = []
199
+
200
+ for _, row in model_data.iterrows():
201
+ amd_stats, nvidia_stats = extract_model_data(row)[:2]
202
+ amd_stats_list.append(amd_stats)
203
+ nvidia_stats_list.append(nvidia_stats)
204
+
205
+ # Create the plot
206
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(FIGURE_WIDTH, FIGURE_HEIGHT), facecolor=BLACK)
207
+ ax1.set_facecolor(BLACK)
208
+ ax2.set_facecolor(BLACK)
209
+
210
+ # Plot 1: AMD results over time
211
+ amd_passed = [stats['passed'] for stats in amd_stats_list]
212
+ amd_failed = [stats['failed'] for stats in amd_stats_list]
213
+ amd_skipped = [stats['skipped'] for stats in amd_stats_list]
214
+
215
+ ax1.plot(dates, amd_passed, color=COLORS['passed'], linewidth=3, label='Passed', marker='o', markersize=6)
216
+ ax1.plot(dates, amd_failed, color=COLORS['failed'], linewidth=3, label='Failed', marker='s', markersize=6)
217
+ ax1.plot(dates, amd_skipped, color=COLORS['skipped'], linewidth=3, label='Skipped', marker='^', markersize=6)
218
+
219
+ ax1.set_title(f'{model_name.upper()} - AMD Results Over Time', fontsize=TITLE_FONT_SIZE, color=TITLE_COLOR,
220
+ fontfamily='monospace', fontweight='bold', pad=20)
221
+ ax1.set_ylabel('Number of Tests', fontsize=LABEL_FONT_SIZE, color=LABEL_COLOR, fontfamily='monospace')
222
+ ax1.grid(True, color=GRID_COLOR, alpha=0.3, linestyle='-', linewidth=0.5)
223
+ ax1.legend(fontsize=LEGEND_FONT_SIZE, loc='upper right', frameon=False,
224
+ labelcolor=LABEL_COLOR, prop={'family': 'monospace'})
225
+
226
+ # Plot 2: NVIDIA results over time
227
+ nvidia_passed = [stats['passed'] for stats in nvidia_stats_list]
228
+ nvidia_failed = [stats['failed'] for stats in nvidia_stats_list]
229
+ nvidia_skipped = [stats['skipped'] for stats in nvidia_stats_list]
230
+
231
+ ax2.plot(dates, nvidia_passed, color=COLORS['passed'], linewidth=3, label='Passed', marker='o', markersize=6)
232
+ ax2.plot(dates, nvidia_failed, color=COLORS['failed'], linewidth=3, label='Failed', marker='s', markersize=6)
233
+ ax2.plot(dates, nvidia_skipped, color=COLORS['skipped'], linewidth=3, label='Skipped', marker='^', markersize=6)
234
+
235
+ ax2.set_title(f'{model_name.upper()} - NVIDIA Results Over Time', fontsize=TITLE_FONT_SIZE, color=TITLE_COLOR,
236
+ fontfamily='monospace', fontweight='bold', pad=20)
237
+ ax2.set_ylabel('Number of Tests', fontsize=LABEL_FONT_SIZE, color=LABEL_COLOR, fontfamily='monospace')
238
+ ax2.set_xlabel('Date', fontsize=LABEL_FONT_SIZE, color=LABEL_COLOR, fontfamily='monospace')
239
+ ax2.grid(True, color=GRID_COLOR, alpha=0.3, linestyle='-', linewidth=0.5)
240
+ ax2.legend(fontsize=LEGEND_FONT_SIZE, loc='upper right', frameon=False,
241
+ labelcolor=LABEL_COLOR, prop={'family': 'monospace'})
242
+
243
+ # Format axes
244
+ for ax in [ax1, ax2]:
245
+ ax.tick_params(colors=LABEL_COLOR, labelsize=LABEL_FONT_SIZE)
246
+ ax.xaxis.label.set_color(LABEL_COLOR)
247
+ ax.yaxis.label.set_color(LABEL_COLOR)
248
+ ax.tick_params(axis='x', rotation=45)
249
+
250
+ plt.tight_layout()
251
+ return fig