gitlab-labkit 4.6.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22a38c0fe1a2f28aa538b80348911947c6de64c16d7d870d24835332796faa99
4
- data.tar.gz: 6259d696d178f3008aefdf91de71731c81d460cef08259555829f15d8abd462c
3
+ metadata.gz: 6d2bc5199fdb961286c36547f3c359b9de03bb8dc8c6c260c2074c177d24fc79
4
+ data.tar.gz: '09d7ddd1b8e64f242ca95c63fce1e7d1a29a8d4d44a3908be2aad99e8df57207'
5
5
  SHA512:
6
- metadata.gz: dd404f717fe9cee20cb544ad23cd5aa3195683ac0efe8bf8363988fdb8dcacc4fcaa817ac6f4cb3fcdb4f2e3d193c6d93cc13deea0344067e44fac7f3c593fb3
7
- data.tar.gz: cc8d6bb5fe6fde8c17f969d4a708bd41901de5f9383f4821e3acf13a2eb35c240a80d18d3853ac5f29b689d7841df254f540233174646204358e698b9e0f643e
6
+ metadata.gz: 366832f3f6b6a1f76f2c4638c4303c3ad4c1c9065a876c9bb32a049fb3c94f214804f43ded0f5a30a65b87f3a1c9595e1cf05c85152a1eb9c6d8eed7228a1ddc
7
+ data.tar.gz: 45f429a193fa57913cc61b5fda09356abb2135eb1f596c8d6eb4c0982fe2d4a4012551a5dedd2e2576b27da6017a7632f00cd24991352c15be7716a72abd4957
data/.gitlab/CODEOWNERS CHANGED
@@ -1 +1 @@
1
- * @reprazent @andrewn @mkaeppler @ayufan @hmerscher @d.barrett @splattael @sankalp_gl @hardikgala @nindurkar @ashs2
1
+ * @reprazent @andrewn @mkaeppler @ayufan @hmerscher @sankalp_gl @hardikgala @nindurkar @ashs2
data/CODEOWNERS CHANGED
@@ -1,4 +1,4 @@
1
1
  # CODEOWNERS is used to lookup assignees for
2
2
  # Renovate Bot dependency change Merge Requests.
3
3
  # https://docs.renovatebot.com/configuration-options/#assigneesfromcodeowners
4
- * @reprazent @andrewn @mkaeppler @ayufan @hmerscher @d.barrett @splattael @e_forbes @M_Alvarez @mwoolf @sankalp_gl @hardikgala @nindurkar @ashs2
4
+ * @reprazent @andrewn @mkaeppler @ayufan @hmerscher @e_forbes @M_Alvarez @mwoolf @sankalp_gl @hardikgala @nindurkar @ashs2
@@ -121,6 +121,8 @@ different question than `check`. A matched `:skip` rule terminates `peek`
121
121
  the same way it terminates `check`: matched, `:allow`, no Redis read, no
122
122
  `info`.
123
123
 
