Module: Perfgate::Comparison::WorkloadComparison

Defined in:
lib/perfgate/comparison/workload_comparison.rb

Overview

Matches baseline/candidate workloads by id and produces each workload's decision entry in the comparison-result document (spec 14.2), including the deterministic per-workload diagnostics (spec 20.3). Split out of Engine to keep both modules under RuboCop's module-length limit.

Constant Summary collapse

SAMPLE_KEY_TO_METRIC =

Maps the raw sample keys instrumentation writes (spec section 13) to the metric names practical_thresholds/fingerprint config uses (spec section 16.4). gc_* keys are deliberately left unmapped: GC activity is diagnostic-only and never drives a decision.

{
  "duration_ns" => "duration",
  "sql_count" => "sql_count",
  "sql_duration_ns" => "sql_duration",
  "allocations" => "allocations"
}.freeze

Class Method Summary collapse

Class Method Details

.compare_all(baseline_run, candidate_run, config) ⇒ Object



27
28
29
30
31
32
33
34
# File 'lib/perfgate/comparison/workload_comparison.rb', line 27

def compare_all(baseline_run, candidate_run, config)
  baseline_by_id = index_by_id(baseline_run)
  candidate_by_id = index_by_id(candidate_run)

  (baseline_by_id.keys | candidate_by_id.keys).map do |id|
    compare_one(id, baseline_by_id[id], candidate_by_id[id], config)
  end
end

.compare_one(id, baseline_workload, candidate_workload, config) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
# File 'lib/perfgate/comparison/workload_comparison.rb', line 40

def compare_one(id, baseline_workload, candidate_workload, config)
  return missing_workload_result(id, "new_workload") unless baseline_workload
  return missing_workload_result(id, "removed_workload") unless candidate_workload

  definition_changed = baseline_workload["definition_hash"] != candidate_workload["definition_hash"]
  return incomparable_workload_result(id) if definition_changed

  metrics = metric_decisions(baseline_workload, candidate_workload, config)
  { "id" => id, "decision" => workload_decision(metrics), "metrics" => metrics,
    "diagnostics" => Diagnostics.for_workload(metrics) }
end

.incomparable_workload_result(id) ⇒ Object



61
62
63
64
# File 'lib/perfgate/comparison/workload_comparison.rb', line 61

def incomparable_workload_result(id)
  { "id" => id, "decision" => "incomparable", "metrics" => {},
    "diagnostics" => ["Workload definition changed since the baseline run; comparison skipped."] }
end

.index_by_id(run) ⇒ Object



36
37
38
# File 'lib/perfgate/comparison/workload_comparison.rb', line 36

def index_by_id(run)
  run.fetch("workloads", []).to_h { |workload| [workload["id"], workload] }
end

.metric_decisions(baseline_workload, candidate_workload, config) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/perfgate/comparison/workload_comparison.rb', line 66

def metric_decisions(baseline_workload, candidate_workload, config)
  sample_keys(baseline_workload, candidate_workload).each_with_object({}) do |sample_key, acc|
    metric = SAMPLE_KEY_TO_METRIC[sample_key]
    next unless metric

    baseline_samples = samples_for(baseline_workload, sample_key)
    candidate_samples = samples_for(candidate_workload, sample_key)
    next if baseline_samples.empty? || candidate_samples.empty?

    acc[metric] = MetricDecision.call(metric: metric, baseline_samples: baseline_samples,
                                      candidate_samples: candidate_samples, config: config)
  end
end

.missing_workload_result(id, reason) ⇒ Object



52
53
54
55
56
57
58
59
# File 'lib/perfgate/comparison/workload_comparison.rb', line 52

def missing_workload_result(id, reason)
  message = if reason == "new_workload"
              "new workload with no prior baseline run"
            else
              "workload removed since the baseline run"
            end
  { "id" => id, "decision" => reason, "metrics" => {}, "diagnostics" => [message] }
end

.sample_keys(baseline_workload, candidate_workload) ⇒ Object



80
81
82
# File 'lib/perfgate/comparison/workload_comparison.rb', line 80

def sample_keys(baseline_workload, candidate_workload)
  (baseline_workload.fetch("summary", {}).keys + candidate_workload.fetch("summary", {}).keys).uniq
end

.samples_for(workload, sample_key) ⇒ Object



84
85
86
# File 'lib/perfgate/comparison/workload_comparison.rb', line 84

def samples_for(workload, sample_key)
  workload.fetch("samples", []).filter_map { |sample| sample[sample_key] }
end

.workload_decision(metrics) ⇒ Object



88
89
90
91
92
93
94
95
# File 'lib/perfgate/comparison/workload_comparison.rb', line 88

def workload_decision(metrics)
  decisions = metrics.values.map { |m| m["decision"] }
  return "fail" if decisions.include?("fail")
  return "warn" if decisions.include?("warn")
  return "inconclusive" if decisions.include?("inconclusive") && decisions.all? { |d| d != "pass" }

  "pass"
end