kino 0.6.0 → 0.7.1

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.
@@ -18,6 +18,8 @@ pub struct WorkerStat {
18
18
  pub in_flight: usize,
19
19
  pub busy_ms: u64,
20
20
  pub quarantined: bool,
21
+ /// Sent home by the pool scaler; leaves at its next idle tick.
22
+ pub retired: bool,
21
23
  }
22
24
 
23
25
  /// Read every slot's per-worker sensors in one pass under the slots read
@@ -46,6 +48,7 @@ pub fn collect_worker_status(server: &ServerInner) -> Vec<WorkerStat> {
46
48
  now.saturating_sub(started)
47
49
  },
48
50
  quarantined,
51
+ retired: slot.retired.load(Ordering::Relaxed),
49
52
  }
50
53
  })
51
54
  .collect()
@@ -70,6 +73,10 @@ pub struct StatsSnapshot {
70
73
  pub worker_status: Vec<WorkerStat>,
71
74
  pub quarantined_count: usize,
72
75
  pub quarantine_replacements: u64,
76
+ pub max_workers: usize,
77
+ pub active_workers: usize,
78
+ pub scale_ups: u64,
79
+ pub scale_downs: u64,
73
80
  pub queue_histogram: crate::registry::QueueHistogramSnapshot,
74
81
  }
75
82
 
@@ -94,6 +101,10 @@ impl StatsSnapshot {
94
101
  worker_status,
95
102
  quarantined_count,
96
103
  quarantine_replacements: server.quarantine_replacements.load(Ordering::Relaxed),
104
+ max_workers: server.topology.max_workers,
105
+ active_workers: server.active_workers.load(Ordering::Relaxed),
106
+ scale_ups: server.scale_ups.load(Ordering::Relaxed),
107
+ scale_downs: server.scale_downs.load(Ordering::Relaxed),
97
108
  queue_histogram: server.queue_histogram.snapshot(),
98
109
  }
99
110
  }