124
+ A peek is counted by its own `peeks_total` counter. See [Metrics](#metrics).
125
+
124
126
  ### Clearing state
125
127
 
126
128
  `Limiter#clear(identifier)` deletes this limiter's counters for one
@@ -267,7 +269,7 @@ flowchart TD
267
269
  Return --> Check[Emit checks_total<br/>action, matched, error]
268
270
  SkipReturn --> Check
269
271
  Return2 --> Check
270
- Eval -. StandardError .-> Error[Emit errors_total +<br/>checks_total error=true<br/>log warn]
272
+ Eval -. StandardError .-> Error[Emit checks_total error=true<br/>log warn]
271
273
  Error --> ReturnErr([Return error=true<br/>action=:allow])
272
274
  ```
273
275
 
@@ -290,12 +292,6 @@ fail-open also emits nothing here — it was never evaluated; that check is
290
292
  visible via `checks_total{error="true"}` and the
291
293
  `rate_limit_missing_count_distinct` log, which carries the rule name.
292
294
 
293
- During the transition the deprecated `calls_total` counter is additionally
294
- emitted at every point the diagram emits `rule_evaluations_total` (with its
295
- historical per-rule semantics, including the `rule="unmatched"` placeholder
296
- after the loop). It is not shown above to keep the diagram legible; see the
297
- Metrics table below.
298
-
299
295
  ### Actions
300
296
 
301
297
  The rule's `action` describes what the rule does; the result's `action`
@@ -361,7 +357,7 @@ window, and it stops counting while that ban holds:
361
357
  written with `SET EX`. A callable is checked again each time it resolves, and
362
358
  a value under a second raises rather than reaching Redis. Being an error, it
363
359
  fails open like any other, so a rule whose `ban_for` cannot resolve stops
364
- blocking entirely: watch `errors_total` after changing one.
360
+ blocking entirely: watch `checks_total{error="true"}` after changing one.
365
361
 
366
362
  Take care combining `ban_for` with `cost:`. One expensive call can cross the
367
363
  limit on its own, and with a ban attached that costs the caller the whole ban
@@ -453,11 +449,13 @@ the most constraining one (see [Evaluation flow](#evaluation-flow)):
453
449
 
454
450
  ```ruby
455
451
  result.matched? # => true if some rule matched
452
+ result.skipped? # => true if a matched :skip rule bypassed the check
456
453
  result.exceeded? # => true if the reported rule's counter > limit
457
454
  result.action # => :block | :allow — what the caller should do
458
455
  result.block? # => result.action == :block
459
456
  result.rule # => the reported Rule, or nil
460
457
  result.error? # => true if Redis failed (see Fail-open)
458
+ result.degraded? # => true if a matched count_distinct rule was skipped
461
459
  result.info # => Result::Info or nil
462
460
  result.evaluations # => every counted Result::Evaluation, in rule order
463
461
  result.to_response_headers
@@ -493,12 +491,13 @@ of checks that encountered an error is `checks_total{error="true"}` over
493
491
  `count_distinct` key — that check completes (`action` and `matched` describe
494
492
  its outcome as usual), so `error="true"` is not exclusively fail-open traffic.
495
493
 
496
- The deprecated `gitlab_labkit_rate_limiter_errors_total` counter is still
497
- incremented on every fail-open (whole-check and per-rule `count_distinct`),
498
- and remains the only error metric for `peek`, which emits no `checks_total`
499
- (peek must not inflate the per-check counter). The follow-up MR that removes
500
- `errors_total` must first decide where `peek` errors goa dedicated peek
501
- metric, or logs only.
494
+ A failed-open `peek` emits `peeks_total{error="true"}`, so the fraction of
495
+ peeks that encountered an error is `peeks_total{error="true"}` over
496
+ `peeks_total`. A `peek` emits no `checks_total`.
497
+
498
+ `clear` emits no counter at all. A failure there is benign the counters it
499
+ could not delete expire on their own — so it is only logged, with the limiter
500
+ name in the `name` field.
502
501
 
503
502
  Metric emission itself is best-effort: a failure in the metrics stack never
504
503
  alters the verdict or breaks fail-open, and is logged at WARN with
@@ -514,8 +513,7 @@ flooding).
514
513
  |-----------------------------------------------------|---------|----------------------------------------------|----------------------------------------------------------------------|
515
514
  | `gitlab_labkit_rate_limiter_checks_total` | counter | `rate_limiter`, `action`, `matched`, `error` | Exactly one increment per `check` call, including fail-open. `action` is what the caller should do (`"allow"` or `"block"`); `matched` and `error` are `"true"`/`"false"`. |
516
515
  | `gitlab_labkit_rate_limiter_rule_evaluations_total` | counter | `rate_limiter`, `rule`, `action`, `result` | One increment per evaluated rule (plus one per matched `:skip` rule). `action` is the configured rule action (`"limit"`, `"log"`, `"skip"`); `result` is what the evaluation decided (`"allow"`, `"block"`, `"log"`, `"skip"`, `"banned"` — see the Actions table). |
517
- | `gitlab_labkit_rate_limiter_calls_total` | counter | `rate_limiter`, `rule`, `action` | **Deprecated** superseded by `checks_total` + `rule_evaluations_total`. Historical per-rule counter: one increment per counted rule (plus one per matched `:skip` rule), `rule="unmatched", action="allow"` when no rule matched. Emitted unchanged during the transition. |
518
- | `gitlab_labkit_rate_limiter_errors_total` | counter | `rate_limiter` | **Deprecated** — use `checks_total{error="true"}`. Fail-open events (any `StandardError` in the labkit path); still the only error metric for `peek`. |
516
+ | `gitlab_labkit_rate_limiter_peeks_total` | counter | `rate_limiter`, `error` | Exactly one increment per `peek` call, including fail-open. `error` is `"true"`/`"false"`. |
519
517
  | `gitlab_labkit_rate_limiter_limit` | gauge | `rate_limiter`, `rule` | Resolved limit at the last check (useful when `limit:` is callable). |
