kino 0.4.0 → 0.6.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.
@@ -120,7 +120,11 @@ pub fn stats_json(s: &StatsSnapshot) -> String {
120
120
  )
121
121
  .expect("writing to a String cannot fail");
122
122
  if let Some(depths) = &s.lane_depths {
123
- let list = depths.iter().map(|d| d.to_string()).collect::<Vec<_>>().join(",");
123
+ let list = depths
124
+ .iter()
125
+ .map(|d| d.to_string())
126
+ .collect::<Vec<_>>()
127
+ .join(",");
124
128
  write!(out, r#","lane_depths":[{list}]"#).expect("writing to a String cannot fail");
125
129
  }
126
130
  out.push_str(r#","worker_status":["#);
@@ -146,7 +150,8 @@ pub fn stats_json(s: &StatsSnapshot) -> String {
146
150
  write!(
147
151
  out,
148
152
  r#","quarantined":{},"state":"{}","version":"{}"}}"#,
149
- s.quarantined_count, s.state_name(),
153
+ s.quarantined_count,
154
+ s.state_name(),
150
155
  env!("CARGO_PKG_VERSION")
151
156
  )
152
157
  .expect("writing to a String cannot fail");
@@ -156,8 +161,11 @@ pub fn stats_json(s: &StatsSnapshot) -> String {
156
161
  /// One HELP/TYPE/sample triple for a single-value metric.
157
162
  fn metric(out: &mut String, name: &str, kind: &str, help: &str, value: impl std::fmt::Display) {
158
163
  use std::fmt::Write;
159
- writeln!(out, "# HELP {name} {help}\n# TYPE {name} {kind}\n{name} {value}")
160
- .expect("writing to a String cannot fail");
164
+ writeln!(
165
+ out,
166
+ "# HELP {name} {help}\n# TYPE {name} {kind}\n{name} {value}"
167
+ )
168
+ .expect("writing to a String cannot fail");
161
169
  }
162
170
 
163
171
  /// One HELP/TYPE header followed by one `name{label="key"} value` line per
@@ -171,9 +179,11 @@ fn series<K: std::fmt::Display, V: std::fmt::Display>(
171
179
  rows: impl Iterator<Item = (K, V)>,
172
180
  ) {
173
181
  use std::fmt::Write;
174
- writeln!(out, "# HELP {name} {help}\n# TYPE {name} {kind}").expect("writing to a String cannot fail");
182
+ writeln!(out, "# HELP {name} {help}\n# TYPE {name} {kind}")
183
+ .expect("writing to a String cannot fail");
175
184
  for (key, value) in rows {
176
- writeln!(out, "{name}{{{label}=\"{key}\"}} {value}").expect("writing to a String cannot fail");
185
+ writeln!(out, "{name}{{{label}=\"{key}\"}} {value}")
186
+ .expect("writing to a String cannot fail");
177
187
  }
178
188
  }
179
189
 
@@ -181,40 +191,138 @@ fn series<K: std::fmt::Display, V: std::fmt::Display>(
181
191
  pub fn metrics_text(s: &StatsSnapshot) -> String {
182
192
  use std::fmt::Write;
183
193
  let mut out = String::with_capacity(1024);
184
- metric(&mut out, "kino_requests_served_total", "counter", "Requests handed to Ruby workers.", s.served);
185
- metric(&mut out, "kino_requests_rejected_total", "counter", "Requests rejected with a 503.", s.rejected);
186
- metric(&mut out, "kino_request_timeouts_total", "counter", "Responses past the request timeout (client got a 504).", s.timeouts);
187
- metric(&mut out, "kino_worker_respawns_total", "counter", "Crashed workers respawned by the supervisor.", s.respawns);
188
- metric(&mut out, "kino_queue_depth", "gauge", "Requests waiting for a worker.", s.queued);
189
- metric(&mut out, "kino_requests_in_flight", "gauge", "Requests currently inside Ruby workers.", s.in_flight);
190
- metric(&mut out, "kino_workers", "gauge", "Configured worker count.", s.workers);
191
- metric(&mut out, "kino_threads_per_worker", "gauge", "Configured threads per worker.", s.threads);
192
- metric(&mut out, "kino_ready", "gauge", "1 when serving, 0 while booting or draining.",
193
- if s.state == STATE_READY { "1" } else { "0" });
194
+ metric(
195
+ &mut out,
196
+ "kino_requests_served_total",
197
+ "counter",
198
+ "Requests handed to Ruby workers.",
199
+ s.served,
200
+ );
201
+ metric(
202
+ &mut out,
203
+ "kino_requests_rejected_total",
204
+ "counter",
205
+ "Requests rejected with a 503.",
206
+ s.rejected,
207
+ );
208
+ metric(
209
+ &mut out,
210
+ "kino_request_timeouts_total",
211
+ "counter",
212
+ "Responses past the request timeout (client got a 504).",
213
+ s.timeouts,
214
+ );
215
+ metric(
216
+ &mut out,
217
+ "kino_worker_respawns_total",
218
+ "counter",
219
+ "Crashed workers respawned by the supervisor.",
220
+ s.respawns,
221
+ );
222
+ metric(
223
+ &mut out,
224
+ "kino_queue_depth",
225
+ "gauge",
226
+ "Requests waiting for a worker.",
227
+ s.queued,
228
+ );
229
+ metric(
230
+ &mut out,
231
+ "kino_requests_in_flight",
232
+ "gauge",
233
+ "Requests currently inside Ruby workers.",
234
+ s.in_flight,
235
+ );
236
+ metric(
237
+ &mut out,
238
+ "kino_workers",
239
+ "gauge",
240
+ "Configured worker count.",
241
+ s.workers,
242
+ );
243
+ metric(
244
+ &mut out,
245
+ "kino_threads_per_worker",
246
+ "gauge",
247
+ "Configured threads per worker.",
248
+ s.threads,
249
+ );
250
+ metric(
251
+ &mut out,
252
+ "kino_ready",
253
+ "gauge",
254
+ "1 when serving, 0 while booting or draining.",
255
+ if s.state == STATE_READY { "1" } else { "0" },
256
+ );
194
257
  if let Some(depths) = &s.lane_depths {
195
- series(&mut out, "kino_lane_depth", "gauge", "Queued requests in each worker lane.", "lane",
196
- depths.iter().enumerate().map(|(lane, depth)| (lane, *depth)));
197
- }
198
- series(&mut out, "kino_worker_requests_served_total", "counter", "Requests handed to each dispatch slot.", "worker",
199
- s.worker_status.iter().map(|w| (w.index, w.served)));
200
- series(&mut out, "kino_worker_in_flight", "gauge", "Requests executing in each dispatch slot.", "worker",
201
- s.worker_status.iter().map(|w| (w.index, w.in_flight)));
202
- series(&mut out, "kino_worker_busy_ms", "gauge", "Age in ms of the current in-flight request per slot (0 when idle).", "worker",
203
- s.worker_status.iter().map(|w| (w.index, w.busy_ms)));
204
- metric(&mut out, "kino_quarantined_workers", "gauge", "Dispatch slots abandoned as wedged.", s.quarantined_count);
205
- metric(&mut out, "kino_quarantine_replacements_total", "counter", "Replacement workers spawned after a wedge.", s.quarantine_replacements);
258
+ series(
259
+ &mut out,
260
+ "kino_lane_depth",
261
+ "gauge",
262
+ "Queued requests in each worker lane.",
263
+ "lane",
264
+ depths
265
+ .iter()
266
+ .enumerate()
267
+ .map(|(lane, depth)| (lane, *depth)),
268
+ );
269
+ }
270
+ series(
271
+ &mut out,
272
+ "kino_worker_requests_served_total",
273
+ "counter",
274
+ "Requests handed to each dispatch slot.",
275
+ "worker",
276
+ s.worker_status.iter().map(|w| (w.index, w.served)),
277
+ );
278
+ series(
279
+ &mut out,
280
+ "kino_worker_in_flight",
281
+ "gauge",
282
+ "Requests executing in each dispatch slot.",
283
+ "worker",
284
+ s.worker_status.iter().map(|w| (w.index, w.in_flight)),
285
+ );
286
+ series(
287
+ &mut out,
288
+ "kino_worker_busy_ms",
289
+ "gauge",
290
+ "Age in ms of the current in-flight request per slot (0 when idle).",
291
+ "worker",
292
+ s.worker_status.iter().map(|w| (w.index, w.busy_ms)),
293
+ );
294
+ metric(
295
+ &mut out,
296
+ "kino_quarantined_workers",
297
+ "gauge",
298
+ "Dispatch slots abandoned as wedged.",
299
+ s.quarantined_count,
300
+ );
301
+ metric(
302
+ &mut out,
303
+ "kino_quarantine_replacements_total",
304
+ "counter",
305
+ "Replacement workers spawned after a wedge.",
306
+ s.quarantine_replacements,
307
+ );
206
308
  let h = &s.queue_histogram;
207
309
  out.push_str("# HELP kino_request_queue_seconds Seconds requests waited in the queue before a worker admitted them.\n# TYPE kino_request_queue_seconds histogram\n");
208
310
  let mut cumulative = 0u64;
209
311
  for (i, bound_us) in crate::registry::QUEUE_BOUNDS_US.iter().enumerate() {
210
312
  cumulative += h.buckets[i];
211
313
  let le = *bound_us as f64 / 1_000_000.0;
212
- writeln!(out, "kino_request_queue_seconds_bucket{{le=\"{le}\"}} {cumulative}")
213
- .expect("writing to a String cannot fail");
314
+ writeln!(
315
+ out,
316
+ "kino_request_queue_seconds_bucket{{le=\"{le}\"}} {cumulative}"
317
+ )
318
+ .expect("writing to a String cannot fail");
214
319
  }
215
320
  let total = cumulative + h.overflow;
216
- writeln!(out, "kino_request_queue_seconds_bucket{{le=\"+Inf\"}} {total}")
217
- .expect("writing to a String cannot fail");
321
+ writeln!(
322
+ out,
323
+ "kino_request_queue_seconds_bucket{{le=\"+Inf\"}} {total}"
324
+ )
325
+ .expect("writing to a String cannot fail");
218
326
  writeln!(out, "kino_request_queue_seconds_sum {}", h.sum_seconds())
219
327
  .expect("writing to a String cannot fail");
220
328
  writeln!(out, "kino_request_queue_seconds_count {total}")
@@ -354,7 +462,11 @@ pub fn start(
354
462
  };
355
463
  control_registry().lock().insert(
356
464
  id,
357
- ControlHandle { stop_tx, join, unix_path },
465
+ ControlHandle {
466
+ stop_tx,
467
+ join,
468
+ unix_path,
469
+ },
358
470
  );
359
471
  Ok(port)
360
472
  }
@@ -380,7 +492,10 @@ fn run(
380
492
  token: Option<String>,
381
493
  stop_rx: tokio::sync::watch::Receiver<bool>,
382
494
  ) {
383
- let runtime = match tokio::runtime::Builder::new_current_thread().enable_all().build() {
495
+ let runtime = match tokio::runtime::Builder::new_current_thread()
496
+ .enable_all()
497
+ .build()
498
+ {
384
499
  Ok(runtime) => runtime,
385
500
  Err(e) => return native_error(format!("control runtime failed: {e}")),
386
501
  };
@@ -423,7 +538,9 @@ async fn serve(
423
538
  ($accepted:expr) => {
424
539
  match $accepted {
425
540
  Ok((stream, _)) => {
426
- let Ok(permit) = permits.clone().try_acquire_owned() else { continue };
541
+ let Ok(permit) = permits.clone().try_acquire_owned() else {
542
+ continue;
543
+ };
427
544
  spawn_connection(stream, permit, server.clone(), token.clone());
428
545
  }
429
546
  Err(_) => tokio::time::sleep(Duration::from_millis(100)).await,
@@ -479,8 +596,13 @@ fn handle(
479
596
  .get(hyper::header::AUTHORIZATION)
480
597
  .and_then(|v| v.to_str().ok());
481
598
  let snapshot = StatsSnapshot::take(server);
482
- let (status, content_type, body) =
483
- route(req.method().as_str(), req.uri().path(), authorization, token, &snapshot);
599
+ let (status, content_type, body) = route(
600
+ req.method().as_str(),
601
+ req.uri().path(),
602
+ authorization,
603
+ token,
604
+ &snapshot,
605
+ );
484
606
  let mut builder = hyper::Response::builder()
485
607
  .status(status)
486
608
  .header("content-type", content_type);
@@ -503,11 +625,28 @@ mod tests {
503
625
 
504
626
  fn snapshot(state: u8) -> StatsSnapshot {
505
627
  StatsSnapshot {
506
- mode: "ractor".to_string(), lanes: false, workers: 8, threads: 1,
507
- batch: 1, respawns: 2, queued: 3, in_flight: 4, served: 100,
508
- rejected: 5, timeouts: 6, lane_depths: None, state, worker_status: vec![],
509
- quarantined_count: 0, quarantine_replacements: 0,
510
- queue_histogram: crate::registry::QueueHistogramSnapshot { buckets: [0; crate::registry::QUEUE_BOUNDS_US.len()], overflow: 0, sum_us: 0, count: 0 },
628
+ mode: "ractor".to_string(),
629
+ lanes: false,
630
+ workers: 8,
631
+ threads: 1,
632
+ batch: 1,
633
+ respawns: 2,
634
+ queued: 3,
635
+ in_flight: 4,
636
+ served: 100,
637
+ rejected: 5,
638
+ timeouts: 6,
639
+ lane_depths: None,
640
+ state,
641
+ worker_status: vec![],
642
+ quarantined_count: 0,
643
+ quarantine_replacements: 0,
644
+ queue_histogram: crate::registry::QueueHistogramSnapshot {
645
+ buckets: [0; crate::registry::QUEUE_BOUNDS_US.len()],
646
+ overflow: 0,
647
+ sum_us: 0,
648
+ count: 0,
649
+ },
511
650
  }
512
651
  }
513
652
 
@@ -515,10 +654,18 @@ mod tests {
515
654
  fn stats_json_reports_every_field_and_the_state_name() {
516
655
  let json = stats_json(&snapshot(crate::registry::STATE_READY));
517
656
  for needle in [
518
- r#""mode":"ractor""#, r#""lanes":false"#, r#""workers":8"#,
519
- r#""threads":1"#, r#""batch":1"#, r#""respawns":2"#,
520
- r#""queued":3"#, r#""in_flight":4"#, r#""served":100"#,
521
- r#""rejected":5"#, r#""timeouts":6"#, r#""state":"ready""#,
657
+ r#""mode":"ractor""#,
658
+ r#""lanes":false"#,
659
+ r#""workers":8"#,
660
+ r#""threads":1"#,
661
+ r#""batch":1"#,
662
+ r#""respawns":2"#,
663
+ r#""queued":3"#,
664
+ r#""in_flight":4"#,
665
+ r#""served":100"#,
666
+ r#""rejected":5"#,
667
+ r#""timeouts":6"#,
668
+ r#""state":"ready""#,
522
669
  r#""version":""#,
523
670
  ] {
524
671
  assert!(json.contains(needle), "missing {needle} in {json}");
@@ -592,7 +739,10 @@ mod tests {
592
739
 
593
740
  // The token guards stats and metrics; the probes stay open.
594
741
  assert_eq!(route("GET", "/stats", None, Some("t"), &ready).0, 401);
595
- assert_eq!(route("GET", "/metrics", Some("Bearer t"), Some("t"), &ready).0, 200);
742
+ assert_eq!(
743
+ route("GET", "/metrics", Some("Bearer t"), Some("t"), &ready).0,
744
+ 200
745
+ );
596
746
  assert_eq!(route("GET", "/ready", None, Some("t"), &ready).0, 200);
597
747
  assert_eq!(route("GET", "/live", None, Some("t"), &ready).0, 200);
598
748
  }
@@ -601,8 +751,20 @@ mod tests {
601
751
  fn stats_json_emits_worker_status_array() {
602
752
  let mut s = snapshot(crate::registry::STATE_READY);
603
753
  s.worker_status = vec![
604
- WorkerStat { index: 0, served: 10, in_flight: 1, busy_ms: 4, quarantined: false },
605
- WorkerStat { index: 1, served: 7, in_flight: 0, busy_ms: 0, quarantined: false },
754
+ WorkerStat {
755
+ index: 0,
756
+ served: 10,
757
+ in_flight: 1,
758
+ busy_ms: 4,
759
+ quarantined: false,
760
+ },
761
+ WorkerStat {
762
+ index: 1,
763
+ served: 7,
764
+ in_flight: 0,
765
+ busy_ms: 0,
766
+ quarantined: false,
767
+ },
606
768
  ];
607
769
  let json = stats_json(&s);
608
770
  assert!(json.contains(r#""worker_status":[{"index":0,"served":10,"in_flight":1,"busy_ms":4,"quarantined":false},{"index":1,"served":7,"in_flight":0,"busy_ms":0,"quarantined":false}]"#), "got {json}");
@@ -618,8 +780,20 @@ mod tests {
618
780
  fn metrics_text_emits_per_worker_series() {
619
781
  let mut s = snapshot(crate::registry::STATE_READY);
620
782
  s.worker_status = vec![
621
- WorkerStat { index: 0, served: 10, in_flight: 1, busy_ms: 4, quarantined: false },
622
- WorkerStat { index: 1, served: 7, in_flight: 0, busy_ms: 0, quarantined: false },
783
+ WorkerStat {
784
+ index: 0,
785
+ served: 10,
786
+ in_flight: 1,
787
+ busy_ms: 4,
788
+ quarantined: false,
789
+ },
790
+ WorkerStat {
791
+ index: 1,
792
+ served: 7,
793
+ in_flight: 0,
794
+ busy_ms: 0,
795
+ quarantined: false,
796
+ },
623
797
  ];
624
798
  let text = metrics_text(&s);
625
799
  assert!(text.contains("# TYPE kino_worker_requests_served_total counter"));
@@ -635,9 +809,15 @@ mod tests {
635
809
  server.register_worker();
636
810
  {
637
811
  let slots = server.slots.read();
638
- slots[0].in_flight.store(1, std::sync::atomic::Ordering::Relaxed);
639
- slots[0].last_started_ms.store(0, std::sync::atomic::Ordering::Relaxed);
640
- slots[0].quarantined.store(true, std::sync::atomic::Ordering::Relaxed);
812
+ slots[0]
813
+ .in_flight
814
+ .store(1, std::sync::atomic::Ordering::Relaxed);
815
+ slots[0]
816
+ .last_started_ms
817
+ .store(0, std::sync::atomic::Ordering::Relaxed);
818
+ slots[0]
819
+ .quarantined
820
+ .store(true, std::sync::atomic::Ordering::Relaxed);
641
821
  }
642
822
  let rows = collect_worker_status(&server);
643
823
  assert!(rows[0].quarantined);
@@ -649,12 +829,30 @@ mod tests {
649
829
  let mut s = snapshot(crate::registry::STATE_READY);
650
830
  s.quarantined_count = 1;
651
831
  s.worker_status = vec![
652
- WorkerStat { index: 0, served: 3, in_flight: 1, busy_ms: 0, quarantined: true },
653
- WorkerStat { index: 1, served: 9, in_flight: 1, busy_ms: 5, quarantined: false },
832
+ WorkerStat {
833
+ index: 0,
834
+ served: 3,
835
+ in_flight: 1,
836
+ busy_ms: 0,
837
+ quarantined: true,
838
+ },
839
+ WorkerStat {
840
+ index: 1,
841
+ served: 9,
842
+ in_flight: 1,
843
+ busy_ms: 5,
844
+ quarantined: false,
845
+ },
654
846
  ];
655
847
  let json = stats_json(&s);
656
- assert!(json.contains(r#""quarantined":1"#), "top-level count: {json}");
657
- assert!(json.contains(r#"{"index":0,"served":3,"in_flight":1,"busy_ms":0,"quarantined":true}"#), "{json}");
848
+ assert!(
849
+ json.contains(r#""quarantined":1"#),
850
+ "top-level count: {json}"
851
+ );
852
+ assert!(
853
+ json.contains(r#"{"index":0,"served":3,"in_flight":1,"busy_ms":0,"quarantined":true}"#),
854
+ "{json}"
855
+ );
658
856
  assert!(json.contains(r#""quarantined":false"#));
659
857
  }
660
858
 
@@ -677,7 +875,10 @@ mod tests {
677
875
  buckets[0] = 3; // <= 0.0005s
678
876
  buckets[2] = 1; // <= 0.0025s
679
877
  s.queue_histogram = crate::registry::QueueHistogramSnapshot {
680
- buckets, overflow: 1, sum_us: 3 * 100 + 2_000 + 20_000_000, count: 5,
878
+ buckets,
879
+ overflow: 1,
880
+ sum_us: 3 * 100 + 2_000 + 20_000_000,
881
+ count: 5,
681
882
  };
682
883
  let text = metrics_text(&s);
683
884
  assert!(text.contains("# TYPE kino_request_queue_seconds histogram"));
@@ -693,9 +894,15 @@ mod tests {
693
894
  fn stats_json_reports_queue_time() {
694
895
  let mut s = snapshot(crate::registry::STATE_READY);
695
896
  s.queue_histogram = crate::registry::QueueHistogramSnapshot {
696
- buckets: [0; crate::registry::QUEUE_BOUNDS_US.len()], overflow: 0, sum_us: 1_500_000, count: 2,
897
+ buckets: [0; crate::registry::QUEUE_BOUNDS_US.len()],
898
+ overflow: 0,
899
+ sum_us: 1_500_000,
900
+ count: 2,
697
901
  };
698
902
  let json = stats_json(&s);
699
- assert!(json.contains(r#""queue_time":{"count":2,"sum_seconds":1.5}"#), "{json}");
903
+ assert!(
904
+ json.contains(r#""queue_time":{"count":2,"sum_seconds":1.5}"#),
905
+ "{json}"
906
+ );
700
907
  }
701
908
  }