Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 56 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ LLM-based agents can accelerate development only if they respect our house rules

| Requirement | Rationale |
|--------------|-----------|
| **British English** spelling (`organisation`, `licence`, *not* `organization`, `license`) except technical US spellings like `synchronized` | Keeps wording consistent with Chronicle's London HQ and existing docs. See the University of Oxford style guide for reference. |
| **ASCII-7 only** (code-points 0-127). Avoid smart quotes, non-breaking spaces and accented characters. | ASCII-7 survives every toolchain Chronicle uses, incl. low-latency binary wire formats that expect the 8th bit to be 0. |
| If a symbol is not available in ASCII-7, use a textual form such as `micro-second`, `>=`, `:alpha:`, `:yes:`. This is the preferred approach and Unicode must not be inserted. | Extended or '8-bit ASCII' variants are *not* portable and are therefore disallowed. |
| **British English** spelling (`organisation`, `licence`, *not* `organization`, `license`) except technical US spellings like `synchronized` | Keeps wording consistent with Chronicle's London HQ and existing docs. See the [University of Oxford style guide](https://www.ox.ac.uk/public-affairs/style-guide) for reference. |
| **ISO-8859-1** (code-points 0-255). Avoid smart quotes, non-breaking spaces and accented characters. | ISO-8859-1 survives every toolchain Chronicle uses. |
| If a symbol is not available in ISO-8859-1, use a textual form such as `>=`, `:alpha:`, `:yes:`. This is the preferred approach and Unicode must not be inserted. | Extended or '8-bit ASCII' variants are *not* portable and are therefore disallowed. |
| Tools to check ASCII compliance include `iconv -f ascii -t ascii` and IDE settings that flag non-ASCII characters. | These help catch stray Unicode characters before code review. |

## Javadoc guidelines

Expand All @@ -26,8 +27,20 @@ noise and slows readers down.
| Prefer `@param` for *constraints* and `@throws` for *conditions*, following Oracle's style guide. | Pad comments to reach a line-length target. |
| Remove or rewrite autogenerated Javadoc for trivial getters/setters. | Leave stale comments that now contradict the code. |

The principle that Javadoc should only explain what is *not* manifest from the signature is well-established in the
wider Java community.
The principle that Javadoc should only explain what is *not* manifest from the
signature is well-established in the wider Java community.

Inline comments should also avoid noise. The following example shows the
difference:

```java
// BAD: adds no value
int count; // the count

// GOOD: explains a subtlety
// count of messages pending flush
int count;
```

## Build & test commands

Expand All @@ -40,11 +53,18 @@ mvn -q verify

## Commit-message & PR etiquette

1. **Subject line <= 72 chars**, imperative mood: "Fix roll-cycle offset in `ExcerptAppender`".
1. **Subject line <= 72 chars**, imperative mood: Fix roll-cycle offset in `ExcerptAppender`.
2. Reference the JIRA/GitHub issue if it exists.
3. In *body*: *root cause -> fix -> measurable impact* (latency, allocation, etc.). Use ASCII bullet points.
4. **Run `mvn verify`** again after rebasing.

### When to open a PR

* Open a pull request once your branch builds and tests pass with `mvn -q clean verify`.
* Link the PR to the relevant issue or decision record.
* Keep PRs focused: avoid bundling unrelated refactoring with new features.
* Re-run the build after addressing review comments to ensure nothing broke.

## What to ask the reviewers

* *Is this AsciiDoc documentation precise enough for a clean-room re-implementation?*
Expand All @@ -53,6 +73,14 @@ mvn -q verify
* Does the commit point back to the relevant requirement or decision tag?
* Would an example or small diagram help future maintainers?

### Security checklist (review **after every change**)

**Run a security review on *every* PR**: Walk through the diff looking for input validation, authentication, authorisation, encoding/escaping, overflow, resource exhaustion and timing-attack issues.

**Never commit secrets or credentials**: tokens, passwords, private keys, TLS materials, internal hostnames, Use environment variables, HashiCorp Vault, AWS/GCP Secret Manager, etc.

**Document security trade-offs**: Chronicle prioritises low-latency systems; sometimes we relax safety checks for specific reasons. Future maintainers must find these hot-spots quickly, In Javadoc and `.adoc` files call out *why* e.g. "Unchecked cast for performance - assumes trusted input".

## Project requirements