@@ -114,9 +125,10 @@ pub fn stats_json(s: &StatsSnapshot) -> String {
114
125
  let mut out = String::with_capacity(256);
115
126
  write!(
116
127
  out,
117
- r#"{{"mode":"{}","lanes":{},"workers":{},"threads":{},"batch":{},"respawns":{},"queued":{},"in_flight":{},"served":{},"rejected":{},"timeouts":{}"#,
128
+ r#"{{"mode":"{}","lanes":{},"workers":{},"threads":{},"batch":{},"respawns":{},"queued":{},"in_flight":{},"served":{},"rejected":{},"timeouts":{},"max_workers":{},"active_workers":{},"scale_ups":{},"scale_downs":{}"#,
118
129
  s.mode, s.lanes, s.workers, s.threads, s.batch, s.respawns,
119
- s.queued, s.in_flight, s.served, s.rejected, s.timeouts
130
+ s.queued, s.in_flight, s.served, s.rejected, s.timeouts,
131
+ s.max_workers, s.active_workers, s.scale_ups, s.scale_downs
120
132
  )
121
133
  .expect("writing to a String cannot fail");
122
134
  if let Some(depths) = &s.lane_depths {
@@ -134,8 +146,8 @@ pub fn stats_json(s: &StatsSnapshot) -> String {
134
146
  }
135
147
  write!(
136
148
  out,
137
- r#"{{"index":{},"served":{},"in_flight":{},"busy_ms":{},"quarantined":{}}}"#,
138
- w.index, w.served, w.in_flight, w.busy_ms, w.quarantined
149
+ r#"{{"index":{},"served":{},"in_flight":{},"busy_ms":{},"quarantined":{},"retired":{}}}"#,
150
+ w.index, w.served, w.in_flight, w.busy_ms, w.quarantined, w.retired
139
151
  )
140
152
  .expect("writing to a String cannot fail");
141
153
  }
@@ -247,6 +259,34 @@ pub fn metrics_text(s: &StatsSnapshot) -> String {
247
259
  "Configured threads per worker.",
248
260
  s.threads,
249
261
  );
262
+ metric(
263
+ &mut out,
264
+ "kino_max_workers",
265
+ "gauge",
266
+ "Worker pool ceiling (equals kino_workers for a fixed pool).",
267
+ s.max_workers,
268
+ );
269
+ metric(
270
+ &mut out,
271
+ "kino_active_workers",
272
+ "gauge",
273
+ "Workers currently alive and serving.",
274
+ s.active_workers,
275
+ );
276
+ metric(
277
+ &mut out,
278
+ "kino_scale_ups_total",
279
+ "counter",
280
+ "Workers added by the pool scaler.",
281
+ s.scale_ups,
282
+ );
283
+ metric(
284
+ &mut out,
285
+ "kino_scale_downs_total",
286
+ "counter",
287
+ "Idle workers retired by the pool scaler.",
288
+ s.scale_downs,
289
+ );
250
290
  metric(
251
291
  &mut out,
252
292
  "kino_ready",
@@ -641,6 +681,10 @@ mod tests {
641
681
  worker_status: vec![],
642
682
  quarantined_count: 0,
643
683
  quarantine_replacements: 0,
684
+ max_workers: 32,
685
+ active_workers: 12,
686
+ scale_ups: 3,
687
+ scale_downs: 1,
644
688
  queue_histogram: crate::registry::QueueHistogramSnapshot {
645
689
  buckets: [0; crate::registry::QUEUE_BOUNDS_US.len()],
646
690
  overflow: 0,
@@ -665,6 +709,10 @@ mod tests {
665
709
  r#""served":100"#,
666
710
  r#""rejected":5"#,
667
711
  r#""timeouts":6"#,
712
+ r#""max_workers":32"#,
713
+ r#""active_workers":12"#,
714
+ r#""scale_ups":3"#,
715
+ r#""scale_downs":1"#,
668
716
  r#""state":"ready""#,
669
717
  r#""version":""#,
670
718
  ] {
@@ -690,6 +738,49 @@ mod tests {
690
738
  assert!(draining.contains("kino_ready 0"));
691
739
  }
692
740
 
741
+ #[test]
742
+ fn metrics_text_reports_the_elastic_pool() {
743
+ let text = metrics_text(&snapshot(crate::registry::STATE_READY));
744
+ assert!(text.contains("# TYPE kino_max_workers gauge"));
745
+ assert!(text.contains("kino_max_workers 32"));
746
+ assert!(text.contains("# TYPE kino_active_workers gauge"));
747
+ assert!(text.contains("kino_active_workers 12"));
748
+ assert!(text.contains("# TYPE kino_scale_ups_total counter"));
749
+ assert!(text.contains("kino_scale_ups_total 3"));
750
+ assert!(text.contains("# TYPE kino_scale_downs_total counter"));
751
+ assert!(text.contains("kino_scale_downs_total 1"));
752
+ }
753
+
754
+ #[test]
755
+ fn worker_status_reports_retired_slots() {
756
+ let server = crate::registry::test_server(false, 4);
757
+ server.register_worker();
758
+ server.register_worker();
759
+ server.slots.read()[1].retire();
760
+
761
+ let status = collect_worker_status(&server);
762
+
763
+ assert_eq!(
764
+ status.iter().map(|w| w.retired).collect::<Vec<_>>(),
765
+ vec![false, true]
766
+ );
767
+ assert!(status.iter().all(|w| w.busy_ms == 0));
768
+ }
769
+
770
+ #[test]
771
+ fn snapshot_reads_the_pool_counters() {
772
+ let server = crate::registry::test_server(false, 4);
773
+ server.active_workers.store(3, Ordering::Relaxed);
774
+ server.scale_ups.fetch_add(2, Ordering::Relaxed);
775
+ server.scale_downs.fetch_add(1, Ordering::Relaxed);
776
+
777
+ let s = StatsSnapshot::take(&server);
778
+
779
+ assert_eq!(s.active_workers, 3);
780
+ assert_eq!(s.max_workers, server.topology.max_workers);
781
+ assert_eq!((s.scale_ups, s.scale_downs), (2, 1));
782
+ }
783
+
693
784
  #[test]
694
785
  fn metrics_text_includes_lane_depth_samples_when_lanes_are_on() {
695
786
  let mut s = snapshot(crate::registry::STATE_READY);
@@ -757,6 +848,7 @@ mod tests {
757
848
  in_flight: 1,
758
849
  busy_ms: 4,
759
850
  quarantined: false,
851
+ retired: false,
760
852
  },
761
853
  WorkerStat {
762
854
  index: 1,
@@ -764,10 +856,11 @@ mod tests {
764
856
  in_flight: 0,
765
857
  busy_ms: 0,
766
858
  quarantined: false,
859
+ retired: true,
767
860
  },
768
861
  ];
769
862
  let json = stats_json(&s);
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}");
863
+ assert!(json.contains(r#""worker_status":[{"index":0,"served":10,"in_flight":1,"busy_ms":4,"quarantined":false,"retired":false},{"index":1,"served":7,"in_flight":0,"busy_ms":0,"quarantined":false,"retired":true}]"#), "got {json}");
771
864
  }
772
865
 
773
866
  #[test]
@@ -786,6 +879,7 @@ mod tests {
786
879
  in_flight: 1,
787
880
  busy_ms: 4,
788
881
  quarantined: false,
882
+ retired: false,
789
883
  },
790
884
  WorkerStat {
791
885
  index: 1,
@@ -793,6 +887,7 @@ mod tests {
793
887
  in_flight: 0,
794
888
  busy_ms: 0,
795
889
  quarantined: false,
890
+ retired: true,
796
891
  },
797
892
  ];
798
893
  let text = metrics_text(&s);
@@ -835,6 +930,7 @@ mod tests {
835
930
  in_flight: 1,
836
931
  busy_ms: 0,
837
932
  quarantined: true,
933
+ retired: false,
838
934
  },
839
935
  WorkerStat {
840
936
  index: 1,
@@ -842,6 +938,7 @@ mod tests {
842
938
  in_flight: 1,
843
939
  busy_ms: 5,
844
940
  quarantined: false,
941
+ retired: false,
845
942
  },
846
943
  ];
847
944
  let json = stats_json(&s);
@@ -850,7 +947,7 @@ mod tests {
850
947
  "top-level count: {json}"
851
948
  );
852
949
  assert!(
853
- json.contains(r#"{"index":0,"served":3,"in_flight":1,"busy_ms":0,"quarantined":true}"#),
950
+ json.contains(r#"{"index":0,"served":3,"in_flight":1,"busy_ms":0,"quarantined":true,"retired":false}"#),
854
951
  "{json}"
855
952
  );
856
953
  assert!(json.contains(r#""quarantined":false"#));