mirror of
https://github.com/ruvnet/RuView
synced 2026-08-09 20:21:43 +00:00
feat: vendor midstream and sublinear-time-solver libraries
Add ruvnet/midstream (AIMDS real-time inference) and ruvnet/sublinear-time-solver (sublinear optimization algorithms) as vendored dependencies under vendor/. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "realistic-temporal-solver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
# Core numerics - using established libraries
|
||||
ndarray = "0.15"
|
||||
ndarray-rand = "0.14"
|
||||
nalgebra = "0.32"
|
||||
|
||||
# Using simpler dependencies to avoid conflicts
|
||||
|
||||
# Actual linear algebra
|
||||
blas-src = { version = "0.8", features = ["openblas"] }
|
||||
lapack-src = { version = "0.8", features = ["openblas"] }
|
||||
|
||||
# Real benchmarking
|
||||
criterion = "0.5"
|
||||
cpu-time = "1.0"
|
||||
|
||||
# Serialization
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
# Error handling
|
||||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5"
|
||||
proptest = "1.0"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
# Benchmarks defined when bench file exists
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Compare our Rust implementation with established PyTorch models
|
||||
This provides ground truth for realistic performance expectations
|
||||
"""
|
||||
|
||||
import time
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from typing import List, Tuple
|
||||
import json
|
||||
|
||||
class SimpleGRU(nn.Module):
|
||||
"""Small GRU network matching the paper's specification"""
|
||||
def __init__(self, input_size=128, hidden_size=32, output_size=4):
|
||||
super().__init__()
|
||||
self.gru = nn.GRU(input_size, hidden_size, batch_first=True)
|
||||
self.fc = nn.Linear(hidden_size, output_size)
|
||||
|
||||
def forward(self, x):
|
||||
# x shape: (batch, input_size) -> need to add seq dimension
|
||||
x = x.unsqueeze(1) # (batch, 1, input_size)
|
||||
out, _ = self.gru(x)
|
||||
# Take the single timestep output
|
||||
out = self.fc(out.squeeze(1))
|
||||
return out
|
||||
|
||||
class SimpleTCN(nn.Module):
|
||||
"""Temporal Convolutional Network for comparison"""
|
||||
def __init__(self, input_size=128, hidden_size=32, output_size=4):
|
||||
super().__init__()
|
||||
self.conv1 = nn.Conv1d(1, hidden_size, kernel_size=3, padding=1)
|
||||
self.conv2 = nn.Conv1d(hidden_size, hidden_size, kernel_size=3, padding=1)
|
||||
self.fc = nn.Linear(hidden_size * input_size, output_size)
|
||||
self.relu = nn.ReLU()
|
||||
|
||||
def forward(self, x):
|
||||
# x shape: (batch, input_size)
|
||||
x = x.unsqueeze(1) # Add channel dimension
|
||||
x = self.relu(self.conv1(x))
|
||||
x = self.relu(self.conv2(x))
|
||||
x = x.flatten(1)
|
||||
return self.fc(x)
|
||||
|
||||
class SimpleFeedforward(nn.Module):
|
||||
"""Simple 2-layer network like our Rust implementation"""
|
||||
def __init__(self, input_size=128, hidden_size=32, output_size=4):
|
||||
super().__init__()
|
||||
self.fc1 = nn.Linear(input_size, hidden_size)
|
||||
self.fc2 = nn.Linear(hidden_size, output_size)
|
||||
self.relu = nn.ReLU()
|
||||
|
||||
def forward(self, x):
|
||||
x = self.relu(self.fc1(x))
|
||||
return self.fc2(x)
|
||||
|
||||
def benchmark_model(model: nn.Module, input_size: int, iterations: int = 1000) -> dict:
|
||||
"""Benchmark a PyTorch model with realistic timing"""
|
||||
model.eval()
|
||||
|
||||
# Warmup
|
||||
with torch.no_grad():
|
||||
dummy_input = torch.randn(1, input_size)
|
||||
for _ in range(100):
|
||||
_ = model(dummy_input)
|
||||
|
||||
# Actual benchmark
|
||||
timings = []
|
||||
with torch.no_grad():
|
||||
for _ in range(iterations):
|
||||
input_tensor = torch.randn(1, input_size)
|
||||
|
||||
start = time.perf_counter()
|
||||
output = model(input_tensor)
|
||||
# Force computation to complete
|
||||
_ = output.cpu().numpy()
|
||||
end = time.perf_counter()
|
||||
|
||||
timings.append((end - start) * 1000) # Convert to milliseconds
|
||||
|
||||
timings.sort()
|
||||
return {
|
||||
"p50": timings[len(timings) // 2],
|
||||
"p90": timings[int(len(timings) * 0.9)],
|
||||
"p99": timings[int(len(timings) * 0.99)],
|
||||
"p999": timings[int(len(timings) * 0.999)],
|
||||
"average": np.mean(timings),
|
||||
"std": np.std(timings),
|
||||
"min": min(timings),
|
||||
"max": max(timings)
|
||||
}
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("PyTorch Baseline Performance Comparison")
|
||||
print("=" * 60)
|
||||
print("\nTesting on CPU with models matching paper specification:")
|
||||
print("- Input size: 128")
|
||||
print("- Hidden size: 32")
|
||||
print("- Output size: 4")
|
||||
print("- Iterations: 1000")
|
||||
print()
|
||||
|
||||
results = {}
|
||||
|
||||
# Test each model type
|
||||
models = {
|
||||
"Feedforward (2-layer)": SimpleFeedforward(),
|
||||
"GRU (1-layer)": SimpleGRU(),
|
||||
"TCN (2-layer)": SimpleTCN()
|
||||
}
|
||||
|
||||
for name, model in models.items():
|
||||
print(f"Benchmarking {name}...")
|
||||
stats = benchmark_model(model, input_size=128)
|
||||
results[name] = stats
|
||||
|
||||
print(f" P50: {stats['p50']:.3f}ms")
|
||||
print(f" P90: {stats['p90']:.3f}ms")
|
||||
print(f" P99: {stats['p99']:.3f}ms")
|
||||
print(f" P99.9: {stats['p999']:.3f}ms")
|
||||
print(f" Avg: {stats['average']:.3f}ms ± {stats['std']:.3f}ms")
|
||||
print(f" Range: {stats['min']:.3f}ms - {stats['max']:.3f}ms")
|
||||
print()
|
||||
|
||||
print("=" * 60)
|
||||
print("Analysis:")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Check if <0.9ms is realistic
|
||||
min_p999 = min(results[m]['p999'] for m in results)
|
||||
print(f"Best P99.9 latency achieved: {min_p999:.3f}ms")
|
||||
|
||||
if min_p999 < 0.9:
|
||||
print("✅ Sub-0.9ms P99.9 latency ACHIEVED!")
|
||||
else:
|
||||
print(f"❌ Sub-0.9ms P99.9 latency NOT achieved")
|
||||
print(f" Gap to target: {min_p999 - 0.9:.3f}ms")
|
||||
|
||||
print()
|
||||
print("Realistic expectations for CPU inference:")
|
||||
print("- Simple feedforward: 0.5-5ms typically")
|
||||
print("- Small GRU: 2-10ms typically")
|
||||
print("- Small TCN: 1-8ms typically")
|
||||
print()
|
||||
print("Sub-millisecond (<1ms) is extremely challenging on CPU")
|
||||
print("Sub-0.9ms specifically would require:")
|
||||
print("- Highly optimized C/Rust implementation")
|
||||
print("- Quantization (INT8 or lower)")
|
||||
print("- Model pruning/distillation")
|
||||
print("- Hardware acceleration (GPU/TPU/NPU)")
|
||||
|
||||
# Save results
|
||||
with open('pytorch_baseline_results.json', 'w') as f:
|
||||
json.dump(results, f, indent=2)
|
||||
print(f"\nResults saved to pytorch_baseline_results.json")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Check PyTorch version
|
||||
print(f"PyTorch version: {torch.__version__}")
|
||||
print(f"Using device: CPU")
|
||||
print()
|
||||
|
||||
main()
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"Feedforward (2-layer)": {
|
||||
"p50": 0.022882999473949894,
|
||||
"p90": 0.02369399953749962,
|
||||
"p99": 0.04163799894740805,
|
||||
"p999": 0.06067299909773283,
|
||||
"average": 0.02379135500450502,
|
||||
"std": 0.0037557752934545417,
|
||||
"min": 0.02239099922007881,
|
||||
"max": 0.06067299909773283
|
||||
},
|
||||
"GRU (1-layer)": {
|
||||
"p50": 0.09998600216931663,
|
||||
"p90": 0.12313900151639245,
|
||||
"p99": 0.20135500017204322,
|
||||
"p999": 0.21927900161244906,
|
||||
"average": 0.10813902704103384,
|
||||
"std": 0.021166710697914044,
|
||||
"min": 0.09721099922899157,
|
||||
"max": 0.21927900161244906
|
||||
},
|
||||
"TCN (2-layer)": {
|
||||
"p50": 0.09211099677486345,
|
||||
"p90": 0.16997700004139915,
|
||||
"p99": 0.19423200137680396,
|
||||
"p999": 0.2223840019723866,
|
||||
"average": 0.1120872970095661,
|
||||
"std": 0.03245083047011204,
|
||||
"min": 0.0877739985298831,
|
||||
"max": 0.2223840019723866
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
+135
@@ -0,0 +1,135 @@
|
||||
// Simple standalone test to show real neural network performance
|
||||
// Compile with: rustc -O simple_test.rs
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn relu(x: f32) -> f32 {
|
||||
if x > 0.0 { x } else { 0.0 }
|
||||
}
|
||||
|
||||
// Simple 2-layer neural network
|
||||
struct SimpleNN {
|
||||
w1: Vec<Vec<f32>>, // 32x128 weight matrix
|
||||
b1: Vec<f32>, // 32 bias vector
|
||||
w2: Vec<Vec<f32>>, // 4x32 weight matrix
|
||||
b2: Vec<f32>, // 4 bias vector
|
||||
}
|
||||
|
||||
impl SimpleNN {
|
||||
fn new() -> Self {
|
||||
// Initialize with small random weights
|
||||
let mut w1 = vec![vec![0.0; 128]; 32];
|
||||
let mut w2 = vec![vec![0.0; 32]; 4];
|
||||
|
||||
for i in 0..32 {
|
||||
for j in 0..128 {
|
||||
w1[i][j] = ((i * j) as f32 * 0.01) % 0.1 - 0.05;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..4 {
|
||||
for j in 0..32 {
|
||||
w2[i][j] = ((i * j) as f32 * 0.01) % 0.1 - 0.05;
|
||||
}
|
||||
}
|
||||
|
||||
SimpleNN {
|
||||
w1,
|
||||
b1: vec![0.0; 32],
|
||||
w2,
|
||||
b2: vec![0.0; 4],
|
||||
}
|
||||
}
|
||||
|
||||
fn forward(&self, input: &[f32]) -> Vec<f32> {
|
||||
// Layer 1: input (128) -> hidden (32)
|
||||
let mut hidden = vec![0.0; 32];
|
||||
for i in 0..32 {
|
||||
let mut sum = self.b1[i];
|
||||
for j in 0..128 {
|
||||
sum += self.w1[i][j] * input[j];
|
||||
}
|
||||
hidden[i] = relu(sum);
|
||||
}
|
||||
|
||||
// Layer 2: hidden (32) -> output (4)
|
||||
let mut output = vec![0.0; 4];
|
||||
for i in 0..4 {
|
||||
let mut sum = self.b2[i];
|
||||
for j in 0..32 {
|
||||
sum += self.w2[i][j] * hidden[j];
|
||||
}
|
||||
output[i] = sum;
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Realistic Neural Network Performance Test ===\n");
|
||||
println!("Architecture: 128 -> 32 (ReLU) -> 4");
|
||||
println!("Pure Rust, no external dependencies");
|
||||
println!("Optimized release build\n");
|
||||
|
||||
let nn = SimpleNN::new();
|
||||
let input = vec![0.1; 128];
|
||||
|
||||
// Warmup
|
||||
for _ in 0..1000 {
|
||||
let _ = nn.forward(&input);
|
||||
}
|
||||
|
||||
// Actual benchmark
|
||||
let iterations = 10000;
|
||||
let mut timings = Vec::new();
|
||||
|
||||
for _ in 0..iterations {
|
||||
let start = Instant::now();
|
||||
let output = nn.forward(&input);
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Prevent optimization
|
||||
if output[0] > 1000.0 {
|
||||
println!("Unexpected output");
|
||||
}
|
||||
|
||||
timings.push(duration);
|
||||
}
|
||||
|
||||
// Sort for percentiles
|
||||
timings.sort();
|
||||
|
||||
let p50 = timings[iterations / 2];
|
||||
let p90 = timings[iterations * 9 / 10];
|
||||
let p99 = timings[iterations * 99 / 100];
|
||||
let p999 = timings[iterations * 999 / 1000];
|
||||
|
||||
println!("Results from {} iterations:", iterations);
|
||||
println!(" P50: {:?}", p50);
|
||||
println!(" P90: {:?}", p90);
|
||||
println!(" P99: {:?}", p99);
|
||||
println!(" P99.9: {:?}", p999);
|
||||
|
||||
let avg: Duration = timings.iter().sum::<Duration>() / iterations as u32;
|
||||
println!(" Average: {:?}", avg);
|
||||
|
||||
println!("\n=== Analysis ===");
|
||||
|
||||
if p999.as_micros() < 900 {
|
||||
println!("✅ Sub-0.9ms achieved at P99.9!");
|
||||
} else {
|
||||
println!("❌ Sub-0.9ms NOT achieved");
|
||||
println!(" P99.9 = {:.3}ms", p999.as_secs_f64() * 1000.0);
|
||||
println!(" This is REALISTIC for CPU inference");
|
||||
}
|
||||
|
||||
println!("\nOperations per inference:");
|
||||
println!(" Layer 1: {} multiply-adds", 128 * 32);
|
||||
println!(" Layer 2: {} multiply-adds", 32 * 4);
|
||||
println!(" Total: {} operations", 128 * 32 + 32 * 4);
|
||||
|
||||
let ops_per_sec = (128 * 32 + 32 * 4) as f64 * iterations as f64
|
||||
/ timings.iter().sum::<Duration>().as_secs_f64();
|
||||
println!("\nThroughput: {:.0} ops/second", ops_per_sec);
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
// Realistic implementation without mocked components
|
||||
use ndarray::{Array1, Array2};
|
||||
use std::time::{Duration, Instant};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NeuralError {
|
||||
#[error("Dimension mismatch: expected {expected}, got {got}")]
|
||||
DimensionMismatch { expected: usize, got: usize },
|
||||
|
||||
#[error("Computation error: {0}")]
|
||||
ComputationError(String),
|
||||
}
|
||||
|
||||
/// A real, simple neural network implementation
|
||||
/// No mocking, actual matrix operations
|
||||
pub struct SimpleNeuralNetwork {
|
||||
weights: Vec<Array2<f32>>,
|
||||
biases: Vec<Array1<f32>>,
|
||||
hidden_size: usize,
|
||||
}
|
||||
|
||||
impl SimpleNeuralNetwork {
|
||||
pub fn new(input_size: usize, hidden_size: usize, output_size: usize) -> Self {
|
||||
use ndarray_rand::RandomExt;
|
||||
use ndarray_rand::rand_distr::Uniform;
|
||||
|
||||
// Initialize with real random weights (Xavier initialization)
|
||||
let scale1 = (2.0 / input_size as f32).sqrt();
|
||||
let scale2 = (2.0 / hidden_size as f32).sqrt();
|
||||
|
||||
let w1 = Array2::random((hidden_size, input_size), Uniform::new(-scale1, scale1));
|
||||
let b1 = Array1::zeros(hidden_size);
|
||||
|
||||
let w2 = Array2::random((output_size, hidden_size), Uniform::new(-scale2, scale2));
|
||||
let b2 = Array1::zeros(output_size);
|
||||
|
||||
Self {
|
||||
weights: vec![w1, w2],
|
||||
biases: vec![b1, b2],
|
||||
hidden_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Real forward pass with actual computation
|
||||
pub fn forward(&self, input: &Array1<f32>) -> Result<Array1<f32>, NeuralError> {
|
||||
// Layer 1: input -> hidden
|
||||
let z1 = self.weights[0].dot(input) + &self.biases[0];
|
||||
let a1 = z1.mapv(|x| x.max(0.0)); // ReLU activation
|
||||
|
||||
// Layer 2: hidden -> output
|
||||
let z2 = self.weights[1].dot(&a1) + &self.biases[1];
|
||||
|
||||
Ok(z2)
|
||||
}
|
||||
|
||||
/// Measure real inference time
|
||||
pub fn timed_inference(&self, input: &Array1<f32>) -> Result<(Array1<f32>, Duration), NeuralError> {
|
||||
let start = Instant::now();
|
||||
let output = self.forward(input)?;
|
||||
let duration = start.elapsed();
|
||||
Ok((output, duration))
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplified Kalman filter for realistic comparison
|
||||
pub struct SimpleKalmanFilter {
|
||||
state: Array1<f32>,
|
||||
covariance: Array2<f32>,
|
||||
process_noise: f32,
|
||||
measurement_noise: f32,
|
||||
}
|
||||
|
||||
impl SimpleKalmanFilter {
|
||||
pub fn new(state_dim: usize) -> Self {
|
||||
Self {
|
||||
state: Array1::zeros(state_dim),
|
||||
covariance: Array2::eye(state_dim),
|
||||
process_noise: 0.01,
|
||||
measurement_noise: 0.1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Real Kalman filter prediction step
|
||||
pub fn predict(&mut self, dt: f32) -> Array1<f32> {
|
||||
// Simple constant velocity model
|
||||
// This is actual computation, not mocked
|
||||
let transition = Array2::eye(self.state.len());
|
||||
self.state = transition.dot(&self.state);
|
||||
self.covariance = &self.covariance + self.process_noise;
|
||||
|
||||
self.state.clone()
|
||||
}
|
||||
|
||||
/// Real Kalman filter update step
|
||||
pub fn update(&mut self, measurement: &Array1<f32>) {
|
||||
// Actual Kalman gain computation
|
||||
let innovation = measurement - &self.state;
|
||||
let innovation_covariance = &self.covariance + self.measurement_noise;
|
||||
let kalman_gain = &self.covariance / innovation_covariance;
|
||||
|
||||
self.state = &self.state + kalman_gain * innovation;
|
||||
self.covariance = &self.covariance * (1.0 - kalman_gain);
|
||||
}
|
||||
}
|
||||
|
||||
/// Realistic benchmark system
|
||||
pub struct RealisticBenchmark {
|
||||
nn: SimpleNeuralNetwork,
|
||||
kalman: SimpleKalmanFilter,
|
||||
}
|
||||
|
||||
impl RealisticBenchmark {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
nn: SimpleNeuralNetwork::new(128, 32, 4), // Realistic sizes
|
||||
kalman: SimpleKalmanFilter::new(4),
|
||||
}
|
||||
}
|
||||
|
||||
/// Measure actual computation time, no mocking
|
||||
pub fn benchmark_inference(&mut self, iterations: usize) -> Vec<Duration> {
|
||||
let mut timings = Vec::new();
|
||||
let input = Array1::from_vec(vec![0.1; 128]);
|
||||
|
||||
for _ in 0..iterations {
|
||||
let start = Instant::now();
|
||||
|
||||
// Real computation happens here
|
||||
let kalman_pred = self.kalman.predict(0.001);
|
||||
let nn_output = self.nn.forward(&input).unwrap();
|
||||
let combined = kalman_pred + nn_output;
|
||||
|
||||
// Force computation to complete (prevent optimization)
|
||||
std::hint::black_box(&combined);
|
||||
|
||||
timings.push(start.elapsed());
|
||||
}
|
||||
|
||||
timings
|
||||
}
|
||||
|
||||
/// Get realistic statistics
|
||||
pub fn analyze_timings(timings: &[Duration]) -> BenchmarkStats {
|
||||
let mut sorted = timings.to_vec();
|
||||
sorted.sort();
|
||||
|
||||
let len = sorted.len();
|
||||
let p50 = sorted[len / 2];
|
||||
let p90 = sorted[len * 9 / 10];
|
||||
let p99 = sorted[len * 99 / 100];
|
||||
let p999 = sorted[len * 999 / 1000];
|
||||
|
||||
let avg: Duration = sorted.iter().sum::<Duration>() / len as u32;
|
||||
|
||||
BenchmarkStats {
|
||||
p50,
|
||||
p90,
|
||||
p99,
|
||||
p999,
|
||||
average: avg,
|
||||
samples: len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BenchmarkStats {
|
||||
pub p50: Duration,
|
||||
pub p90: Duration,
|
||||
pub p99: Duration,
|
||||
pub p999: Duration,
|
||||
pub average: Duration,
|
||||
pub samples: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_real_neural_network() {
|
||||
let nn = SimpleNeuralNetwork::new(10, 5, 2);
|
||||
let input = Array1::from_vec(vec![0.1; 10]);
|
||||
let output = nn.forward(&input).unwrap();
|
||||
assert_eq!(output.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_real_timing() {
|
||||
let nn = SimpleNeuralNetwork::new(128, 32, 4);
|
||||
let input = Array1::from_vec(vec![0.1; 128]);
|
||||
let (_output, duration) = nn.timed_inference(&input).unwrap();
|
||||
|
||||
// Realistic: should take microseconds to milliseconds
|
||||
assert!(duration.as_micros() > 0);
|
||||
assert!(duration.as_millis() < 100); // Should be under 100ms
|
||||
|
||||
println!("Real inference time: {:?}", duration);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_benchmark_realistic() {
|
||||
let mut bench = RealisticBenchmark::new();
|
||||
let timings = bench.benchmark_inference(100);
|
||||
let stats = RealisticBenchmark::analyze_timings(&timings);
|
||||
|
||||
println!("Realistic benchmark results:");
|
||||
println!(" P50: {:?}", stats.p50);
|
||||
println!(" P90: {:?}", stats.p90);
|
||||
println!(" P99: {:?}", stats.p99);
|
||||
println!(" P99.9: {:?}", stats.p999);
|
||||
println!(" Average: {:?}", stats.average);
|
||||
|
||||
// Reality check: should be in microseconds to low milliseconds range
|
||||
assert!(stats.p50.as_micros() > 10); // At least 10 microseconds
|
||||
assert!(stats.p999.as_millis() < 100); // Under 100ms even at P99.9
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user