See the [Decision Log](src/main/adoc/decision-log.adoc) for the latest project decisions.
Expand Down Expand Up @@ -84,7 +112,7 @@ This tight loop informs the AI accurately and creates immediate clarity for all

When using AI agents to assist with development, please adhere to the following guidelines:

* **Respect the Language & Character-set Policy**: Ensure all AI-generated content follows the British English and ASCII-7 guidelines outlined above.
* **Respect the Language & Character-set Policy**: Ensure all AI-generated content follows the British English and ISO-8859-1 guidelines outlined above.
Focus on Clarity: AI-generated documentation should be clear and concise and add value beyond what is already present in the code or existing documentation.
* **Avoid Redundancy**: Do not generate content that duplicates existing documentation or code comments unless it provides additional context or clarification.
* **Review AI Outputs**: Always review AI-generated content for accuracy, relevance, and adherence to the project's documentation standards before committing it to the repository.
Expand Down Expand Up @@ -122,8 +150,7 @@ Date:: YYYY-MM-DD
Context::
* What is the issue that this decision addresses?
* What are the driving forces, constraints, and requirements?
Decision Statement::
* What is the change that is being proposed or was decided?
Decision Statement :: What is the change that is being proposed or was decided?
Alternatives Considered::
* [Alternative 1 Name/Type]:
** *Description:* Brief description of the alternative.
Expand Down Expand Up @@ -159,3 +186,23 @@ section:: Top Level Section
### Emphasis and Bold Text

In AsciiDoc, an underscore `_` is _emphasis_; `*text*` is *bold*.

### Section Numbering

Use automatic section numbering for all `.adoc` files.

* Add `:sectnums:` to the document header.
* Do not prefix section titles with manual numbers to avoid duplication.

```asciidoc
= Document Title
Chronicle Software
:toc:
:sectnums:
:lang: en-GB
:source-highlighter: rouge

The document overview goes here.

== Section 1 Title
```
219 changes: 219 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

JLBH (Java Latency Benchmark Harness) is a specialised benchmarking library for measuring end-to-end latency in Java applications under realistic throughput conditions. Unlike JMH micro-benchmarks, JLBH is designed to benchmark code "in context" and account for coordinated omission. It is particularly suited for producer/consumer workloads where iteration start and completion may occur on different threads.

## Build & Test Commands

```bash
# Verify build and run all tests
mvn -q verify

# Clean build from scratch
mvn -q clean verify

# Run a specific test class
mvn -q test -Dtest=JLBHTest

# Run a single test method
mvn -q test -Dtest=JLBHTest#shouldProvideResultData

# Compile only (skip tests)
mvn -q compile

# Skip tests during verify
mvn -q verify -DskipTests
```

## Language & Character-Set Requirements

**Critical**: All code and documentation must use:
- **British English** spelling (`organisation`, `licence`) except for Java keywords like `synchronized`
- **ISO-8859-1** character encoding only (code points 0-255)
- **No Unicode characters**: Use ASCII equivalents like `>=` instead of special symbols
- Verify with: `iconv -f ascii -t ascii <file>`

See AGENTS.md for complete language policy and rationale.

## Architecture Overview

### Core Components

**JLBH** (src/main/java/net/openhft/chronicle/jlbh/JLBH.java)
- The main orchestrator that manages the entire benchmark lifecycle
- Runs on a single thread (annotated `@SingleThreaded`)
- Provides `sample(long durationNs)` to record end-to-end latencies
- Provides `addProbe(String)` to create additional `NanoSampler` instances for measuring sub-stages
- Can run standalone via `start()` or integrate with Chronicle EventLoop via `eventLoopHandler(EventLoop)`

**JLBHOptions** (src/main/java/net/openhft/chronicle/jlbh/JLBHOptions.java)
- Builder-style configuration object defining all benchmark parameters
- Key settings: `throughput()`, `iterations()`, `runs()`, `warmUpIterations()`, `accountForCoordinatedOmission()`, `recordOSJitter()`
- Default throughput: 10,000 ops/sec; default runs: 3

**JLBHTask** (src/main/java/net/openhft/chronicle/jlbh/JLBHTask.java)
- User-implemented interface defining the workload to benchmark
- Lifecycle methods called by JLBH:
- `init(JLBH)`: Setup phase, register probes
- `run(long startTimeNs)`: Execute benchmark iteration (called for each iteration)
- `warmedUp()`: Notification after warmup completes
- `runComplete()`: Notification after each run
- `complete()`: Final cleanup

