@@ -260,12 +260,44 @@ def matches_job_name(api_name, log_job_name):
260260 )
261261
262262
263+ def find_job_metrics (job_name , metrics ):
264+ if not job_name or not metrics :
265+ return None
266+ for k , v in metrics .items ():
267+ if normalize_name (job_name ) == normalize_name (k ):
268+ return v
269+ for k , v in metrics .items ():
270+ if matches_job_name (job_name , k ):
271+ return v
272+ return None
273+
274+ def get_job_cost (job_metrics ):
275+ if job_metrics and "Cost" in job_metrics :
276+ return job_metrics ["Cost" ]
277+ return "N/A"
278+
263279def generate_report (run_data , jobs , metrics , log_dir , format_type ):
264280 start_time = run_data .get ("run_started_at" ) or run_data .get ("created_at" )
265281 end_time = run_data .get ("updated_at" )
266282 total_runtime = format_duration (start_time , end_time )
267283 total_runtime_sec = parse_duration_seconds (start_time , end_time )
268284
285+ # Compute workflow total cost
286+ total_cost_val = 0.0
287+ has_cost_data = False
288+ for job in jobs :
289+ jm = find_job_metrics (job .get ('name' , '' ), metrics )
290+ c_str = get_job_cost (jm )
291+ if c_str != "N/A" :
292+ try :
293+ val = float (c_str .replace ('$' , '' ).strip ())
294+ total_cost_val += val
295+ has_cost_data = True
296+ except ValueError :
297+ pass
298+
299+ total_workflow_cost = f"${ total_cost_val :.4f} " if has_cost_data else "N/A"
300+
269301 if format_type == "json" :
270302 jobs_json = []
271303 slowest = []
@@ -284,18 +316,9 @@ def generate_report(run_data, jobs, metrics, log_dir, format_type):
284316 runner_name = job .get ("runner_name" ) or "Unknown"
285317 is_outlier = job_dur_sec > (1.5 * avg_dur_sec ) if avg_dur_sec > 0 else False
286318
287- # Find metrics
288319 job_name = job .get ('name' , '' )
289- job_metrics = None
290- for k , v in metrics .items ():
291- if normalize_name (job_name ) == normalize_name (k ):
292- job_metrics = v
293- break
294- if not job_metrics :
295- for k , v in metrics .items ():
296- if matches_job_name (job_name , k ):
297- job_metrics = v
298- break
320+ job_metrics = find_job_metrics (job_name , metrics )
321+ job_cost = get_job_cost (job_metrics )
299322
300323 job_entry = {
301324 "name" : job .get ("name" ),
@@ -309,6 +332,7 @@ def generate_report(run_data, jobs, metrics, log_dir, format_type):
309332 "completed_at" : job .get ("completed_at" ),
310333 "duration" : job_dur ,
311334 "duration_seconds" : job_dur_sec ,
335+ "cost" : job_cost ,
312336 "is_outlier" : is_outlier ,
313337 "metrics" : job_metrics or {}
314338 }
@@ -317,6 +341,7 @@ def generate_report(run_data, jobs, metrics, log_dir, format_type):
317341 "name" : job .get ("name" ),
318342 "duration" : job_dur ,
319343 "duration_seconds" : job_dur_sec ,
344+ "cost" : job_cost ,
320345 "is_outlier" : is_outlier ,
321346 "status" : job .get ("status" ),
322347 "conclusion" : job .get ("conclusion" )
@@ -331,6 +356,7 @@ def generate_report(run_data, jobs, metrics, log_dir, format_type):
331356 "conclusion" : run_data .get ("conclusion" ),
332357 "runtime" : total_runtime ,
333358 "runtime_seconds" : total_runtime_sec ,
359+ "total_cost" : total_workflow_cost ,
334360 "avg_job_duration_seconds" : avg_dur_sec
335361 },
336362 "logs_dir" : log_dir ,
@@ -346,6 +372,7 @@ def generate_report(run_data, jobs, metrics, log_dir, format_type):
346372 f"- **Status**: `{ run_data .get ('status' )} `" ,
347373 f"- **Conclusion**: `{ run_data .get ('conclusion' )} `" ,
348374 f"- **Runtime**: `{ total_runtime } `" ,
375+ f"- **Total Cost**: `{ total_workflow_cost } `" ,
349376 f"- **Logs Directory**: `{ log_dir } `" ,
350377 "" ,
351378 "## Longest Jobs (Bottlenecks)"
@@ -358,18 +385,23 @@ def generate_report(run_data, jobs, metrics, log_dir, format_type):
358385 sorted_jobs .append ((dur_sec , j ))
359386 sorted_jobs .sort (key = lambda x : x [0 ], reverse = True )
360387
361- lines .append ("| Job Name | Duration | Status | Conclusion | Runner |" )
362- lines .append ("| --- | --- | --- | --- | --- |" )
388+ lines .append ("| Job Name | Duration | Cost | Status | Conclusion | Runner |" )
389+ lines .append ("| --- | --- | --- | --- | --- | --- | " )
363390 for dur_sec , j in sorted_jobs [:10 ]:
364391 dur_str = format_duration (j .get ("started_at" ), j .get ("completed_at" ))
392+ jm = find_job_metrics (j .get ('name' , '' ), metrics )
393+ c_str = get_job_cost (jm )
365394 labels_list = j .get ("labels" , [])
366395 labels_str = ", " .join (labels_list ) if labels_list else "None"
367- lines .append (f"| { j .get ('name' )} | `{ dur_str } ` | `{ j .get ('status' )} ` | `{ j .get ('conclusion' )} ` | `{ labels_str } ` |" )
396+ lines .append (f"| { j .get ('name' )} | `{ dur_str } ` | `{ c_str } ` | ` { j .get ('status' )} ` | `{ j .get ('conclusion' )} ` | `{ labels_str } ` |" )
368397
369398 lines .append ("\n ## Jobs Summary" )
370399
371400 for job in jobs :
372401 job_dur = format_duration (job .get ("started_at" ), job .get ("completed_at" ))
402+ job_name = job .get ('name' , '' )
403+ job_metrics = find_job_metrics (job_name , metrics )
404+ job_cost = get_job_cost (job_metrics )
373405 labels_list = job .get ("labels" , [])
374406 labels_str = ", " .join (labels_list ) if labels_list else "None"
375407 runner_name = job .get ("runner_name" ) or "Unknown"
@@ -381,19 +413,7 @@ def generate_report(run_data, jobs, metrics, log_dir, format_type):
381413 lines .append (f"- **Start**: `{ job .get ('started_at' )} `" )
382414 lines .append (f"- **End**: `{ job .get ('completed_at' )} `" )
383415 lines .append (f"- **Duration**: `{ job_dur } `" )
384-
385- # Look up job metrics
386- job_name = job .get ('name' , '' )
387- job_metrics = None
388- for k , v in metrics .items ():
389- if normalize_name (job_name ) == normalize_name (k ):
390- job_metrics = v
391- break
392- if not job_metrics :
393- for k , v in metrics .items ():
394- if matches_job_name (job_name , k ):
395- job_metrics = v
396- break
416+ lines .append (f"- **Cost**: `{ job_cost } `" )
397417
398418 if job_metrics :
399419 lines .append ("- **Runner Details**:" )
0 commit comments