mirror of
https://github.com/ruvnet/RuView
synced 2026-08-08 20:11: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:
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
[package]
|
||||
name = "lean-agentic-wasm"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
description = "WASM bindings for Lean Agentic Learning System with WebSocket and SSE support"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
# WASM bindings
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
web-sys = { version = "0.3", features = [
|
||||
"console",
|
||||
"Window",
|
||||
"Document",
|
||||
"WebSocket",
|
||||
"MessageEvent",
|
||||
"CloseEvent",
|
||||
"ErrorEvent",
|
||||
"BinaryType",
|
||||
"EventSource",
|
||||
"EventTarget",
|
||||
"ReadableStream",
|
||||
"Response",
|
||||
"Request",
|
||||
"Headers",
|
||||
"RequestInit",
|
||||
"RequestMode",
|
||||
] }
|
||||
|
||||
# Serialization
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde-wasm-bindgen = "0.6"
|
||||
|
||||
# Async
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
|
||||
# Optimization
|
||||
wee_alloc = { version = "0.4", optional = true }
|
||||
|
||||
# Core lean agentic (simplified for WASM)
|
||||
async-trait = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ["-O4", "--enable-simd"]
|
||||
Vendored
+345
@@ -0,0 +1,345 @@
|
||||
# Lean Agentic WASM - Ultra-Low Latency Bindings
|
||||
|
||||
WebAssembly bindings for the Lean Agentic Learning System with **sub-millisecond latency** and support for WebSocket, SSE, and HTTP streaming.
|
||||
|
||||
## Features
|
||||
|
||||
- ⚡ **Ultra-Low Latency**: <1ms processing overhead
|
||||
- 🌐 **WebSocket Support**: Full-duplex real-time communication
|
||||
- 📡 **SSE Support**: Server-Sent Events for one-way streaming
|
||||
- 🔄 **HTTP Streaming**: Chunked transfer encoding support
|
||||
- 🚀 **High Throughput**: 50,000+ messages/second
|
||||
- 📦 **Small Bundle**: ~65KB (Brotli compressed)
|
||||
- 🔒 **Type Safe**: Full TypeScript definitions
|
||||
- 🧪 **Battle Tested**: Comprehensive benchmarks included
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Build WASM Module
|
||||
|
||||
```bash
|
||||
cd wasm
|
||||
wasm-pack build --release --target web
|
||||
```
|
||||
|
||||
### 2. Run Demo
|
||||
|
||||
```bash
|
||||
cd www
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open http://localhost:8080 in your browser.
|
||||
|
||||
## Installation
|
||||
|
||||
### NPM Package
|
||||
|
||||
```bash
|
||||
npm install lean-agentic-wasm
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```javascript
|
||||
import init, { LeanAgenticClient, WebSocketClient } from 'lean-agentic-wasm';
|
||||
|
||||
async function run() {
|
||||
// Initialize WASM
|
||||
await init();
|
||||
|
||||
// Create client
|
||||
const client = new LeanAgenticClient('session-001', null);
|
||||
|
||||
// Process messages
|
||||
const result = client.process_message('What is the weather?');
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
run();
|
||||
```
|
||||
|
||||
## WebSocket Example
|
||||
|
||||
```javascript
|
||||
import { WebSocketClient, LeanAgenticClient } from 'lean-agentic-wasm';
|
||||
|
||||
// Create WebSocket connection
|
||||
const ws = new WebSocketClient('ws://localhost:8080/ws');
|
||||
|
||||
// Create agentic client
|
||||
const client = new LeanAgenticClient('ws-session', null);
|
||||
|
||||
// Set message handler
|
||||
ws.set_on_message((data) => {
|
||||
const start = performance.now();
|
||||
|
||||
// Process with lean agentic system
|
||||
const result = client.process_message(data);
|
||||
|
||||
const latency = performance.now() - start;
|
||||
console.log(`Processed in ${latency.toFixed(2)}ms:`, result);
|
||||
});
|
||||
|
||||
// Send message
|
||||
ws.send('Hello, world!');
|
||||
```
|
||||
|
||||
## SSE Example
|
||||
|
||||
```javascript
|
||||
import { SSEClient, LeanAgenticClient } from 'lean-agentic-wasm';
|
||||
|
||||
// Create SSE connection
|
||||
const sse = new SSEClient('http://localhost:8080/sse');
|
||||
|
||||
// Create agentic client
|
||||
const client = new LeanAgenticClient('sse-session', null);
|
||||
|
||||
// Set message handler
|
||||
sse.set_on_message((data) => {
|
||||
const result = client.process_message(data);
|
||||
console.log('Received:', result);
|
||||
});
|
||||
```
|
||||
|
||||
## HTTP Streaming Example
|
||||
|
||||
```javascript
|
||||
import { StreamingHTTPClient, LeanAgenticClient } from 'lean-agentic-wasm';
|
||||
|
||||
const http = new StreamingHTTPClient('http://localhost:8080/stream');
|
||||
const client = new LeanAgenticClient('http-session', null);
|
||||
|
||||
await http.stream((chunk) => {
|
||||
const result = client.process_message(chunk);
|
||||
console.log('Chunk:', result);
|
||||
});
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Benchmarks (Chrome 120, M1 Mac)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| p50 latency | 0.15ms |
|
||||
| p95 latency | 0.35ms |
|
||||
| p99 latency | 0.55ms |
|
||||
| Max latency | 1.2ms |
|
||||
| Throughput (single) | 50,000 msg/s |
|
||||
| Throughput (100 concurrent) | 25,000 msg/s |
|
||||
| WASM size (uncompressed) | 180 KB |
|
||||
| WASM size (Brotli) | 65 KB |
|
||||
|
||||
### Running Benchmarks
|
||||
|
||||
Open the demo at http://localhost:8080 and click the "Benchmark" tab.
|
||||
|
||||
Or run programmatically:
|
||||
|
||||
```javascript
|
||||
// Latency benchmark
|
||||
const latencies = [];
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
const start = performance.now();
|
||||
client.process_message(`test ${i}`);
|
||||
latencies.push(performance.now() - start);
|
||||
}
|
||||
|
||||
const p50 = latencies.sort()[Math.floor(latencies.length * 0.5)];
|
||||
console.log(`p50: ${p50.toFixed(3)}ms`);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### LeanAgenticClient
|
||||
|
||||
```typescript
|
||||
class LeanAgenticClient {
|
||||
constructor(sessionId: string, config?: LeanAgenticConfig);
|
||||
process_message(message: string): ProcessingResult;
|
||||
get_avg_latency_ms(): number;
|
||||
get_message_count(): number;
|
||||
get_session_id(): string;
|
||||
}
|
||||
```
|
||||
|
||||
### WebSocketClient
|
||||
|
||||
```typescript
|
||||
class WebSocketClient {
|
||||
constructor(url: string);
|
||||
set_on_message(callback: (data: string) => void): void;
|
||||
set_on_error(callback: (error: string) => void): void;
|
||||
set_on_close(callback: (code: number) => void): void;
|
||||
send(message: string): void;
|
||||
send_binary(data: Uint8Array): void;
|
||||
close(): void;
|
||||
ready_state(): number;
|
||||
}
|
||||
```
|
||||
|
||||
### SSEClient
|
||||
|
||||
```typescript
|
||||
class SSEClient {
|
||||
constructor(url: string);
|
||||
set_on_message(callback: (data: string) => void): void;
|
||||
close(): void;
|
||||
ready_state(): number;
|
||||
}
|
||||
```
|
||||
|
||||
### StreamingHTTPClient
|
||||
|
||||
```typescript
|
||||
class StreamingHTTPClient {
|
||||
constructor(url: string);
|
||||
stream(callback: (chunk: string) => void): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with agentic-flow
|
||||
|
||||
```javascript
|
||||
import { LeanAgenticClient } from 'lean-agentic-wasm';
|
||||
import { AgenticFlowBridge } from '../integrations/agentic_flow_bridge';
|
||||
|
||||
const bridge = new AgenticFlowBridge('http://localhost:8080', {
|
||||
agents: [
|
||||
{ id: 'agent1', name: 'Weather', type: 'specialist', capabilities: ['weather'], config: {} },
|
||||
{ id: 'agent2', name: 'Calendar', type: 'specialist', capabilities: ['calendar'], config: {} },
|
||||
],
|
||||
});
|
||||
|
||||
// Execute workflow
|
||||
const result = await bridge.executeWorkflow('workflow_id', inputs, context);
|
||||
|
||||
// Create swarm
|
||||
const swarm = await bridge.createSwarm(['agent1', 'agent2'], 'task', context);
|
||||
```
|
||||
|
||||
## Optimization Tips
|
||||
|
||||
### 1. Use Release Builds
|
||||
|
||||
```bash
|
||||
wasm-pack build --release --target web
|
||||
```
|
||||
|
||||
### 2. Enable SIMD
|
||||
|
||||
Ensure your `Cargo.toml` has:
|
||||
|
||||
```toml
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ["-O4", "--enable-simd"]
|
||||
```
|
||||
|
||||
### 3. Batch Processing
|
||||
|
||||
```javascript
|
||||
const batch = [];
|
||||
ws.set_on_message((data) => {
|
||||
batch.push(data);
|
||||
|
||||
if (batch.length >= 100) {
|
||||
batch.forEach(msg => client.process_message(msg));
|
||||
batch.length = 0;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Pre-allocate Connections
|
||||
|
||||
```javascript
|
||||
const connections = Array.from(
|
||||
{ length: 10 },
|
||||
(_, i) => new WebSocketClient(`ws://server${i}.example.com`)
|
||||
);
|
||||
```
|
||||
|
||||
## Building for Production
|
||||
|
||||
### 1. Optimize WASM
|
||||
|
||||
```bash
|
||||
cd wasm
|
||||
wasm-pack build --release --target web
|
||||
|
||||
# Strip debug info
|
||||
wasm-strip pkg/lean_agentic_wasm_bg.wasm
|
||||
|
||||
# Compress
|
||||
brotli -o pkg/lean_agentic_wasm_bg.wasm.br pkg/lean_agentic_wasm_bg.wasm
|
||||
```
|
||||
|
||||
### 2. Bundle with Webpack
|
||||
|
||||
```javascript
|
||||
// webpack.config.js
|
||||
module.exports = {
|
||||
experiments: {
|
||||
asyncWebAssembly: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Serve with Compression
|
||||
|
||||
```nginx
|
||||
# nginx.conf
|
||||
location ~ \.wasm$ {
|
||||
types { application/wasm wasm; }
|
||||
gzip on;
|
||||
gzip_types application/wasm;
|
||||
brotli on;
|
||||
brotli_types application/wasm;
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Import Error
|
||||
|
||||
Make sure to initialize WASM before use:
|
||||
|
||||
```javascript
|
||||
import init from 'lean-agentic-wasm';
|
||||
await init();
|
||||
```
|
||||
|
||||
### High Latency
|
||||
|
||||
1. Check network latency with browser DevTools
|
||||
2. Verify WebSocket compression is disabled
|
||||
3. Use binary mode: `ws.binaryType = 'arraybuffer'`
|
||||
|
||||
### Memory Issues
|
||||
|
||||
1. Limit cache sizes in config
|
||||
2. Use buffer pools for frequent operations
|
||||
3. Monitor with `performance.memory`
|
||||
|
||||
## Examples
|
||||
|
||||
See the [examples directory](./examples) for:
|
||||
- WebSocket chat application
|
||||
- SSE real-time dashboard
|
||||
- HTTP streaming analyzer
|
||||
- Multi-agent swarm coordination
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Performance Guide](../WASM_PERFORMANCE_GUIDE.md)
|
||||
- [Integration Guide](../integrations/README.md)
|
||||
- [API Documentation](./docs/API.md)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Vendored
+444
@@ -0,0 +1,444 @@
|
||||
//! Ultra-low-latency WASM bindings for Lean Agentic Learning System
|
||||
//!
|
||||
//! Features:
|
||||
//! - WebSocket streaming with minimal overhead
|
||||
//! - SSE (Server-Sent Events) support
|
||||
//! - HTTP streaming
|
||||
//! - Zero-copy message passing where possible
|
||||
//! - Optimized for latency (<1ms overhead)
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{WebSocket, EventSource, MessageEvent, CloseEvent, ErrorEvent};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
// Use wee_alloc for smaller binary size
|
||||
#[cfg(feature = "wee_alloc")]
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
/// Initialize panic hook for better error messages
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn init() {
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
/// Configuration for the lean agentic system
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct LeanAgenticConfig {
|
||||
#[wasm_bindgen(skip)]
|
||||
pub enable_formal_verification: bool,
|
||||
|
||||
#[wasm_bindgen(skip)]
|
||||
pub learning_rate: f64,
|
||||
|
||||
#[wasm_bindgen(skip)]
|
||||
pub max_planning_depth: usize,
|
||||
|
||||
#[wasm_bindgen(skip)]
|
||||
pub action_threshold: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl LeanAgenticConfig {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enable_formal_verification: true,
|
||||
learning_rate: 0.01,
|
||||
max_planning_depth: 5,
|
||||
action_threshold: 0.7,
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn enable_formal_verification(&self) -> bool {
|
||||
self.enable_formal_verification
|
||||
}
|
||||
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_enable_formal_verification(&mut self, value: bool) {
|
||||
self.enable_formal_verification = value;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn learning_rate(&self) -> f64 {
|
||||
self.learning_rate
|
||||
}
|
||||
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_learning_rate(&mut self, value: f64) {
|
||||
self.learning_rate = value;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LeanAgenticConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Processing result
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ProcessingResult {
|
||||
pub action: String,
|
||||
pub reward: f64,
|
||||
pub verified: bool,
|
||||
#[wasm_bindgen(skip)]
|
||||
pub timestamp: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ProcessingResult {
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn timestamp(&self) -> f64 {
|
||||
self.timestamp
|
||||
}
|
||||
}
|
||||
|
||||
/// WebSocket client for ultra-low-latency streaming
|
||||
#[wasm_bindgen]
|
||||
pub struct WebSocketClient {
|
||||
socket: WebSocket,
|
||||
#[wasm_bindgen(skip)]
|
||||
on_message: Rc<RefCell<Option<js_sys::Function>>>,
|
||||
#[wasm_bindgen(skip)]
|
||||
on_error: Rc<RefCell<Option<js_sys::Function>>>,
|
||||
#[wasm_bindgen(skip)]
|
||||
on_close: Rc<RefCell<Option<js_sys::Function>>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WebSocketClient {
|
||||
/// Create a new WebSocket connection
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(url: &str) -> Result<WebSocketClient, JsValue> {
|
||||
let socket = WebSocket::new(url)?;
|
||||
|
||||
// Set binary type for optimal performance
|
||||
socket.set_binary_type(web_sys::BinaryType::Arraybuffer);
|
||||
|
||||
Ok(Self {
|
||||
socket,
|
||||
on_message: Rc::new(RefCell::new(None)),
|
||||
on_error: Rc::new(RefCell::new(None)),
|
||||
on_close: Rc::new(RefCell::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Set message handler with minimal overhead
|
||||
pub fn set_on_message(&mut self, callback: js_sys::Function) -> Result<(), JsValue> {
|
||||
*self.on_message.borrow_mut() = Some(callback.clone());
|
||||
|
||||
let on_message_ref = self.on_message.clone();
|
||||
|
||||
let closure = Closure::wrap(Box::new(move |e: MessageEvent| {
|
||||
if let Some(cb) = on_message_ref.borrow().as_ref() {
|
||||
// Zero-copy data access when possible
|
||||
let data = if let Ok(txt) = e.data().dyn_into::<js_sys::JsString>() {
|
||||
txt
|
||||
} else if let Ok(array_buffer) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
|
||||
// Convert ArrayBuffer to string
|
||||
let array = js_sys::Uint8Array::new(&array_buffer);
|
||||
let vec = array.to_vec();
|
||||
JsValue::from_str(&String::from_utf8_lossy(&vec))
|
||||
} else {
|
||||
e.data()
|
||||
};
|
||||
|
||||
let _ = cb.call1(&JsValue::NULL, &data);
|
||||
}
|
||||
}) as Box<dyn FnMut(MessageEvent)>);
|
||||
|
||||
self.socket.set_onmessage(Some(closure.as_ref().unchecked_ref()));
|
||||
closure.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set error handler
|
||||
pub fn set_on_error(&mut self, callback: js_sys::Function) -> Result<(), JsValue> {
|
||||
*self.on_error.borrow_mut() = Some(callback.clone());
|
||||
|
||||
let on_error_ref = self.on_error.clone();
|
||||
|
||||
let closure = Closure::wrap(Box::new(move |e: ErrorEvent| {
|
||||
if let Some(cb) = on_error_ref.borrow().as_ref() {
|
||||
let _ = cb.call1(&JsValue::NULL, &JsValue::from_str(&e.message()));
|
||||
}
|
||||
}) as Box<dyn FnMut(ErrorEvent)>);
|
||||
|
||||
self.socket.set_onerror(Some(closure.as_ref().unchecked_ref()));
|
||||
closure.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set close handler
|
||||
pub fn set_on_close(&mut self, callback: js_sys::Function) -> Result<(), JsValue> {
|
||||
*self.on_close.borrow_mut() = Some(callback);
|
||||
|
||||
let on_close_ref = self.on_close.clone();
|
||||
|
||||
let closure = Closure::wrap(Box::new(move |e: CloseEvent| {
|
||||
if let Some(cb) = on_close_ref.borrow().as_ref() {
|
||||
let _ = cb.call1(&JsValue::NULL, &JsValue::from(e.code()));
|
||||
}
|
||||
}) as Box<dyn FnMut(CloseEvent)>);
|
||||
|
||||
self.socket.set_onclose(Some(closure.as_ref().unchecked_ref()));
|
||||
closure.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send message with minimal overhead
|
||||
pub fn send(&self, message: &str) -> Result<(), JsValue> {
|
||||
self.socket.send_with_str(message)
|
||||
}
|
||||
|
||||
/// Send binary message
|
||||
pub fn send_binary(&self, data: &[u8]) -> Result<(), JsValue> {
|
||||
self.socket.send_with_u8_array(data)
|
||||
}
|
||||
|
||||
/// Close connection
|
||||
pub fn close(&self) -> Result<(), JsValue> {
|
||||
self.socket.close()
|
||||
}
|
||||
|
||||
/// Get ready state
|
||||
pub fn ready_state(&self) -> u16 {
|
||||
self.socket.ready_state()
|
||||
}
|
||||
}
|
||||
|
||||
/// SSE (Server-Sent Events) client for streaming
|
||||
#[wasm_bindgen]
|
||||
pub struct SSEClient {
|
||||
event_source: EventSource,
|
||||
#[wasm_bindgen(skip)]
|
||||
on_message: Rc<RefCell<Option<js_sys::Function>>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SSEClient {
|
||||
/// Create new SSE connection
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(url: &str) -> Result<SSEClient, JsValue> {
|
||||
let event_source = EventSource::new(url)?;
|
||||
|
||||
Ok(Self {
|
||||
event_source,
|
||||
on_message: Rc::new(RefCell::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Set message handler
|
||||
pub fn set_on_message(&mut self, callback: js_sys::Function) -> Result<(), JsValue> {
|
||||
*self.on_message.borrow_mut() = Some(callback.clone());
|
||||
|
||||
let on_message_ref = self.on_message.clone();
|
||||
|
||||
let closure = Closure::wrap(Box::new(move |e: MessageEvent| {
|
||||
if let Some(cb) = on_message_ref.borrow().as_ref() {
|
||||
let _ = cb.call1(&JsValue::NULL, &e.data());
|
||||
}
|
||||
}) as Box<dyn FnMut(MessageEvent)>);
|
||||
|
||||
self.event_source.set_onmessage(Some(closure.as_ref().unchecked_ref()));
|
||||
closure.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Close connection
|
||||
pub fn close(&self) {
|
||||
self.event_source.close();
|
||||
}
|
||||
|
||||
/// Get ready state
|
||||
pub fn ready_state(&self) -> u16 {
|
||||
self.event_source.ready_state()
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP Streaming client using Fetch API with streaming
|
||||
#[wasm_bindgen]
|
||||
pub struct StreamingHTTPClient {
|
||||
url: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl StreamingHTTPClient {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(url: &str) -> Self {
|
||||
Self {
|
||||
url: url.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start streaming with minimal latency
|
||||
pub async fn stream(&self, callback: js_sys::Function) -> Result<(), JsValue> {
|
||||
let window = web_sys::window().ok_or("No window")?;
|
||||
|
||||
let mut opts = web_sys::RequestInit::new();
|
||||
opts.method("GET");
|
||||
|
||||
let request = web_sys::Request::new_with_str_and_init(&self.url, &opts)?;
|
||||
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
let resp: web_sys::Response = resp_value.dyn_into()?;
|
||||
|
||||
let body = resp.body().ok_or("No body")?;
|
||||
let reader = body.get_reader();
|
||||
|
||||
// Read stream chunks
|
||||
loop {
|
||||
let chunk_promise = js_sys::Reflect::get(&reader, &JsValue::from_str("read"))?
|
||||
.dyn_into::<js_sys::Function>()?
|
||||
.call0(&reader)?;
|
||||
|
||||
let chunk_result = JsFuture::from(js_sys::Promise::from(chunk_promise)).await?;
|
||||
|
||||
let done = js_sys::Reflect::get(&chunk_result, &JsValue::from_str("done"))?
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
|
||||
if done {
|
||||
break;
|
||||
}
|
||||
|
||||
let value = js_sys::Reflect::get(&chunk_result, &JsValue::from_str("value"))?;
|
||||
|
||||
if let Ok(array) = value.dyn_into::<js_sys::Uint8Array>() {
|
||||
let vec = array.to_vec();
|
||||
let text = String::from_utf8_lossy(&vec);
|
||||
callback.call1(&JsValue::NULL, &JsValue::from_str(&text))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// High-performance agent client
|
||||
#[wasm_bindgen]
|
||||
pub struct LeanAgenticClient {
|
||||
config: LeanAgenticConfig,
|
||||
session_id: String,
|
||||
#[wasm_bindgen(skip)]
|
||||
message_count: u64,
|
||||
#[wasm_bindgen(skip)]
|
||||
total_latency_ms: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl LeanAgenticClient {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(session_id: String, config: Option<LeanAgenticConfig>) -> Self {
|
||||
Self {
|
||||
config: config.unwrap_or_default(),
|
||||
session_id,
|
||||
message_count: 0,
|
||||
total_latency_ms: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Process message with minimal latency
|
||||
pub fn process_message(&mut self, message: &str) -> Result<JsValue, JsValue> {
|
||||
let start = js_sys::Date::now();
|
||||
|
||||
// Fast processing logic
|
||||
let action_type = if message.to_lowercase().contains("weather") {
|
||||
"get_weather"
|
||||
} else if message.to_lowercase().contains("learn") || message.to_lowercase().contains("remember") {
|
||||
"update_knowledge"
|
||||
} else {
|
||||
"process_text"
|
||||
};
|
||||
|
||||
let reward = 0.8; // Placeholder
|
||||
|
||||
let result = ProcessingResult {
|
||||
action: action_type.to_string(),
|
||||
reward,
|
||||
verified: self.config.enable_formal_verification,
|
||||
timestamp: js_sys::Date::now(),
|
||||
};
|
||||
|
||||
self.message_count += 1;
|
||||
let latency = js_sys::Date::now() - start;
|
||||
self.total_latency_ms += latency;
|
||||
|
||||
// Serialize to JS
|
||||
serde_wasm_bindgen::to_value(&result).map_err(|e| JsValue::from_str(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Get average latency in milliseconds
|
||||
pub fn get_avg_latency_ms(&self) -> f64 {
|
||||
if self.message_count == 0 {
|
||||
0.0
|
||||
} else {
|
||||
self.total_latency_ms / self.message_count as f64
|
||||
}
|
||||
}
|
||||
|
||||
/// Get message count
|
||||
pub fn get_message_count(&self) -> u64 {
|
||||
self.message_count
|
||||
}
|
||||
|
||||
/// Get session ID
|
||||
pub fn get_session_id(&self) -> String {
|
||||
self.session_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility: Log to console
|
||||
#[wasm_bindgen]
|
||||
pub fn log(message: &str) {
|
||||
web_sys::console::log_1(&JsValue::from_str(message));
|
||||
}
|
||||
|
||||
/// Utility: Get high-resolution timestamp
|
||||
#[wasm_bindgen]
|
||||
pub fn now() -> f64 {
|
||||
js_sys::Date::now()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_config_creation() {
|
||||
let config = LeanAgenticConfig::new();
|
||||
assert!(config.enable_formal_verification);
|
||||
assert_eq!(config.learning_rate, 0.01);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_client_creation() {
|
||||
let client = LeanAgenticClient::new("test_session".to_string(), None);
|
||||
assert_eq!(client.get_session_id(), "test_session");
|
||||
assert_eq!(client.get_message_count(), 0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_message_processing() {
|
||||
let mut client = LeanAgenticClient::new("test".to_string(), None);
|
||||
let result = client.process_message("What's the weather?");
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
Vendored
+291
@@ -0,0 +1,291 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lean Agentic WASM - Ultra Low Latency Streaming</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #333;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #667eea;
|
||||
margin-bottom: 10px;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9em;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
color: #667eea;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.log {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin-bottom: 8px;
|
||||
padding: 5px;
|
||||
border-left: 3px solid #667eea;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.log-entry.error {
|
||||
border-left-color: #e74c3c;
|
||||
background: rgba(231, 76, 60, 0.1);
|
||||
}
|
||||
|
||||
.log-entry.success {
|
||||
border-left-color: #2ecc71;
|
||||
background: rgba(46, 204, 113, 0.1);
|
||||
}
|
||||
|
||||
.latency-meter {
|
||||
height: 100px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.latency-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: linear-gradient(180deg, #2ecc71 0%, #27ae60 100%);
|
||||
transition: height 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
background: #e0e0e0;
|
||||
border: none;
|
||||
border-radius: 6px 6px 0 0;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.connecting {
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🚀 Lean Agentic WASM</h1>
|
||||
<p class="subtitle">Ultra-low-latency stream learning with WebSocket, SSE, and HTTP streaming</p>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Avg Latency</div>
|
||||
<div class="stat-value" id="avg-latency">0.0ms</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Messages Processed</div>
|
||||
<div class="stat-value" id="msg-count">0</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Throughput</div>
|
||||
<div class="stat-value" id="throughput">0/s</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Connection Status</div>
|
||||
<div class="stat-value" id="status">Disconnected</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-container">
|
||||
<button class="tab active" onclick="switchTab('websocket')">WebSocket</button>
|
||||
<button class="tab" onclick="switchTab('sse')">SSE</button>
|
||||
<button class="tab" onclick="switchTab('http')">HTTP Streaming</button>
|
||||
<button class="tab" onclick="switchTab('benchmark')">Benchmark</button>
|
||||
</div>
|
||||
|
||||
<div id="websocket-tab" class="tab-content active">
|
||||
<div class="demo-section">
|
||||
<h2>WebSocket Streaming</h2>
|
||||
<div class="controls">
|
||||
<input type="text" id="ws-url" placeholder="ws://localhost:8080/ws" value="ws://localhost:8080/ws">
|
||||
<button onclick="connectWebSocket()">Connect</button>
|
||||
<button onclick="disconnectWebSocket()">Disconnect</button>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<input type="text" id="ws-message" placeholder="Enter message...">
|
||||
<button onclick="sendWebSocketMessage()">Send</button>
|
||||
<button onclick="startWebSocketBurst()">Burst Test (1000 msgs)</button>
|
||||
</div>
|
||||
<div class="log" id="ws-log"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sse-tab" class="tab-content">
|
||||
<div class="demo-section">
|
||||
<h2>Server-Sent Events (SSE)</h2>
|
||||
<div class="controls">
|
||||
<input type="text" id="sse-url" placeholder="http://localhost:8080/sse" value="http://localhost:8080/sse">
|
||||
<button onclick="connectSSE()">Connect</button>
|
||||
<button onclick="disconnectSSE()">Disconnect</button>
|
||||
</div>
|
||||
<div class="log" id="sse-log"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="http-tab" class="tab-content">
|
||||
<div class="demo-section">
|
||||
<h2>HTTP Streaming</h2>
|
||||
<div class="controls">
|
||||
<input type="text" id="http-url" placeholder="http://localhost:8080/stream" value="http://localhost:8080/stream">
|
||||
<button onclick="startHTTPStream()">Start Stream</button>
|
||||
</div>
|
||||
<div class="log" id="http-log"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="benchmark-tab" class="tab-content">
|
||||
<div class="demo-section">
|
||||
<h2>Performance Benchmark</h2>
|
||||
<div class="controls">
|
||||
<button onclick="runLatencyBenchmark()">Latency Test</button>
|
||||
<button onclick="runThroughputBenchmark()">Throughput Test</button>
|
||||
<button onclick="runConcurrentBenchmark()">Concurrent Test</button>
|
||||
</div>
|
||||
<div class="log" id="benchmark-log"></div>
|
||||
<div class="latency-meter" id="latency-meter">
|
||||
<div class="latency-bar" id="latency-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+311
@@ -0,0 +1,311 @@
|
||||
import * as wasm from 'lean-agentic-wasm';
|
||||
|
||||
// Global state
|
||||
let wsClient = null;
|
||||
let sseClient = null;
|
||||
let httpClient = null;
|
||||
let agenticClient = null;
|
||||
let messageCount = 0;
|
||||
let startTime = Date.now();
|
||||
|
||||
// Initialize WASM and agentic client
|
||||
async function init() {
|
||||
try {
|
||||
agenticClient = new wasm.LeanAgenticClient('demo-session', null);
|
||||
log('ws', 'WASM module loaded successfully', 'success');
|
||||
updateStats();
|
||||
} catch (err) {
|
||||
log('ws', `Failed to initialize: ${err}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Tab switching
|
||||
window.switchTab = function(tab) {
|
||||
const tabs = document.querySelectorAll('.tab');
|
||||
const contents = document.querySelectorAll('.tab-content');
|
||||
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
contents.forEach(c => c.classList.remove('active'));
|
||||
|
||||
document.querySelector(`.tab:nth-child(${getTabIndex(tab)})`).classList.add('active');
|
||||
document.getElementById(`${tab}-tab`).classList.add('active');
|
||||
};
|
||||
|
||||
function getTabIndex(tab) {
|
||||
const tabs = ['websocket', 'sse', 'http', 'benchmark'];
|
||||
return tabs.indexOf(tab) + 1;
|
||||
}
|
||||
|
||||
// WebSocket functions
|
||||
window.connectWebSocket = async function() {
|
||||
const url = document.getElementById('ws-url').value;
|
||||
|
||||
try {
|
||||
wsClient = new wasm.WebSocketClient(url);
|
||||
|
||||
wsClient.set_on_message((data) => {
|
||||
const start = performance.now();
|
||||
const result = agenticClient.process_message(data);
|
||||
const latency = performance.now() - start;
|
||||
|
||||
log('ws', `Received: ${data} | Latency: ${latency.toFixed(2)}ms`, 'success');
|
||||
messageCount++;
|
||||
updateStats();
|
||||
});
|
||||
|
||||
wsClient.set_on_error((error) => {
|
||||
log('ws', `Error: ${error}`, 'error');
|
||||
});
|
||||
|
||||
wsClient.set_on_close((code) => {
|
||||
log('ws', `Connection closed: ${code}`, 'error');
|
||||
document.getElementById('status').textContent = 'Disconnected';
|
||||
});
|
||||
|
||||
// Wait for connection
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
if (wsClient.ready_state() === 1) {
|
||||
log('ws', `Connected to ${url}`, 'success');
|
||||
document.getElementById('status').textContent = 'Connected (WS)';
|
||||
}
|
||||
} catch (err) {
|
||||
log('ws', `Connection failed: ${err}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.disconnectWebSocket = function() {
|
||||
if (wsClient) {
|
||||
wsClient.close();
|
||||
wsClient = null;
|
||||
log('ws', 'Disconnected', 'success');
|
||||
document.getElementById('status').textContent = 'Disconnected';
|
||||
}
|
||||
};
|
||||
|
||||
window.sendWebSocketMessage = function() {
|
||||
const message = document.getElementById('ws-message').value;
|
||||
|
||||
if (wsClient && wsClient.ready_state() === 1) {
|
||||
const start = performance.now();
|
||||
wsClient.send(message);
|
||||
const latency = performance.now() - start;
|
||||
|
||||
log('ws', `Sent: ${message} | Send latency: ${latency.toFixed(3)}ms`, 'success');
|
||||
document.getElementById('ws-message').value = '';
|
||||
} else {
|
||||
log('ws', 'Not connected', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.startWebSocketBurst = async function() {
|
||||
if (!wsClient || wsClient.ready_state() !== 1) {
|
||||
log('ws', 'Not connected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const count = 1000;
|
||||
const start = performance.now();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
wsClient.send(`Message ${i}`);
|
||||
}
|
||||
|
||||
const duration = performance.now() - start;
|
||||
const throughput = (count / duration) * 1000;
|
||||
|
||||
log('ws', `Burst test: ${count} messages in ${duration.toFixed(2)}ms (${throughput.toFixed(0)} msg/s)`, 'success');
|
||||
};
|
||||
|
||||
// SSE functions
|
||||
window.connectSSE = function() {
|
||||
const url = document.getElementById('sse-url').value;
|
||||
|
||||
try {
|
||||
sseClient = new wasm.SSEClient(url);
|
||||
|
||||
sseClient.set_on_message((data) => {
|
||||
const start = performance.now();
|
||||
const result = agenticClient.process_message(data);
|
||||
const latency = performance.now() - start;
|
||||
|
||||
log('sse', `Received: ${data} | Latency: ${latency.toFixed(2)}ms`, 'success');
|
||||
messageCount++;
|
||||
updateStats();
|
||||
});
|
||||
|
||||
log('sse', `Connected to ${url}`, 'success');
|
||||
document.getElementById('status').textContent = 'Connected (SSE)';
|
||||
} catch (err) {
|
||||
log('sse', `Connection failed: ${err}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.disconnectSSE = function() {
|
||||
if (sseClient) {
|
||||
sseClient.close();
|
||||
sseClient = null;
|
||||
log('sse', 'Disconnected', 'success');
|
||||
document.getElementById('status').textContent = 'Disconnected';
|
||||
}
|
||||
};
|
||||
|
||||
// HTTP Streaming
|
||||
window.startHTTPStream = async function() {
|
||||
const url = document.getElementById('http-url').value;
|
||||
|
||||
try {
|
||||
httpClient = new wasm.StreamingHTTPClient(url);
|
||||
|
||||
log('http', `Starting stream from ${url}...`, 'success');
|
||||
|
||||
await httpClient.stream((data) => {
|
||||
const start = performance.now();
|
||||
const result = agenticClient.process_message(data);
|
||||
const latency = performance.now() - start;
|
||||
|
||||
log('http', `Chunk: ${data.substring(0, 50)}... | Latency: ${latency.toFixed(2)}ms`, 'success');
|
||||
messageCount++;
|
||||
updateStats();
|
||||
});
|
||||
|
||||
log('http', 'Stream completed', 'success');
|
||||
} catch (err) {
|
||||
log('http', `Stream error: ${err}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// Benchmark functions
|
||||
window.runLatencyBenchmark = function() {
|
||||
log('benchmark', 'Running latency benchmark...', 'success');
|
||||
|
||||
const iterations = 10000;
|
||||
const latencies = [];
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const start = performance.now();
|
||||
agenticClient.process_message(`Test message ${i}`);
|
||||
const latency = performance.now() - start;
|
||||
latencies.push(latency);
|
||||
}
|
||||
|
||||
const avg = latencies.reduce((a, b) => a + b, 0) / iterations;
|
||||
const sorted = latencies.sort((a, b) => a - b);
|
||||
const p50 = sorted[Math.floor(iterations * 0.5)];
|
||||
const p95 = sorted[Math.floor(iterations * 0.95)];
|
||||
const p99 = sorted[Math.floor(iterations * 0.99)];
|
||||
const min = sorted[0];
|
||||
const max = sorted[iterations - 1];
|
||||
|
||||
log('benchmark', `Latency Statistics (${iterations} iterations):`, 'success');
|
||||
log('benchmark', ` Min: ${min.toFixed(3)}ms`, 'success');
|
||||
log('benchmark', ` P50: ${p50.toFixed(3)}ms`, 'success');
|
||||
log('benchmark', ` P95: ${p95.toFixed(3)}ms`, 'success');
|
||||
log('benchmark', ` P99: ${p99.toFixed(3)}ms`, 'success');
|
||||
log('benchmark', ` Max: ${max.toFixed(3)}ms`, 'success');
|
||||
log('benchmark', ` Avg: ${avg.toFixed(3)}ms`, 'success');
|
||||
|
||||
updateLatencyMeter(avg, p99);
|
||||
};
|
||||
|
||||
window.runThroughputBenchmark = function() {
|
||||
log('benchmark', 'Running throughput benchmark...', 'success');
|
||||
|
||||
const duration = 5000; // 5 seconds
|
||||
const start = performance.now();
|
||||
let count = 0;
|
||||
|
||||
while (performance.now() - start < duration) {
|
||||
agenticClient.process_message(`Throughput test ${count}`);
|
||||
count++;
|
||||
}
|
||||
|
||||
const actualDuration = performance.now() - start;
|
||||
const throughput = (count / actualDuration) * 1000;
|
||||
|
||||
log('benchmark', `Throughput: ${throughput.toFixed(0)} messages/second`, 'success');
|
||||
log('benchmark', `Total processed: ${count} messages in ${actualDuration.toFixed(0)}ms`, 'success');
|
||||
|
||||
document.getElementById('throughput').textContent = `${throughput.toFixed(0)}/s`;
|
||||
};
|
||||
|
||||
window.runConcurrentBenchmark = async function() {
|
||||
log('benchmark', 'Running concurrent sessions benchmark...', 'success');
|
||||
|
||||
const sessions = 100;
|
||||
const messagesPerSession = 10;
|
||||
const clients = [];
|
||||
|
||||
for (let i = 0; i < sessions; i++) {
|
||||
clients.push(new wasm.LeanAgenticClient(`session-${i}`, null));
|
||||
}
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
// Process messages concurrently
|
||||
const promises = clients.map((client, i) => {
|
||||
return new Promise(resolve => {
|
||||
for (let j = 0; j < messagesPerSession; j++) {
|
||||
client.process_message(`Session ${i} message ${j}`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const duration = performance.now() - start;
|
||||
const totalMessages = sessions * messagesPerSession;
|
||||
const throughput = (totalMessages / duration) * 1000;
|
||||
|
||||
log('benchmark', `Concurrent test: ${sessions} sessions`, 'success');
|
||||
log('benchmark', `Total messages: ${totalMessages}`, 'success');
|
||||
log('benchmark', `Duration: ${duration.toFixed(2)}ms`, 'success');
|
||||
log('benchmark', `Throughput: ${throughput.toFixed(0)} msg/s`, 'success');
|
||||
log('benchmark', `Avg per session: ${(duration / sessions).toFixed(2)}ms`, 'success');
|
||||
};
|
||||
|
||||
// Utility functions
|
||||
function log(tab, message, type = 'info') {
|
||||
const logDiv = document.getElementById(`${tab}-log`);
|
||||
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
||||
const entry = document.createElement('div');
|
||||
entry.className = `log-entry ${type}`;
|
||||
entry.textContent = `[${timestamp}] ${message}`;
|
||||
logDiv.appendChild(entry);
|
||||
logDiv.scrollTop = logDiv.scrollHeight;
|
||||
}
|
||||
|
||||
function updateStats() {
|
||||
if (agenticClient) {
|
||||
const avgLatency = agenticClient.get_avg_latency_ms();
|
||||
const msgCount = agenticClient.get_message_count();
|
||||
const elapsed = (Date.now() - startTime) / 1000;
|
||||
const throughput = msgCount / elapsed;
|
||||
|
||||
document.getElementById('avg-latency').textContent = `${avgLatency.toFixed(2)}ms`;
|
||||
document.getElementById('msg-count').textContent = msgCount;
|
||||
document.getElementById('throughput').textContent = `${throughput.toFixed(0)}/s`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateLatencyMeter(avg, p99) {
|
||||
const meter = document.getElementById('latency-bar');
|
||||
const maxLatency = 10; // 10ms max for visualization
|
||||
const percentage = Math.min((avg / maxLatency) * 100, 100);
|
||||
meter.style.height = `${percentage}%`;
|
||||
|
||||
if (avg < 1) {
|
||||
meter.style.background = 'linear-gradient(180deg, #2ecc71 0%, #27ae60 100%)';
|
||||
} else if (avg < 5) {
|
||||
meter.style.background = 'linear-gradient(180deg, #f39c12 0%, #e67e22 100%)';
|
||||
} else {
|
||||
meter.style.background = 'linear-gradient(180deg, #e74c3c 0%, #c0392b 100%)';
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-update stats
|
||||
setInterval(updateStats, 1000);
|
||||
|
||||
// Initialize on load
|
||||
init();
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "lean-agentic-wasm-demo",
|
||||
"version": "1.0.0",
|
||||
"description": "Ultra-low-latency WASM demo with WebSocket and SSE support",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --mode production",
|
||||
"dev": "webpack serve --mode development",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"lean-agentic-wasm": "file:../pkg",
|
||||
"agentic-flow": "^1.8.10",
|
||||
"@midstream/lean-agentic": "file:../../lean-agentic-js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wasm-tool/wasm-pack-plugin": "^1.7.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user