**Histograms & Results**
- Each probe (end-to-end, custom probes, OS jitter) records samples into a `Histogram` from chronicle-core
- Histograms use logarithmic bucketing with 35-bit range and 8 significant figures
- Immutable result objects: `JLBHResult`, `ProbeResult`, `RunResult`
- Use `ThreadSafeJLBHResultConsumer` to retrieve results from another thread

### Threading Model

- **Single-threaded harness**: All JLBHTask lifecycle methods run on the single JLBH thread
- **User code may spawn threads**: The benchmarked code within `JLBHTask.run()` can be multi-threaded
- **Sample recording thread-safety**: Only the JLBH harness thread should call `jlbh.sample()` or probe samplers
- **Event loop integration**: Via `eventLoopHandler()` instead of `start()` (requires coordinated omission accounting enabled)

### Execution Flow

1. **Initialization**: User configures `JLBHOptions`, creates `JLBH` instance, calls `JLBHTask.init()`
2. **Warm-up**: Runs `warmUpIterations` times to allow JIT compilation (default ~12k iterations)
3. **Measurement runs**: Executes `runs` times (default 3), each with `iterations` samples
4. **Completion**: Prints summary, constructs immutable `JLBHResult`, calls `JLBHTask.complete()`

See src/main/adoc/benchmark-lifecycle.adoc for visual flow diagram.

### Coordinated Omission

JLBH accounts for coordinated omission by default (`accountForCoordinatedOmission = true`):
- `startTimeNs` passed to `run()` is the *calculated ideal start time*, not `System.nanoTime()`
- Harness busy-waits if current time is before scheduled start time
- Disabling this feature means start time is simply based on throughput without waiting

### OS Jitter Monitoring

Enabled by default via `recordOSJitter(true)`:
- Background thread `OSJitterMonitor` repeatedly samples `System.nanoTime()` to detect scheduler delays
- Records jitter > `recordJitterGreaterThanNs` (default: 1000ns) into separate histogram
- Incurs overhead; disable with `recordOSJitter(false)` for minimal-overhead benchmarks

## Key Design Patterns

**Probes for Multi-Stage Benchmarks**
```java
class MyTask implements JLBHTask {
private JLBH jlbh;
private NanoSampler stage1Probe;

public void init(JLBH jlbh) {
this.jlbh = jlbh;
this.stage1Probe = jlbh.addProbe("stage1");
}

public void run(long startTimeNs) {
long stage1Start = System.nanoTime();
// ... stage 1 work ...
stage1Probe.sampleNanos(System.nanoTime() - stage1Start);

// ... remaining work ...
jlbh.sample(System.nanoTime() - startTimeNs);
}
}
```

**Typical Benchmark Setup**
```java
JLBHOptions options = new JLBHOptions()
.warmUpIterations(100_000)
.iterations(1_000_000)
.throughput(50_000)
.runs(3)
.jlbhTask(myTask);
new JLBH(options).start();
```

**Thread-Safe Result Retrieval**
```java
JLBHResultConsumer resultConsumer = JLBHResultConsumer.newThreadSafeInstance();
JLBH jlbh = new JLBH(options, System.out, resultConsumer);
// Run on separate thread
new Thread(() -> jlbh.start()).start();
// Retrieve results from main thread after completion
JLBHResult result = resultConsumer.get();
```

## Documentation Structure

All requirements and design decisions are in src/main/adoc/:
- **project-requirements.adoc**: Formal requirements specification with tagged requirements (FN-xxx, NF-P-xxx, etc.)
- **decision-log.adoc**: Architectural decision records (ADRs) following Nine-Box taxonomy
- **architecture.adoc**: High-level architecture, components, execution flow, threading model
- **benchmark-lifecycle.adoc**: Visual diagram of benchmark execution phases
- **jlbh-cookbook.adoc**: Common usage patterns and recipes
- **results-interpretation-guide.adoc**: How to interpret percentile output

When making changes:
1. Update relevant .adoc files first (if changing requirements/design)
2. Update code and tests
3. Verify all three stay synchronised in same commit

## Javadoc Standards

Follow guidelines from AGENTS.md:
- Document *behavioural contracts*, edge cases, thread-safety, units, performance characteristics
- Do NOT restate the obvious ("Gets the value")
- First sentence must be concise (becomes summary line)
- Remove autogenerated Javadoc for trivial getters/setters
- Explain *why* and *how*, not just *what*