520
518
  | `gitlab_labkit_rate_limiter_period_seconds` | gauge | `rate_limiter`, `rule` | Resolved period at the last check. |
521
519
 
@@ -524,11 +522,6 @@ the request rate through a limiter — no exclusions or dedup needed. A single
524
522
  `check` call emits **one** `checks_total` increment and as many
525
523
  `rule_evaluations_total` increments as rules it evaluated (possibly zero).
526
524
 
527
- **Transition:** the deprecated `calls_total` and `errors_total` counters keep
528
- emitting exactly as before this split, so existing dashboards and alerts stay
529
- correct while consumers migrate to the new counters. Both are removed in a
530
- follow-up major release once nothing consumes them.
531
-
532
525
  ## Dev/test vs production guards
533
526
 
534
527
  `Limiter.new` and `Rule.new` validate names and configuration. In
@@ -124,7 +124,6 @@ module Labkit
124
124
  rescue StandardError => e
125
125
  # Intentionally broad: fail-open applies to any unexpected error (network,
126
126
  # timeout, OOM) not only Redis protocol errors.
127
- report_error_metrics
128
127
  log_error(e, identifier, cursor.rule)
129
128
  result = Result.error
130
129
  ensure
@@ -138,11 +137,12 @@ module Labkit
138
137
  # extended. A missing Redis key is treated as count=0 (matched, not exceeded).
139
138
  def peek(identifier, rule_context: nil)
140
139
  cursor = RuleCursor.new
141
- peek_rules(identifier, rule_context, cursor)
140
+ result = peek_rules(identifier, rule_context, cursor)
142
141
  rescue StandardError => e
143
- report_error_metrics
144
142
  log_error(e, identifier, cursor.rule)
145
- Result.error
143
+ result = Result.error
144
+ ensure
145
+ report_peek_metrics(result) if result
146
146
  end
147
147
 
148
148
  # Deletes this limiter's counters, and any bans, for one identifier.
@@ -175,7 +175,6 @@ module Labkit
175
175
 
176
176
  removed
177
177
  rescue StandardError => e
178
- report_error_metrics
179
178
  log_error(e, identifier, nil)
180
179
  removed
181
180
  end
@@ -203,14 +202,14 @@ module Labkit
203
202
  # most-constraining evaluation (ranking lives in Result::Evaluation#<=>).
204
203
  # cost is therefore debited from every matching rule, not just the first.
205
204
  #
206
- # Metrics: each evaluated rule emits rule_evaluations_total (plus the
207
- # deprecated per-rule calls_total); the per-check checks_total is
208
- # emitted once in #check. Full contract in the README's Metrics section.
205
+ # Metrics: each evaluated rule emits rule_evaluations_total; the per-check
206
+ # checks_total is emitted once in #check. Full contract in the README's
207
+ # Metrics section.
209
208
  #
210
209
  # SET-mode rules (rule.count_distinct set) that match but whose identifier
211
- # is missing the count_distinct key fail open + log + bump errors_total +
212
- # flag the Result, and the loop continues to the next rule (the rule is
213
- # treated as not applicable rather than aborting the whole evaluation).
210
+ # is missing the count_distinct key fail open + log + flag the Result, and
211
+ # the loop continues to the next rule (the rule is treated as not
212
+ # applicable rather than aborting the whole evaluation).
214
213
  #
215
214
  # Error handling stays whole-check (see #check): a raise part-way through
216
215
  # discards the results of the rules already evaluated, even though their
@@ -234,7 +233,6 @@ module Labkit
234
233
 
235
234
  if rule.count_distinct && missing_count_distinct_value?(rule, identifier)
236
235
  log_missing_count_distinct(rule, identifier)
237
- report_error_metrics
238
236
  result.degraded!
239
237
  next
240
238
  end
@@ -248,7 +246,6 @@ module Labkit
248
246
  # The loop is done, so anything raised from here on belongs to no rule.
249
247
  cursor.rule = nil
250
248
 
251
- report_unmatched_metrics unless result.matched?
252
249
  result
253
250
  rescue StandardError => e # binds e for the ensure
254
251
  raise
@@ -393,11 +390,10 @@ module Labkit
393
390
  Result::Evaluation.new(rule: rule, exceeded: count > resolved_limit, info: info)
394
391
  end
395
392
 
