Unverified Commit f10e44ca authored by Keiven C's avatar Keiven C Committed by GitHub
Browse files

fix: Integration tests fixes (#2161)


Co-authored-by: default avatarKeiven Chang <keivenchang@users.noreply.github.com>
parent 7e3b3fab
...@@ -86,27 +86,17 @@ mod tests { ...@@ -86,27 +86,17 @@ mod tests {
// todo - make a distributed runtime fixture // todo - make a distributed runtime fixture
// todo - two options - fully mocked or integration test // todo - two options - fully mocked or integration test
#[tokio::test] #[tokio::test]
async fn test_publish() { async fn test_publish_and_subscribe() {
let rt = Runtime::from_current().unwrap(); let rt = Runtime::from_current().unwrap();
let dtr = DistributedRuntime::from_settings(rt.clone()).await.unwrap(); let dtr = DistributedRuntime::from_settings(rt.clone()).await.unwrap();
let ns = dtr.namespace("test".to_string()).unwrap(); let ns = dtr.namespace("test_component".to_string()).unwrap();
let cp = ns.component("component".to_string()).unwrap(); let cp = ns.component("test_component".to_string()).unwrap();
cp.publish("test", &"test".to_string()).await.unwrap();
rt.shutdown();
}
#[tokio::test]
async fn test_subscribe() {
let rt = Runtime::from_current().unwrap();
let dtr = DistributedRuntime::from_settings(rt.clone()).await.unwrap();
let ns = dtr.namespace("test".to_string()).unwrap();
let cp = ns.component("component".to_string()).unwrap();
// Create a subscriber // Create a subscriber on the component
let mut subscriber = ns.subscribe("test").await.unwrap(); let mut subscriber = cp.subscribe("test_event").await.unwrap();
// Publish a message // Publish a message from the component
cp.publish("test", &"test_message".to_string()) cp.publish("test_event", &"test_message".to_string())
.await .await
.unwrap(); .unwrap();
......
...@@ -99,8 +99,8 @@ mod tests { ...@@ -99,8 +99,8 @@ mod tests {
async fn test_publish() { async fn test_publish() {
let rt = Runtime::from_current().unwrap(); let rt = Runtime::from_current().unwrap();
let dtr = DistributedRuntime::from_settings(rt.clone()).await.unwrap(); let dtr = DistributedRuntime::from_settings(rt.clone()).await.unwrap();
let ns = dtr.namespace("test".to_string()).unwrap(); let ns = dtr.namespace("test_namespace_publish".to_string()).unwrap();
ns.publish("test", &"test".to_string()).await.unwrap(); ns.publish("test_event", &"test".to_string()).await.unwrap();
rt.shutdown(); rt.shutdown();
} }
...@@ -108,13 +108,15 @@ mod tests { ...@@ -108,13 +108,15 @@ mod tests {
async fn test_subscribe() { async fn test_subscribe() {
let rt = Runtime::from_current().unwrap(); let rt = Runtime::from_current().unwrap();
let dtr = DistributedRuntime::from_settings(rt.clone()).await.unwrap(); let dtr = DistributedRuntime::from_settings(rt.clone()).await.unwrap();
let ns = dtr.namespace("test".to_string()).unwrap(); let ns = dtr
.namespace("test_namespace_subscribe".to_string())
.unwrap();
// Create a subscriber // Create a subscriber
let mut subscriber = ns.subscribe("test").await.unwrap(); let mut subscriber = ns.subscribe("test_event").await.unwrap();
// Publish a message // Publish a message
ns.publish("test", &"test_message".to_string()) ns.publish("test_event", &"test_message".to_string())
.await .await
.unwrap(); .unwrap();
......
...@@ -77,7 +77,7 @@ impl crate::traits::DistributedRuntimeProvider for HttpMetricsRegistry { ...@@ -77,7 +77,7 @@ impl crate::traits::DistributedRuntimeProvider for HttpMetricsRegistry {
impl MetricsRegistry for HttpMetricsRegistry { impl MetricsRegistry for HttpMetricsRegistry {
fn basename(&self) -> String { fn basename(&self) -> String {
"http_server".to_string() "dynamo".to_string()
} }
fn parent_hierarchy(&self) -> Vec<String> { fn parent_hierarchy(&self) -> Vec<String> {
...@@ -100,7 +100,7 @@ impl HttpServerState { ...@@ -100,7 +100,7 @@ impl HttpServerState {
// Note: This metric is created at the DRT level (no namespace), so we manually add "dynamo_" prefix // Note: This metric is created at the DRT level (no namespace), so we manually add "dynamo_" prefix
// to maintain consistency with the project's metric naming convention // to maintain consistency with the project's metric naming convention
let uptime_gauge = http_metrics_registry.as_ref().create_gauge( let uptime_gauge = http_metrics_registry.as_ref().create_gauge(
"dynamo_uptime_seconds", "system_uptime_seconds",
"Total uptime of the DistributedRuntime in seconds", "Total uptime of the DistributedRuntime in seconds",
&[], &[],
)?; )?;
...@@ -368,9 +368,9 @@ mod tests { ...@@ -368,9 +368,9 @@ mod tests {
println!("Full metrics response:\n{}", response); println!("Full metrics response:\n{}", response);
let expected = "\ let expected = "\
# HELP dynamo_uptime_seconds Total uptime of the DistributedRuntime in seconds # HELP dynamo_system_uptime_seconds Total uptime of the DistributedRuntime in seconds
# TYPE dynamo_uptime_seconds gauge # TYPE dynamo_system_uptime_seconds gauge
dynamo_uptime_seconds{namespace=\"http_server\"} 42 dynamo_system_uptime_seconds{namespace=\"dynamo\"} 42
"; ";
assert_eq!(response, expected); assert_eq!(response, expected);
} }
......
...@@ -797,7 +797,7 @@ mod test_prefixes { ...@@ -797,7 +797,7 @@ mod test_prefixes {
println!("\n=== Testing Invalid Namespace Behavior ==="); println!("\n=== Testing Invalid Namespace Behavior ===");
// Create a namespace with invalid name (contains hyphen) // Create a namespace with invalid name (contains hyphen)
let invalid_namespace = drt.namespace("test-namespace").unwrap(); let invalid_namespace = drt.namespace("@@123").unwrap();
// Debug: Let's see what the hierarchy looks like // Debug: Let's see what the hierarchy looks like
println!( println!(
...@@ -810,15 +810,15 @@ mod test_prefixes { ...@@ -810,15 +810,15 @@ mod test_prefixes {
); );
println!("Invalid namespace prefix: '{}'", invalid_namespace.prefix()); println!("Invalid namespace prefix: '{}'", invalid_namespace.prefix());
// Try to create a metric - this should fail because the namespace name will be used in the metric name // Try to create a metric - this should fail because "@@123" gets stripped to "" which is invalid
let result = invalid_namespace.create_counter("test_counter", "A test counter", &[]); let result = invalid_namespace.create_counter("test_counter", "A test counter", &[]);
println!("Result with invalid namespace 'test-namespace':"); println!("Result with invalid namespace '@@123':");
println!("{:?}", result); println!("{:?}", result);
// The result should be an error from Prometheus // The result should be an error because empty metric names are invalid
assert!( assert!(
result.is_err(), result.is_err(),
"Creating metric with invalid namespace should fail" "Creating metric with namespace '@@123' should fail because it gets stripped to empty string"
); );
// For comparison, show a valid namespace works // For comparison, show a valid namespace works
...@@ -926,15 +926,15 @@ testnamespace_testgauge{{component="testcomponent",namespace="testnamespace"}} 5 ...@@ -926,15 +926,15 @@ testnamespace_testgauge{{component="testcomponent",namespace="testnamespace"}} 5
println!("{}", namespace_output); println!("{}", namespace_output);
let expected_namespace_output = format!( let expected_namespace_output = format!(
r#"# HELP testintcounter A test int counter r#"# HELP testnamespace_testcounter A test counter
# TYPE testintcounter counter
testintcounter{{namespace="testnamespace"}} 12345
# HELP testnamespace_testcounter A test counter
# TYPE testnamespace_testcounter counter # TYPE testnamespace_testcounter counter
testnamespace_testcounter{{component="testcomponent",endpoint="testendpoint",namespace="testnamespace"}} 123.456789 testnamespace_testcounter{{component="testcomponent",endpoint="testendpoint",namespace="testnamespace"}} 123.456789
# HELP testnamespace_testgauge A test gauge # HELP testnamespace_testgauge A test gauge
# TYPE testnamespace_testgauge gauge # TYPE testnamespace_testgauge gauge
testnamespace_testgauge{{component="testcomponent",namespace="testnamespace"}} 50000 testnamespace_testgauge{{component="testcomponent",namespace="testnamespace"}} 50000
# HELP testnamespace_testintcounter A test int counter
# TYPE testnamespace_testintcounter counter
testnamespace_testintcounter{{namespace="testnamespace"}} 12345
"# "#
); );
...@@ -1015,9 +1015,6 @@ testhistogram_bucket{{le="10"}} 3 ...@@ -1015,9 +1015,6 @@ testhistogram_bucket{{le="10"}} 3
testhistogram_bucket{{le="+Inf"}} 3 testhistogram_bucket{{le="+Inf"}} 3
testhistogram_sum 7.5 testhistogram_sum 7.5
testhistogram_count 3 testhistogram_count 3
# HELP testintcounter A test int counter
# TYPE testintcounter counter
testintcounter{{namespace="testnamespace"}} 12345
# HELP testintgauge A test int gauge # HELP testintgauge A test int gauge
# TYPE testintgauge gauge # TYPE testintgauge gauge
testintgauge 42 testintgauge 42
...@@ -1031,6 +1028,9 @@ testnamespace_testcounter{{component="testcomponent",endpoint="testendpoint",nam ...@@ -1031,6 +1028,9 @@ testnamespace_testcounter{{component="testcomponent",endpoint="testendpoint",nam
# HELP testnamespace_testgauge A test gauge # HELP testnamespace_testgauge A test gauge
# TYPE testnamespace_testgauge gauge # TYPE testnamespace_testgauge gauge
testnamespace_testgauge{{component="testcomponent",namespace="testnamespace"}} 50000 testnamespace_testgauge{{component="testcomponent",namespace="testnamespace"}} 50000
# HELP testnamespace_testintcounter A test int counter
# TYPE testnamespace_testintcounter counter
testnamespace_testintcounter{{namespace="testnamespace"}} 12345
"# "#
); );
......
...@@ -13,6 +13,17 @@ ...@@ -13,6 +13,17 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// cargo test --test soak integration::main --features integration
//!
//! It will send a batch of requests to the runtime and measure the throughput.
//!
//! It will also measure the latency of the requests.
//!
//! A reasonable soak test configuration to start off is 1 minute duration with 10000 batch load:
//! export DYN_QUEUED_UP_PROCESSING=true
//! export DYN_SOAK_BATCH_LOAD=10000
//! export DYN_SOAK_RUN_DURATION=60s
//! cargo test --test soak integration::main --features integration -- --nocapture
#[cfg(feature = "integration")] #[cfg(feature = "integration")]
mod integration { mod integration {
...@@ -22,13 +33,17 @@ mod integration { ...@@ -22,13 +33,17 @@ mod integration {
logging, logging,
pipeline::{ pipeline::{
async_trait, network::Ingress, AsyncEngine, AsyncEngineContextProvider, Error, ManyOut, async_trait, network::Ingress, AsyncEngine, AsyncEngineContextProvider, Error, ManyOut,
ResponseStream, SingleIn, PushRouter, ResponseStream, SingleIn,
}, },
protocols::annotated::Annotated, protocols::annotated::Annotated,
DistributedRuntime, ErrorContext, Result, Runtime, Worker, stream, DistributedRuntime, ErrorContext, Result, Runtime, Worker,
}; };
use futures::StreamExt; use futures::StreamExt;
use std::{sync::Arc, time::Duration}; use std::{
sync::atomic::{AtomicU64, Ordering},
sync::Arc,
time::Duration,
};
use tokio::time::Instant; use tokio::time::Instant;
#[test] #[test]
...@@ -45,16 +60,29 @@ mod integration { ...@@ -45,16 +60,29 @@ mod integration {
client.await??; client.await??;
distributed.shutdown(); distributed.shutdown();
server.await??; let handler = server.await??;
// Print final backend counter value
let final_count = handler.backend_counter.load(Ordering::Relaxed);
println!(
"Final RequestHandler backend_counter: {} requests processed",
final_count
);
Ok(()) Ok(())
} }
struct RequestHandler {} struct RequestHandler {
backend_counter: AtomicU64,
queued_up_processing: bool,
}
impl RequestHandler { impl RequestHandler {
fn new() -> Arc<Self> { fn new(queued_up_processing: bool) -> Arc<Self> {
Arc::new(Self {}) Arc::new(Self {
backend_counter: AtomicU64::new(0),
queued_up_processing,
})
} }
} }
...@@ -63,25 +91,40 @@ mod integration { ...@@ -63,25 +91,40 @@ mod integration {
async fn generate(&self, input: SingleIn<String>) -> Result<ManyOut<Annotated<String>>> { async fn generate(&self, input: SingleIn<String>) -> Result<ManyOut<Annotated<String>>> {
let (data, ctx) = input.into_parts(); let (data, ctx) = input.into_parts();
// Increment backend counter
self.backend_counter.fetch_add(1, Ordering::Relaxed);
let chars = data let chars = data
.chars() .chars()
.map(|c| Annotated::from_data(c.to_string())) .map(|c| Annotated::from_data(c.to_string()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let stream = async_stream::stream! { if self.queued_up_processing {
// queued up processing - delayed response to saturate the queue
let async_stream = async_stream::stream! {
for c in chars { for c in chars {
yield c; yield c;
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
} }
}; };
Ok(ResponseStream::new(Box::pin(async_stream), ctx.context()))
Ok(ResponseStream::new(Box::pin(stream), ctx.context())) } else {
// normal processing - immediate response
let iter_stream = stream::iter(chars);
Ok(ResponseStream::new(Box::pin(iter_stream), ctx.context()))
}
} }
} }
async fn backend(runtime: DistributedRuntime) -> Result<()> { async fn backend(runtime: DistributedRuntime) -> Result<Arc<RequestHandler>> {
// get the queued up processing setting from env (not delayed)
let queued_up_processing =
std::env::var("DYN_QUEUED_UP_PROCESSING").unwrap_or("false".to_string());
let queued_up_processing: bool = queued_up_processing.parse().unwrap_or(false);
// attach an ingress to an engine // attach an ingress to an engine
let ingress = Ingress::for_engine(RequestHandler::new())?; let handler = RequestHandler::new(queued_up_processing);
let ingress = Ingress::for_engine(handler.clone())?;
// // make the ingress discoverable via a component service // // make the ingress discoverable via a component service
// // we must first create a service, then we can attach one more more endpoints // // we must first create a service, then we can attach one more more endpoints
...@@ -95,27 +138,32 @@ mod integration { ...@@ -95,27 +138,32 @@ mod integration {
.endpoint_builder() .endpoint_builder()
.handler(ingress) .handler(ingress)
.start() .start()
.await .await?;
Ok(handler)
} }
async fn client(runtime: DistributedRuntime) -> Result<()> { async fn client(runtime: DistributedRuntime) -> Result<()> {
// get the run duration from env // get the run duration from env
let run_duration = std::env::var("DYN_SOAK_RUN_DURATION").unwrap_or("1m".to_string()); let run_duration = std::env::var("DYN_SOAK_RUN_DURATION").unwrap_or("3s".to_string());
let run_duration = let run_duration =
humantime::parse_duration(&run_duration).unwrap_or(Duration::from_secs(60)); humantime::parse_duration(&run_duration).unwrap_or(Duration::from_secs(3));
let batch_load = std::env::var("DYN_SOAK_BATCH_LOAD").unwrap_or("10000".to_string()); let batch_load = std::env::var("DYN_SOAK_BATCH_LOAD").unwrap_or("100".to_string());
let batch_load: usize = batch_load.parse().unwrap_or(10000); let batch_load: usize = batch_load.parse().unwrap_or(100);
let client = runtime let client = runtime
.namespace(DEFAULT_NAMESPACE)? .namespace(DEFAULT_NAMESPACE)?
.component("backend")? .component("backend")?
.endpoint("generate") .endpoint("generate")
.client::<String, Annotated<String>>() .client()
.await?; .await?;
client.wait_for_instances().await?; client.wait_for_instances().await?;
let client = Arc::new(client); let router =
PushRouter::<String, Annotated<String>>::from_client(client, Default::default())
.await?;
let router = Arc::new(router);
let start = Instant::now(); let start = Instant::now();
let mut count = 0; let mut count = 0;
...@@ -123,11 +171,11 @@ mod integration { ...@@ -123,11 +171,11 @@ mod integration {
loop { loop {
let mut tasks = Vec::new(); let mut tasks = Vec::new();
for _ in 0..batch_load { for _ in 0..batch_load {
let client = client.clone(); let router = router.clone();
tasks.push(tokio::spawn(async move { tasks.push(tokio::spawn(async move {
let mut stream = tokio::time::timeout( let mut stream = tokio::time::timeout(
Duration::from_secs(30), Duration::from_secs(5),
client.random("hello world".to_string().into()), router.random("hello world".to_string().into()),
) )
.await .await
.context("request timed out")??; .context("request timed out")??;
...@@ -147,7 +195,9 @@ mod integration { ...@@ -147,7 +195,9 @@ mod integration {
let elapsed = start.elapsed(); let elapsed = start.elapsed();
count += batch_load; count += batch_load;
if count % 1000 == 0 {
println!("elapsed: {:?}; count: {}", elapsed, count); println!("elapsed: {:?}; count: {}", elapsed, count);
}
if elapsed > run_duration { if elapsed > run_duration {
println!("done"); println!("done");
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment