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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/Cargo.lock +64 -71
- data/README.md +52 -0
- data/doc/architecture.md +10 -0
- data/ext/kino/Cargo.toml +2 -2
- data/ext/kino/src/control.rs +103 -6
- data/ext/kino/src/env_strings.rs +267 -124
- data/ext/kino/src/lib.rs +11 -0
- data/ext/kino/src/pin.rs +66 -15
- data/ext/kino/src/queue.rs +99 -3
- data/ext/kino/src/registry.rs +142 -1
- data/ext/kino/src/request.rs +2 -2
- data/ext/kino/src/server.rs +220 -4
- data/ext/kino/src/test_support.rs +44 -0
- data/lib/kino/cli.rb +12 -4
- data/lib/kino/configuration.rb +16 -2
- data/lib/kino/monitor.rb +52 -0
- data/lib/kino/pool_scaler.rb +103 -0
- data/lib/kino/quarantine_monitor.rb +5 -37
- data/lib/kino/ractor_supervisor.rb +102 -8
- data/lib/kino/server.rb +64 -85
- data/lib/kino/slot_bank.rb +31 -0
- data/lib/kino/templates/kino.rb.tt +15 -0
- data/lib/kino/threaded_pool.rb +186 -0
- data/lib/kino/version.rb +1 -1
- data/lib/kino.rb +4 -0
- data/lib/rackup/handler/kino.rb +4 -4
- data/sig/kino.rbs +2 -0
- metadata +6 -2
data/ext/kino/src/control.rs
CHANGED
|
@@ -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"#));
|