///|
/// Performance budget configuration.
pub(all) struct HarBudget {
max_entries : Int
max_total_bytes : Int
max_total_time : Int
max_average_time : Int
max_failed : Int
max_redirects : Int
max_slowest_time : Int
} derive(Eq, Debug)
///|
pub(all) struct BudgetResult {
passed : Bool
diagnostics : Array[Diagnostic]
} derive(Eq, Debug)
///|
pub fn HarBudget::default() -> HarBudget {
HarBudget::{
max_entries: 200,
max_total_bytes: 5_000_000,
max_total_time: 30_000,
max_average_time: 500,
max_failed: 0,
max_redirects: 10,
max_slowest_time: 2000,
}
}
///|
pub fn HarBudget::strict() -> HarBudget {
HarBudget::{
max_entries: 80,
max_total_bytes: 1_000_000,
max_total_time: 10_000,
max_average_time: 250,
max_failed: 0,
max_redirects: 2,
max_slowest_time: 1000,
}
}
///|
pub fn check_budget(archive : HarArchive, budget : HarBudget) -> BudgetResult {
let summary = analyze_archive(archive)
let diagnostics : Array[Diagnostic] = []
budget_check(
summary.entry_count,
budget.max_entries,
"$.log.entries",
"budget.entries",
"entry count exceeds budget",
diagnostics,
)
budget_check(
summary.total_bytes,
budget.max_total_bytes,
"$.log.entries",
"budget.bytes",
"total bytes exceed budget",
diagnostics,
)
budget_check(
summary.total_time,
budget.max_total_time,
"$.log.entries",
"budget.totalTime",
"total time exceeds budget",
diagnostics,
)
budget_check(
summary.average_time,
budget.max_average_time,
"$.log.entries",
"budget.averageTime",
"average request time exceeds budget",
diagnostics,
)
budget_check(
summary.failed_count,
budget.max_failed,
"$.log.entries",
"budget.failed",
"failed request count exceeds budget",
diagnostics,
)
budget_check(
summary.redirected_count,
budget.max_redirects,
"$.log.entries",
"budget.redirects",
"redirect count exceeds budget",
diagnostics,
)
budget_check(
summary.longest_time,
budget.max_slowest_time,
"$.log.entries",
"budget.slowest",
"slowest request exceeds budget",
diagnostics,
)
BudgetResult::{ passed: diagnostics.is_empty(), diagnostics }
}
///|
fn budget_check(
value : Int,
limit : Int,
path : String,
code : String,
message : String,
out : Array[Diagnostic],
) -> Unit {
if value > limit {
out.push(Diagnostic::{
severity: Error,
code,
path,
message: message + ": " + value.to_string() + " > " + limit.to_string(),
})
}
}
///|
pub fn render_budget_result(result : BudgetResult) -> String {
if result.passed {
"Performance budget passed.\n"
} else {
"Performance budget failed.\n" + render_diagnostics(result.diagnostics)
}
}
///|
pub fn budget_score(archive : HarArchive, budget : HarBudget) -> Int {
let summary = analyze_archive(archive)
let mut score = 100
score = score - penalty(summary.entry_count, budget.max_entries, 10)
score = score - penalty(summary.total_bytes, budget.max_total_bytes, 20)
score = score - penalty(summary.average_time, budget.max_average_time, 20)
score = score - penalty(summary.failed_count, budget.max_failed, 30)
score = score - penalty(summary.redirected_count, budget.max_redirects, 5)
score = score - penalty(summary.longest_time, budget.max_slowest_time, 15)
if score < 0 {
0
} else {
score
}
}
///|
fn penalty(value : Int, limit : Int, weight : Int) -> Int {
if value <= limit {
0
} else if limit <= 0 {
weight
} else {
let over = value - limit
let scaled = over * weight / limit
if scaled > weight {
weight
} else {
scaled
}
}
}