///|
pub(all) struct SketchBudgetPlan {
expected_events : Int
expected_unique : Int
accuracy_level : Int
count_min_width : Int
count_min_depth : Int
bloom_bits : Int
bloom_hashes : Int
hll_buckets : Int
topk_capacity : Int
reservoir_size : Int
estimated_cells : Int
} derive(Eq, Debug)
///|
pub fn sketch_budget_from_text(
input : String,
accuracy_level : Int,
) -> SketchBudgetPlan {
let events = parse_events(input)
let counter = exact_counter_from_items(events)
sketch_budget_plan(
events.length(),
exact_counter_unique(counter),
accuracy_level,
)
}
///|
pub fn sketch_budget_plan(
expected_events : Int,
expected_unique : Int,
accuracy_level : Int,
) -> SketchBudgetPlan {
let events = sketch_max(0, expected_events)
let unique = sketch_max(0, expected_unique)
let level = sketch_budget_clamp(accuracy_level, 1, 10)
let width = sketch_next_power_of_two(sketch_max(32, unique * level + 31))
let depth = sketch_budget_clamp(2 + level / 2, 3, 8)
let bloom_bits = sketch_next_power_of_two(sketch_max(64, unique * level * 8))
let bloom_hashes = sketch_budget_clamp(2 + level / 2, 2, 8)
let hll_buckets = sketch_next_power_of_two(
sketch_budget_clamp(level * 16, 16, 256),
)
let topk = sketch_budget_clamp(unique / 10 + level, 5, 100)
let reservoir = sketch_budget_clamp(level * 10 + events / 100, 10, 250)
{
expected_events: events,
expected_unique: unique,
accuracy_level: level,
count_min_width: width,
count_min_depth: depth,
bloom_bits,
bloom_hashes,
hll_buckets,
topk_capacity: topk,
reservoir_size: reservoir,
estimated_cells: width * depth +
bloom_bits +
hll_buckets +
topk * 3 +
reservoir,
}
}
///|
pub fn sketch_budget_markdown(plan : SketchBudgetPlan) -> String {
let out = StringBuilder()
out.write_string("# Sketch Budget Plan\n\n")
out.write_string("| metric | value |\n| --- | ---: |\n")
out.write_string(
"| expected events | " + plan.expected_events.to_string() + " |\n",
)
out.write_string(
"| expected unique | " + plan.expected_unique.to_string() + " |\n",
)
out.write_string(
"| accuracy level | " + plan.accuracy_level.to_string() + " |\n",
)
out.write_string(
"| estimated cells | " + plan.estimated_cells.to_string() + " |\n\n",
)
out.write_string("## Suggested Parameters\n\n")
out.write_string("| sketch | parameter | value |\n| --- | --- | ---: |\n")
out.write_string(
"| Count-Min | width | " + plan.count_min_width.to_string() + " |\n",
)
out.write_string(
"| Count-Min | depth | " + plan.count_min_depth.to_string() + " |\n",
)
out.write_string("| Bloom | bits | " + plan.bloom_bits.to_string() + " |\n")
out.write_string(
"| Bloom | hashes | " + plan.bloom_hashes.to_string() + " |\n",
)
out.write_string(
"| HLL-lite | buckets | " + plan.hll_buckets.to_string() + " |\n",
)
out.write_string(
"| Space-Saving | capacity | " + plan.topk_capacity.to_string() + " |\n",
)
out.write_string(
"| Reservoir | size | " + plan.reservoir_size.to_string() + " |\n",
)
out.to_string()
}
///|
pub fn sketch_budget_json(plan : SketchBudgetPlan) -> String {
let out = StringBuilder()
out.write_string("{")
out.write_string("\"expected_events\":" + plan.expected_events.to_string())
out.write_string(",\"expected_unique\":" + plan.expected_unique.to_string())
out.write_string(",\"accuracy_level\":" + plan.accuracy_level.to_string())
out.write_string(",\"count_min_width\":" + plan.count_min_width.to_string())
out.write_string(",\"count_min_depth\":" + plan.count_min_depth.to_string())
out.write_string(",\"bloom_bits\":" + plan.bloom_bits.to_string())
out.write_string(",\"bloom_hashes\":" + plan.bloom_hashes.to_string())
out.write_string(",\"hll_buckets\":" + plan.hll_buckets.to_string())
out.write_string(",\"topk_capacity\":" + plan.topk_capacity.to_string())
out.write_string(",\"reservoir_size\":" + plan.reservoir_size.to_string())
out.write_string(",\"estimated_cells\":" + plan.estimated_cells.to_string())
out.write_string("}")
out.to_string()
}
///|
pub fn sketch_budget_recommendations(
plan : SketchBudgetPlan,
) -> Array[SketchRecommendation] {
let items : Array[SketchRecommendation] = Array::new()
if plan.expected_events == 0 {
items.push(
sketch_recommendation(
"warning", "empty-budget-input", "Budget is based on empty input", "No events were observed when deriving sketch parameters.",
"Run the planner on a representative sample before publishing defaults.",
),
)
return items
}
if plan.expected_unique * 2 > plan.expected_events {
items.push(
sketch_recommendation(
"info", "budget-high-cardinality", "High-cardinality budget", "The unique count is large compared with total events.",
"Prefer HLL-lite and Bloom sizing guidance over exact in-memory maps.",
),
)
}
if plan.estimated_cells > 10000 {
items.push(
sketch_recommendation(
"warning",
"budget-large-memory",
"The suggested plan is relatively large",
"The current settings require " +
plan.estimated_cells.to_string() +
" integer/boolean cells.",
"Lower the accuracy level for browser demos or increase it for backend checks.",
),
)
}
if items.length() == 0 {
items.push(
sketch_recommendation(
"info", "budget-balanced", "Budget looks balanced", "The suggested parameters are moderate for the observed sample.",
"Use this plan as default CLI output and tune with a higher accuracy level if needed.",
),
)
}
items
}
///|
fn sketch_next_power_of_two(value : Int) -> Int {
let mut result = 1
let target = sketch_max(1, value)
while result < target {
result *= 2
}
result
}
///|
fn sketch_budget_clamp(value : Int, low : Int, high : Int) -> Int {
if value < low {
low
} else if value > high {
high
} else {
value
}
}