396
- # The limiter, rule and characteristics sit inside a Redis hash tag, so a
397
- # counter and its ban always hash to the same cluster slot. BAN_SCRIPT
398
- # touches both in one call, and Redis Cluster rejects a script whose keys
399
- # span slots. +suffix+ is appended outside the tag, so it cannot change
400
- # the slot and cannot collide with a characteristic value.
393
+ # The braces are the cluster hash tag: a counter and its ban must share a
394
+ # slot because BAN_SCRIPT touches both in one call. The rule name stays
395
+ # inside it so one identifier's keys spread across nodes instead of piling
396
+ # onto one. +suffix+ sits outside, so it cannot change the slot.
401
397
  def build_redis_key(rule, identifier, suffix = nil)
402
398
  key = "#{REDIS_KEY_PREFIX}:{#{@name}:#{rule.name}"
403
399
  rule.characteristics.each do |char|
@@ -566,12 +562,6 @@ module Labkit
566
562
  rule_labels.merge(action: evaluation.rule.action.to_s, result: evaluation_result(evaluation)),
567
563
  logger: @logger
568
564
  )
569
- # Deprecated dual emission - remove together with Metrics.calls_total.
570
- Metrics.safe_increment(
571
- :calls_total,
572
- rule_labels.merge(action: (evaluation.exceeded? ? evaluation.rule.action : :allow).to_s),
573
- logger: @logger
574
- )
575
565
  Metrics.safe_set(:limit_gauge, rule_labels, evaluation.info.resolved_limit, logger: @logger)
576
566
  Metrics.safe_set(:period_gauge, rule_labels, evaluation.info.resolved_period, logger: @logger)
577
567
  end
@@ -595,13 +585,6 @@ module Labkit
595
585
  { rate_limiter: @name, rule: rule.name, action: "skip", result: "skip" },
596
586
  logger: @logger
597
587
  )
598
- # Deprecated dual emission - remove together with Metrics.calls_total.
599
- Metrics.safe_increment(:calls_total, { rate_limiter: @name, rule: rule.name, action: "skip" }, logger: @logger)
600
- end
601
-
602
- # Deprecated dual emission - remove together with Metrics.calls_total.
603
- def report_unmatched_metrics
604
- Metrics.safe_increment(:calls_total, { rate_limiter: @name, rule: "unmatched", action: "allow" }, logger: @logger)
605
588
  end
606
589
 
607
590
  def report_check_metrics(result)
@@ -617,8 +600,12 @@ module Labkit
617
600
  )
618
601
  end
619
602
 
620
- def report_error_metrics
621
- Metrics.safe_increment(:errors_total, { rate_limiter: @name }, logger: @logger)
603
+ def report_peek_metrics(result)
604
+ Metrics.safe_increment(
605
+ :peeks_total,
606
+ { rate_limiter: @name, error: (result.error? || result.degraded?).to_s },
607
+ logger: @logger
608
+ )
622
609
  end
623
610
  end
624
611
  end
@@ -52,14 +52,12 @@ module Labkit
52
52
  )
53
53
  end
54
54
 
55
- # Deprecated: superseded by checks_total and rule_evaluations_total.
56
- # Emitted unchanged (once per matched rule, rule="unmatched" when none
57
- # matched) until consumers migrate; will get removed in a follow-up release.
58
- def calls_total
55
+ # Emitted exactly once per #peek call, including calls that fail open.
56
+ def peeks_total
59
57
  Labkit::Metrics::Client.counter(
60
- :gitlab_labkit_rate_limiter_calls_total,
61
- 'Total number of successful rate limit checks',
62
- { rate_limiter: nil, rule: nil, action: nil }
58
+ :gitlab_labkit_rate_limiter_peeks_total,
59
+ 'Total number of rate limit peeks',
60
+ { rate_limiter: nil, error: nil }
63
61
  )
64
62
  end
65
63
 
@@ -73,16 +71,6 @@ module Labkit
73
71
  )
74
72
  end
75
73
 
76
- # Deprecated: superseded by checks_total{error="true"}. Still emitted
77
- # because it remains the only error metric for #peek.
78
- def errors_total
79
- Labkit::Metrics::Client.counter(
80
- :gitlab_labkit_rate_limiter_errors_total,
81
- 'Total number of rate limit check errors',
82
- { rate_limiter: nil }
83
- )
84
- end
85
-
86
74
  def limit_gauge
87
75
  Labkit::Metrics::Client.gauge(
88
76
  :gitlab_labkit_rate_limiter_limit,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-labkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Newdigate