NAME
App::GHGen::CostEstimator - Estimate CI costs and savings
SYNOPSIS
use App::GHGen::CostEstimator qw(estimate_current_usage);
my $estimate = estimate_current_usage(\@workflows);
FUNCTIONS
estimate_current_usage($workflows)
Estimate current monthly CI usage based on workflow configurations.
Purpose
Load and analyse every workflow file in $workflows, then aggregate estimated runs, minutes, and cost into a single summary hash.
Arguments
$workflows(ArrayRef[Path::Tiny], required)-
Array reference of Path::Tiny objects pointing to YAML workflow files. Each file is loaded with
YAML::XS::LoadFile.
Returns
A hash reference with keys:
{
total_minutes => Num,
billable_minutes => Num, # 0 when within free tier (2 000 min/month)
monthly_cost => Num, # USD; 0 when within free tier
workflows => ArrayRef[ estimate_workflow_cost result ],
}
Side Effects
Reads each workflow file from disk via YAML::XS::LoadFile.
Usage Example
use App::GHGen::CostEstimator qw(estimate_current_usage);
use App::GHGen::Analyzer qw(find_workflows);
my @wfs = find_workflows();
my $usage = estimate_current_usage(\@wfs);
printf "Monthly cost: \$%.2f\n", $usage->{monthly_cost};
API SPECIFICATION
Input
{ workflows => { type => 'arrayref', required => 1 } }
Output
{
type => 'hashref',
keys => {
total_minutes => { type => 'scalar' },
billable_minutes => { type => 'scalar' },
monthly_cost => { type => 'scalar' },
workflows => { type => 'arrayref' },
},
}
FORMAL SPECIFICATION
FREE_TIER ≔ 2000
COST_PER_MIN ≔ 0.008
estimate_current_usage : seq Path → UsageSummary
total ≔ ∑ { estimate_workflow_cost(f).minutes_per_month ∣ f ∈ workflows }
billable ≔ max(0, total − FREE_TIER)
result ≔ {
total_minutes ↦ total,
billable_minutes ↦ billable,
monthly_cost ↦ billable × COST_PER_MIN,
workflows ↦ [ estimate_workflow_cost(f) ∣ f ∈ workflows ],
}
estimate_workflow_cost($workflow, $filename)
Estimate the monthly CI cost of a single parsed workflow.
Purpose
Compute estimated runs per month, average run duration, and total minute usage for one workflow, based on its trigger configuration and step complexity.
Arguments
$workflow(HashRef, required)-
A parsed workflow hash (e.g. from
YAML::XS::LoadFile). $filename(Str, required)-
Filename string used as a display label when
$workflow->{name}is absent.
Returns
A hash reference:
{
name => Str,
file => Str,
runs_per_month => Num,
minutes_per_run => Num,
minutes_per_month => Num, # == runs_per_month * minutes_per_run
}
Side Effects
None. Pure function.
Usage Example
my $cost = estimate_workflow_cost($workflow, 'ci.yml');
printf "%s: %d min/month\n", $cost->{name}, $cost->{minutes_per_month};
API SPECIFICATION
Input
{
workflow => { type => 'hashref', required => 1 },
filename => { type => 'scalar', required => 1 },
}
Output
{
type => 'hashref',
keys => {
name => { type => 'scalar' },
file => { type => 'scalar' },
runs_per_month => { type => 'scalar' },
minutes_per_run => { type => 'scalar' },
minutes_per_month => { type => 'scalar' },
},
}
FORMAL SPECIFICATION
estimate_workflow_cost : Workflow × ℤ* → CostRecord
runs ≔ estimate_runs_per_month(w)
dur ≔ estimate_duration(w)
result ≔ {
name ↦ w.name ?? f,
file ↦ f,
runs_per_month ↦ runs,
minutes_per_run ↦ dur,
minutes_per_month ↦ runs × dur,
}
invariant: result.minutes_per_month = result.runs_per_month × result.minutes_per_run
estimate_savings($issues, $workflows)
Estimate potential CI-minute and cost savings from resolving a set of issues.
Purpose
For each issue in $issues, compute how many CI minutes per month would be saved by fixing it. Optionally uses $workflows to proportion savings against actual current usage.
Arguments
$issues(ArrayRef[HashRef], required)-
Array reference of issue hashes, each with at least
typeandmessage. $workflows(ArrayRef[Path::Tiny], optional, default[])-
Workflow files used to compute current usage for percentage calculations.
Returns
A hash reference:
{
minutes => Int, # total minutes saved per month
percentage => Int, # 0–100; 0 when no current usage available
cost => Str, # formatted as "NN.NN" (USD)
details => ArrayRef[{ description => Str, minutes => Int, issue_type => Str }],
}
Side Effects
May read workflow files from disk when $workflows is non-empty.
Usage Example
my $savings = estimate_savings(\@issues, \@workflow_paths);
printf "Save %d min/month (\$%s)\n",
$savings->{minutes}, $savings->{cost};
API SPECIFICATION
Input
{
issues => { type => 'arrayref', required => 1 },
workflows => { type => 'arrayref', default => [] },
}
Output
{
type => 'hashref',
keys => {
minutes => { type => 'scalar' },
percentage => { type => 'scalar' },
cost => { type => 'scalar' },
details => { type => 'arrayref' },
},
}
FORMAL SPECIFICATION
estimate_savings : seq Issue × seq Path → SavingsSummary
savings(i) ≔
i.type = performance ∧ i.message =~ /caching/ → 75
i.type = cost ∧ i.message =~ /concurrency/ → 50 | usage×0.15
i.type = cost ∧ i.message =~ /triggers/ → 100 | usage×0.25
otherwise → 0
total ≔ ∑ { savings(i) ∣ i ∈ issues }
result ≔ {
minutes ↦ floor(total),
percentage ↦ floor(total / usage × 100) | 30 (if total > 0, no usage),
cost ↦ sprintf("%.2f", total × 0.008),
details ↦ [ { description, minutes, issue_type } ∣ savings(i) > 0 ],
}
AUTHOR
Nigel Horne <njh@nigelhorne.com>
LICENSE
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 94:
Non-ASCII character seen before =encoding in '≔'. Assuming UTF-8