# lmn-core

Core engine for [lmn](https://github.com/talek-solutions/lmn) — a fast HTTP load testing tool.

This crate provides the building blocks for running load tests programmatically: HTTP execution, dynamic request templating, load curve definitions, result sampling, threshold evaluation, and report generation. The `lmn` CLI is built entirely on top of this crate.

> Full documentation at [https://lmn.talek.cloud](https://lmn.talek.cloud)

## Features

- **Fixed and curve-based execution** — run N requests at fixed concurrency, or drive VU counts dynamically over time with linear or step ramps
- **Dynamic request templates** — JSON templates with typed placeholder generators (strings, floats), `:once` placeholders, and `${ENV_VAR}` secret injection
- **Response field tracking** — extract and aggregate typed fields from response bodies across all requests
- **Two-stage sampling** — VU-threshold gate + Vitter's Algorithm R reservoir to bound memory while preserving statistical accuracy
- **Threshold evaluation** — pass/fail rules on latency percentiles, error rate, and throughput
- **Structured reports** — serializable `RunReport` with percentiles, per-stage breakdowns, status code distribution, and sampling metadata
- **OpenTelemetry tracing** — all major operations are instrumented with named spans

## Usage

```toml
[dependencies]
lmn-core = "0.1"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
```

## Key types

| Type | Module | Purpose |
|---|---|---|
| `RunCommand` | `command::run` | Entry point — owns request spec, execution mode, and sampling config |
| `ExecutionMode` | `execution` | `Fixed { request_count, concurrency }` or `Curve(LoadCurve)` |
| `RequestSpec` | `execution` | Host, method, body, template paths, headers |
| `SamplingConfig` | `execution` | VU threshold and reservoir size |
| `RunStats` | `execution` | Raw output of a completed run |
| `RunReport` | `output` | Serializable report built from `RunStats` |
| `LoadCurve` | `load_curve` | Staged VU ramp definition (parses from JSON) |
| `Threshold` | `threshold` | Single pass/fail rule on a metric |

---

### Minimal example — fixed load test

```rust,no_run
use lmn_core::command::{Command, Commands, HttpMethod};
use lmn_core::command::run::RunCommand;
use lmn_core::execution::{ExecutionMode, RequestSpec};
use lmn_core::output::{RunReport, RunReportParams};

#[tokio::main]
async fn main() {
    let cmd = RunCommand {
        request: RequestSpec {
            host: "https://example.com/api/ping".to_string(),
            method: HttpMethod::Get,
            body: None,
            template_path: None,
            response_template_path: None,
            headers: vec![],
        },
        execution: ExecutionMode::Fixed {
            request_count: 1000,
            concurrency: 50,
        },
    };

    if let Ok(Some(stats)) = Commands::Run(cmd).execute().await {
        let report = RunReport::from_params(RunReportParams { stats: &stats });
        println!("{}", serde_json::to_string_pretty(&report).unwrap());
    }
}
```

### Load curves

Define time-based VU ramps using `LoadCurve`, which parses from JSON:

```rust,no_run
use lmn_core::load_curve::LoadCurve;
use lmn_core::execution::ExecutionMode;

let curve: LoadCurve = r#"{
    "stages": [
        { "duration": "30s", "target_vus": 5 },
        { "duration": "2m",  "target_vus": 50, "ramp": "linear" },
        { "duration": "30s", "target_vus": 0,  "ramp": "linear" }
    ]
}"#.parse().unwrap();

let execution = ExecutionMode::Curve(curve);
```

Stage `ramp` defaults to `"linear"` if omitted. Use `"step"` for an immediate jump.

### Request templates

Load a JSON template file with typed placeholder definitions:

```rust,ignore
use lmn_core::request_template::Template;

let template = Template::parse(Path::new("request.json")).unwrap();

// Generate on demand — thread-safe, used by both fixed and curve executors
let body = template.generate_one();

// Or pre-generate N bodies upfront (e.g. for deterministic replay)
let bodies = template.pre_generate(1000);
```

Template files embed placeholder definitions under `_loadtest_metadata_templates`:

```json
{
  "user_id": "{{user_id}}",
  "amount":  "{{price}}",
  "_loadtest_metadata_templates": {
    "user_id": {
      "type": "string",
      "details": { "length": { "min": 8, "max": 16 } }
    },
    "price": {
      "type": "float",
      "min": 1.0,
      "max": 999.99,
      "details": { "decimals": 2 }
    }
  }
}
```

Environment variables are resolved at template load time:

```json
{ "token": "{{ENV:API_TOKEN}}" }
```

See [Template Placeholders](https://lmn.talek.cloud/reference/template-placeholders/) for the full reference.

### Thresholds

```rust,ignore
use lmn_core::threshold::{evaluate, EvaluateParams, parse_thresholds};

let thresholds = parse_thresholds(r#"{
    "thresholds": [
        { "metric": "latency_p99", "operator": "lt", "value": 200.0 },
        { "metric": "error_rate",  "operator": "lt", "value": 0.01  }
    ]
}"#).unwrap();

let result = evaluate(EvaluateParams { report: &report, thresholds: &thresholds });
if !result.all_passed() {
    eprintln!("thresholds failed: {:?}", result);
}
```

`parse_thresholds` accepts both JSON and YAML strings. Available metrics: `latency_p50`, `latency_p75`, `latency_p90`, `latency_p95`, `latency_p99`, `error_rate`, `throughput_rps`. Operators: `lt`, `lte`, `gt`, `gte`, `eq`.

## Configuration

If you prefer YAML-based configuration, `lmn-core` exposes a full config parser:

```rust,ignore
use lmn_core::config::parse_config;

let config = parse_config(yaml_str).unwrap();
```

See the [Config File Reference](https://lmn.talek.cloud/reference/config/) for the full YAML schema.

## License

Apache-2.0