## Test Examples

Test files in src/test/java/net/openhft/chronicle/jlbh/ demonstrate usage:
- `ExampleJLBHMain`: Basic command-line harness demonstration
- `SimpleBenchmark`: Minimal benchmark example
- `SimpleOSJitterBenchmark`: Demonstrates OS jitter recording and custom probes
- `JLBHTest`: Unit test showing programmatic result extraction
- `JLBHIntegrationTest`: Example integration test

## Performance Requirements

From project-requirements.adoc (NF-P requirements):
- Overhead per sample: < 100 ns when no additional probes active
- Histogram generation: Support >= 200M iterations without heap pressure
- Warm-up rule of thumb: ~30% of run iterations (default uses JIT compile threshold * 6/5)

## Dependencies

Key dependencies from pom.xml:
- **chronicle-core**: Provides `Histogram`, `NanoSampler`, threading utilities
- **affinity**: CPU affinity control via `AffinityLock`
- **chronicle-threads**: Event loop integration (test scope)

## Common Gotchas

- JLBH is `@SingleThreaded` - the harness itself runs on one thread
- The `startTimeNs` parameter to `run()` is NOT `System.nanoTime()` - it's the calculated ideal start time
- Warm-up iterations use ~12k by default; adjust with `warmUpIterations()` if benchmark takes long to stabilise
- OS jitter monitoring is enabled by default and adds overhead; explicitly disable if needed
- When using `eventLoopHandler()`, coordinated omission must remain enabled
- CSV export: Use `JLBHResultSerializer.runResultToCSV(jlbhResult)` (writes to `result.csv` by default)

## Non-Functional Quality Attributes

From README.adoc summary:
- **Performance**: < 100 ns overhead per sample, >= 200M iterations supported
- **Reliability**: Graceful abort on interruption/timeout, immutable thread-safe results
- **Usability**: Fluent API, runnable in <= 10 LOC, human-readable ASCII output
- **Portability**: Pure Java, runtime-detected JDK optimisations
- **Maintainability**: >= 80% test coverage (current: ~83% line, ~78% branch per pom.xml)

## Commit & PR Guidelines

From AGENTS.md:
- Subject line <= 72 chars, imperative mood
- Body: root cause -> fix -> measurable impact
- Run `mvn -q verify` before opening PR
- Keep PRs focused; avoid bundling unrelated changes
- Re-run build after addressing review comments
8 changes: 5 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Chronicle Software
:toc: macro
:toclevels: 2
:icons: font
:lang: en-GB
:source-highlighter: rouge

image:https://maven-badges.herokuapp.com/maven-central/net.openhft/jlbh/badge.svg[caption="",link=https://maven-badges.herokuapp.com/maven-central/net.openhft/jlbh]
image:https://javadoc.io/badge2/net.openhft/JLBH/javadoc.svg[link="https://www.javadoc.io/doc/net.openhft/chronicle-wire/latest/index.html"]
Expand All @@ -17,12 +19,12 @@ toc::[]

== About

Java Latency Benchmark Harness is a tool that allows you to benchmark your code running in context, rather than in a microbenchmark.
Java Latency Benchmark Harness is a tool that allows you to benchmark your code running in context, rather than in a micro-benchmark.
See <<further-reading,Further Reading>> for a series of articles introducing JLBH.
An excellent introduction can be found in http://www.rationaljava.com/2016/04/a-series-of-posts-on-jlbh-java-latency.html[this series of articles.]
An excellent introduction can be found in http://www.rationaljava.com/2016/04/a-series-of-posts-on-jlbh-java-latency.html[this series of articles].
link:src/main/adoc/project-requirements.adoc[The requirements document] contains detailed feature descriptions.

For terminology used throughout the project, see link:src/main/adoc/project-requirements.adoc#_7-glossary[the Glossary (section 7)].
For terminology used throughout the project, see link:src/main/adoc/project-requirements.adoc#_glossary[the Glossary (section 7)].

Since those articles were written the main change has been to allow JLBH to be installed to an event loop, rather than it running in its own thread.
To do this, use the JLBH.eventLoopHandler method rather than JLBH.start.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<dependency>
<groupId>net.openhft</groupId>
<artifactId>third-party-bom</artifactId>
<version>3.27ea5</version>
<version>3.27ea7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down
Loading