180 lines
5.8 KiB
Rust
180 lines
5.8 KiB
Rust
|
|
use std::time::Duration;
|
||
|
|
|
||
|
|
use datastream::{
|
||
|
|
ChannelId, DatastreamEndpoint, DeliveryFanout, Frame, Lifetime, NodeId, Position, StreamId,
|
||
|
|
};
|
||
|
|
use serde_json::Value;
|
||
|
|
use swactor::actor::ActorAddress;
|
||
|
|
use swactor::stats::ActorSnapshot;
|
||
|
|
|
||
|
|
fn stream() -> StreamId {
|
||
|
|
StreamId::new(NodeId::new("node-endpoint"), Lifetime(7))
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn endpoint_without_subscribers_drains_to_bitbucket() {
|
||
|
|
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
|
||
|
|
let producer = endpoint.producer();
|
||
|
|
|
||
|
|
producer.submit_text("runtime.log", "before");
|
||
|
|
let tick = endpoint.tick();
|
||
|
|
|
||
|
|
assert_eq!(tick.drained, 1);
|
||
|
|
assert_eq!(tick.subscribers, 0);
|
||
|
|
assert_eq!(tick.delivered, 0);
|
||
|
|
assert_eq!(endpoint.assigned(), 1);
|
||
|
|
assert_eq!(endpoint.drained(), 1);
|
||
|
|
assert_eq!(endpoint.bitbucketed(), 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn subscription_receives_only_future_frames() {
|
||
|
|
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
|
||
|
|
let producer = endpoint.producer();
|
||
|
|
|
||
|
|
producer.submit_text("runtime.log", "pre-subscription");
|
||
|
|
endpoint.tick();
|
||
|
|
|
||
|
|
let subscription = endpoint.subscribe_all("dashboard");
|
||
|
|
producer.submit_text("runtime.log", "visible");
|
||
|
|
endpoint.tick();
|
||
|
|
|
||
|
|
let deliveries = subscription.drain_available();
|
||
|
|
assert_eq!(deliveries.len(), 1);
|
||
|
|
assert_eq!(deliveries[0].stream, stream());
|
||
|
|
assert_eq!(deliveries[0].frame.position, Position(1));
|
||
|
|
assert_eq!(deliveries[0].frame.channel, ChannelId::new("runtime.log"));
|
||
|
|
assert_eq!(deliveries[0].frame.payload, b"visible");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn endpoint_fans_out_ordered_frames_to_multiple_subscribers() {
|
||
|
|
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
|
||
|
|
let producer = endpoint.producer();
|
||
|
|
let left = endpoint.subscribe_all("left");
|
||
|
|
let right = endpoint.subscribe_all("right");
|
||
|
|
|
||
|
|
for n in 0..3 {
|
||
|
|
producer.submit_text("runtime.log", format!("line-{n}"));
|
||
|
|
}
|
||
|
|
let tick = endpoint.tick();
|
||
|
|
|
||
|
|
assert_eq!(tick.drained, 3);
|
||
|
|
assert_eq!(tick.subscribers, 2);
|
||
|
|
assert_eq!(tick.delivered, 6);
|
||
|
|
assert_eq!(positions(&left.drain_available()), vec![0, 1, 2]);
|
||
|
|
assert_eq!(positions(&right.drain_available()), vec![0, 1, 2]);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn slow_subscriber_drops_without_blocking_fast_subscriber() {
|
||
|
|
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 8);
|
||
|
|
let producer = endpoint.producer();
|
||
|
|
let slow = endpoint.subscribe_all_with_capacity("slow", 1);
|
||
|
|
let fast = endpoint.subscribe_all_with_capacity("fast", 8);
|
||
|
|
|
||
|
|
for n in 0..4 {
|
||
|
|
producer.submit_text("runtime.log", format!("line-{n}"));
|
||
|
|
}
|
||
|
|
let tick = endpoint.tick();
|
||
|
|
|
||
|
|
assert_eq!(tick.drained, 4);
|
||
|
|
assert_eq!(tick.delivered, 5);
|
||
|
|
assert_eq!(tick.dropped_for_subscribers, 3);
|
||
|
|
assert_eq!(positions(&slow.drain_available()), vec![0]);
|
||
|
|
assert_eq!(positions(&fast.drain_available()), vec![0, 1, 2, 3]);
|
||
|
|
|
||
|
|
let slow_snapshot = endpoint
|
||
|
|
.subscriber_snapshots()
|
||
|
|
.into_iter()
|
||
|
|
.find(|snapshot| snapshot.name == "slow")
|
||
|
|
.expect("slow subscriber snapshot");
|
||
|
|
assert_eq!(slow_snapshot.dropped, 3);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn delivery_fanout_can_publish_collector_deliveries_without_a_mux() {
|
||
|
|
let fanout = DeliveryFanout::new(4);
|
||
|
|
let sub = fanout.subscribe_all("dashboard");
|
||
|
|
let delivery = datastream::Delivery::new(
|
||
|
|
stream(),
|
||
|
|
Frame::new("remote.lifecycle", Position(9), b"ready".to_vec()),
|
||
|
|
);
|
||
|
|
|
||
|
|
let tick = fanout.publish(delivery.clone());
|
||
|
|
|
||
|
|
assert_eq!(tick.drained, 1);
|
||
|
|
assert_eq!(tick.delivered, 1);
|
||
|
|
assert_eq!(
|
||
|
|
sub.recv_timeout(Duration::from_millis(50)).unwrap(),
|
||
|
|
delivery
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn process_observer_adapter_submits_configured_channels() {
|
||
|
|
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
|
||
|
|
let producer = endpoint.producer();
|
||
|
|
let observer = producer.process_observer_with(|label, is_stderr| {
|
||
|
|
ChannelId::new(format!(
|
||
|
|
"proc.{label}.{}",
|
||
|
|
if is_stderr { "stderr" } else { "stdout" }
|
||
|
|
))
|
||
|
|
});
|
||
|
|
let sub = endpoint.subscribe_all("test");
|
||
|
|
|
||
|
|
observer.on_output("trainer", false, b"hello\n");
|
||
|
|
observer.on_output("trainer", true, b"warn\n");
|
||
|
|
endpoint.tick();
|
||
|
|
|
||
|
|
let deliveries = sub.drain_available();
|
||
|
|
assert_eq!(deliveries.len(), 2);
|
||
|
|
assert_eq!(
|
||
|
|
deliveries[0].frame.channel,
|
||
|
|
ChannelId::new("proc.trainer.stdout")
|
||
|
|
);
|
||
|
|
assert_eq!(deliveries[0].frame.payload, b"hello\n");
|
||
|
|
assert_eq!(
|
||
|
|
deliveries[1].frame.channel,
|
||
|
|
ChannelId::new("proc.trainer.stderr")
|
||
|
|
);
|
||
|
|
assert_eq!(deliveries[1].frame.payload, b"warn\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn stats_hook_adapter_submits_worker_snapshot_json() {
|
||
|
|
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
|
||
|
|
let producer = endpoint.producer();
|
||
|
|
let hook = producer.stats_hook_on("runtime.actors");
|
||
|
|
let sub = endpoint.subscribe_all("test");
|
||
|
|
let actor = ActorAddress::new_random();
|
||
|
|
let snapshots = [ActorSnapshot {
|
||
|
|
address: actor,
|
||
|
|
mailbox_depth: 3,
|
||
|
|
last_msg_type: Some("Ping"),
|
||
|
|
messages_processed: 5,
|
||
|
|
poisoned: false,
|
||
|
|
message_type_counts: vec![("Ping", 5)],
|
||
|
|
}];
|
||
|
|
|
||
|
|
hook.on_tick(2, &snapshots);
|
||
|
|
endpoint.tick();
|
||
|
|
|
||
|
|
let delivery = sub.recv_timeout(Duration::from_millis(50)).unwrap();
|
||
|
|
assert_eq!(delivery.frame.channel, ChannelId::new("runtime.actors"));
|
||
|
|
let json: Value = serde_json::from_slice(&delivery.frame.payload).unwrap();
|
||
|
|
assert_eq!(json["worker_id"], 2);
|
||
|
|
assert_eq!(json["actors"][0]["address"], actor.to_string());
|
||
|
|
assert_eq!(json["actors"][0]["mailbox_depth"], 3);
|
||
|
|
assert_eq!(json["actors"][0]["last_msg_type"], "Ping");
|
||
|
|
assert_eq!(json["actors"][0]["messages_processed"], 5);
|
||
|
|
assert_eq!(json["actors"][0]["message_type_counts"][0]["ty"], "Ping");
|
||
|
|
}
|
||
|
|
|
||
|
|
fn positions(deliveries: &[datastream::Delivery]) -> Vec<u64> {
|
||
|
|
deliveries
|
||
|
|
.iter()
|
||
|
|
.map(|delivery| delivery.frame.position.0)
|
||
|
|
.collect()
|
||
|
|
}
|