Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e366988
test: add append visibility test for Latest cursor
XTLine Jul 30, 2026
793466e
perf(storage): publish LAC via ArcSwap and drop ledger content locks
XTLine Jul 30, 2026
29614f1
Remove unneeded mut binding
XTLine Jul 31, 2026
bd8fe06
perf(storage): cap RocksDB memory usage
XTLine Aug 12, 2026
15b4303
perf storage leak between scenarios
XTLine Jul 29, 2026
dba75f2
test: increase stress test offered load
XTLine Jul 30, 2026
21f9e3a
test: Add perf log parsing and E2E diagnostics tests
XTLine Jul 31, 2026
1d63ef7
Refactor perf parsing and error reporting
XTLine Jul 31, 2026
9867496
feat: add external broker backend and local cgroup limits
XTLine Aug 4, 2026
15fca7f
Monitor external broker via systemd unit and cgroup
XTLine Aug 7, 2026
1834646
Update perf stress scripts for external broker runs
XTLine Aug 13, 2026
7137100
Add TODO comment in read_position
XTLine Aug 4, 2026
55906cd
fix(broker): bound shared dispatcher memory with unacked gate and mer…
XTLine Aug 6, 2026
1534a36
perf(broker): switch to jemalloc to avoid glibc arena memory retention
XTLine Aug 6, 2026
bd3bf6d
style: cargo fmt
XTLine Aug 21, 2026
180470e
feat(proto): extract pulsar binary protocol into pulsar-lite-proto crate
XTLine Aug 8, 2026
2c73988
refactor(broker): remove duplicated protocol/storage modules from mai…
XTLine Aug 8, 2026
407b697
fix(broker): resolve orphan-rule violation via ConsumerMessage newtyp…
XTLine Aug 8, 2026
11c4f26
refactor(broker): migrate imports to extracted pulsar-lite-proto and …
XTLine Aug 8, 2026
e5213af
style: cargo fmt workspace formatting
XTLine Aug 8, 2026
5da74d6
perf(broker): decouple dispatch from send-receipt path
XTLine Aug 12, 2026
87bdd4c
perf(broker): remove locks from persistent enqueue hot path
XTLine Aug 12, 2026
6e5af16
perf(storage): coalesce individual acks into ranges
XTLine Aug 12, 2026
b3f244b
debug(broker): add TEMP DIAG file-based probes for ack pipeline
XTLine Aug 12, 2026
aa8b6d8
fix(broker): pass persistent_appender in test connection builder
XTLine Aug 12, 2026
c5c16e1
Fix permit accounting and backpressure in broker dispatch
XTLine Aug 13, 2026
cc4b065
Add pending publish byte limits and connection write-state config
XTLine Aug 13, 2026
970e76b
chore(broker): remove TEMP DIAG instrumentation probes
XTLine Aug 13, 2026
6acc438
style: cargo fmt and clippy 1.98 fixes (while-let loop in cursor)
XTLine Aug 21, 2026
b4dc1c2
fix(broker): restore persistent_appender arg and drop redundant u32 c…
XTLine Aug 21, 2026
3acae83
fix(broker): apply flow permits synchronously under the subscription …
XTLine Aug 21, 2026
6a9a8eb
fix(broker): enable TCP_NODELAY on accepted sockets
XTLine Aug 18, 2026
8daa9e9
perf(broker): ordered batched fan-out worker for non-persistent publish
XTLine Aug 18, 2026
21238db
chore(broker): demote per-Flow log to debug
XTLine Aug 18, 2026
3e84dc5
perf(broker): batch persistent send receipts into single flushes
XTLine Aug 21, 2026
7f621af
feat(metrics): add pulsar-lite-metrics crate with storage-side families
XTLine Aug 19, 2026
c4d1337
feat(broker): wire Prometheus metrics across broker layers
XTLine Aug 19, 2026
8e61aa3
feat(observability): add Grafana dashboards and compose stack
XTLine Aug 19, 2026
bf11655
docs: add monitoring section to README
XTLine Aug 19, 2026
1b9980f
fix(perf): give each perf broker a dedicated scrape-able metrics port
XTLine Aug 20, 2026
e542f06
feat(broker): batch-aware message accounting across dispatch and comm…
XTLine Aug 21, 2026
98189d6
fix(stats): keep previous gauge value when storage lock is busy
XTLine Aug 21, 2026
086120b
chore(grafana): expose Grafana on host port 7070 to avoid 3000 conflicts
XTLine Aug 21, 2026
c0003ad
style: cargo fmt and clippy 1.98 fixes (checked_div in metrics)
XTLine Aug 21, 2026
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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ local setup cost:
| Non-persistent topics | Dispatch-or-drop runtime semantics with coverage for flow control, disconnect/reconnect, ordering, dynamic consumers, and KeyShared routing. |
| Persistent topics | RocksDB-backed managed-ledger style storage is available behind the `rocksdb-storage` feature. |
| Subscription modes | Shared, Failover, Exclusive, and KeyShared are covered by Rust and Python integration tests. |
| Monitoring | Prometheus metrics on `GET /metrics` (port 8080): broker/topic/subscription counters, backlog and unacked gauges, storage latency and entry-size histograms, process metrics. Optional Grafana stack under `grafana/`. |
| Partitioned topics | Default partition metadata and partition topic routing are supported for local testing. |
| Python package | Provides a small helper SDK that can start and manage a local broker process. |

Expand Down Expand Up @@ -110,6 +111,39 @@ Stop the broker:
../rust/pulsar-lite.sh stop
```

## Monitoring

The broker exposes Prometheus metrics on `GET /metrics` (default
`0.0.0.0:8080`, same port model as native Pulsar's web service). Metric
families reuse native Pulsar names and labels (`pulsar_rate_in`,
`pulsar_subscription_back_log`, `pulsar_storage_write_latency`, ...), so
existing dashboards and PromQL translate directly; extensions unique to
Pulsar Lite use the `pulsar_lite_*` prefix.

Quick check:

```bash
curl -s localhost:8080/metrics | grep -E '^pulsar_(broker|subscription)'
```

Configuration (`rust/pulsar-lite.toml`):

```toml
[metrics]
enabled = true # false: no listener, no scrape aggregation
addr = "0.0.0.0:8080"
cluster = "pulsar-lite" # `cluster` label value on every family
rate_window_secs = 60 # window for pulsar_rate_in-style gauges
```

A ready-to-run Prometheus + Grafana stack (with provisioned dashboards for
topics and broker overview) lives under [`grafana/`](grafana/README.md):

```bash
docker compose -f grafana/docker-compose.yml up -d
# Grafana http://localhost:3000 (admin/admin), Prometheus http://localhost:9090
```

## Embedded Python Usage

The Python helper can start a local broker for short-lived tests or examples:
Expand Down
68 changes: 68 additions & 0 deletions grafana/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Pulsar Lite Observability Stack

A one-command Prometheus + Grafana stack that scrapes the broker's
`GET /metrics` endpoint (default `0.0.0.0:8080`).

## Quick start

```bash
# 1. Start the broker (metrics are on by default)
../rust/target/release/pulsar-lite --config ../rust/pulsar-lite.toml

# 2. Start the observability stack
docker compose up -d

# 3. Open
# Grafana http://localhost:3000 (admin/admin; anonymous read enabled)
# Prometheus http://localhost:9090/targets — the pulsar-lite job must be UP
```

Dashboards are provisioned automatically (folder `Pulsar Lite`):

- **Pulsar Lite / Topics** — publish/deliver rates and throughput, entity
counts, subscription backlog, unacked gate state, redelivery, storage
size, end-to-end and ledger write latency (P50/P99), entry-size
distribution.
- **Pulsar Lite / Broker** — broker-level rates (counter `rate()` and
window-gauge views), connections and rejection reasons, backlog and
storage totals, write-queue batch sizes, process RSS/CPU.

## Live view during perf tests

The perf harness (`tests/perf/`) starts real brokers, each with a private
metrics port derived from its protocol port (6651/6652 → 8081/8082,
6661/6662 → 8091/8092, 6671/6672 → 8101/8102, bound on `0.0.0.0`). This
stack scrapes all of them (`job="pulsar-lite-perf"`), so with the stack up
you can watch a run live:

```bash
docker compose up -d # once
python tests/perf/run_persistent_stress.py ... # then run any scenario
# Grafana http://localhost:3000 — dashboards show the running broker's series
```

Perf targets show DOWN in Prometheus `/targets` while no run is active —
that is expected. Docker-backed perf runs use `--network host`, so the same
ports apply.

## Metric naming conventions

- `pulsar_*` families reproduce native Apache Pulsar names and label sets
verbatim, so official dashboards and PromQL translate directly.
- `pulsar_lite_*` families are extensions with no native counterpart
(error reasons, redelivery counters, write-queue batch metrics).
- Histograms use the standard Prometheus shape (`_bucket{le=...}` +
`_sum` + `_count`); query percentiles with `histogram_quantile()`.

## Broker configuration

```toml
[metrics]
enabled = true # false: no listener, no scrape aggregation
addr = "0.0.0.0:8080" # /metrics path; must be reachable by the scraper
cluster = "pulsar-lite" # cluster label value on every family
rate_window_secs = 60 # window for pulsar_rate_in-style gauges
```

Remote broker: change the target in `prometheus/prometheus.yml` from
`host.docker.internal:8080` to your broker address.
223 changes: 223 additions & 0 deletions grafana/dashboards/pulsar-lite-broker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
{
"uid": "pulsar-lite-broker",
"title": "Pulsar Lite / Broker",
"tags": ["pulsar-lite"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"editable": true,
"refresh": "10s",
"time": { "from": "now-30m", "to": "now" },
"templating": {
"list": [
{
"name": "cluster",
"label": "Cluster",
"type": "query",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"query": "label_values(pulsar_broker_rate_in, cluster)",
"refresh": 2,
"includeAll": false,
"multi": false,
"current": { "text": "pulsar-lite", "value": "pulsar-lite" }
}
]
},
"panels": [
{
"id": 1,
"type": "timeseries",
"title": "Broker message rate (in / out)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"targets": [
{
"expr": "pulsar_broker_rate_in{cluster=\"$cluster\"}",
"legendFormat": "in"
},
{
"expr": "pulsar_broker_rate_out{cluster=\"$cluster\"}",
"legendFormat": "out"
}
]
},
{
"id": 2,
"type": "timeseries",
"title": "Broker throughput (bytes/s)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
"targets": [
{
"expr": "pulsar_broker_throughput_in{cluster=\"$cluster\"}",
"legendFormat": "in"
},
{
"expr": "pulsar_broker_throughput_out{cluster=\"$cluster\"}",
"legendFormat": "out"
}
]
},
{
"id": 3,
"type": "timeseries",
"title": "Accepted / delivered totals (rate over counters)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
"targets": [
{
"expr": "rate(pulsar_broker_in_messages_total{cluster=\"$cluster\"}[1m])",
"legendFormat": "published msg/s"
},
{
"expr": "rate(pulsar_broker_out_messages_total{cluster=\"$cluster\"}[1m])",
"legendFormat": "delivered msg/s"
},
{
"expr": "rate(pulsar_broker_in_bytes_total{cluster=\"$cluster\"}[1m])",
"legendFormat": "published B/s"
},
{
"expr": "rate(pulsar_broker_out_bytes_total{cluster=\"$cluster\"}[1m])",
"legendFormat": "delivered B/s"
}
]
},
{
"id": 4,
"type": "timeseries",
"title": "Entity counts",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 6, "x": 12, "y": 8 },
"targets": [
{
"expr": "pulsar_broker_topics_count{cluster=\"$cluster\"}",
"legendFormat": "topics"
},
{
"expr": "pulsar_broker_subscriptions_count{cluster=\"$cluster\"}",
"legendFormat": "subscriptions"
},
{
"expr": "pulsar_broker_producers_count{cluster=\"$cluster\"}",
"legendFormat": "producers"
},
{
"expr": "pulsar_broker_consumers_count{cluster=\"$cluster\"}",
"legendFormat": "consumers"
}
]
},
{
"id": 5,
"type": "timeseries",
"title": "Connections",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 6, "x": 18, "y": 8 },
"targets": [
{
"expr": "pulsar_active_connections{cluster=\"$cluster\"}",
"legendFormat": "active"
},
{
"expr": "rate(pulsar_connection_created_total_count{cluster=\"$cluster\"}[5m])",
"legendFormat": "created /s"
},
{
"expr": "rate(pulsar_connection_closed_total_count{cluster=\"$cluster\"}[5m])",
"legendFormat": "closed /s"
}
]
},
{
"id": 6,
"type": "timeseries",
"title": "Rejections by reason (/s)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 },
"targets": [
{
"expr": "sum by (reason) (rate(pulsar_lite_broker_errors_total{cluster=\"$cluster\"}[5m]))",
"legendFormat": "{{reason}}"
}
]
},
{
"id": 7,
"type": "timeseries",
"title": "Backlog + storage size",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 6, "x": 12, "y": 16 },
"targets": [
{
"expr": "pulsar_broker_msg_backlog{cluster=\"$cluster\"}",
"legendFormat": "backlog entries"
},
{
"expr": "pulsar_broker_storage_size{cluster=\"$cluster\"}",
"legendFormat": "stored bytes"
}
]
},
{
"id": 8,
"type": "timeseries",
"title": "Publish rate-limit rejections (/s)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 6, "x": 18, "y": 16 },
"targets": [
{
"expr": "sum by (topic) (rate(pulsar_publish_rate_limit_times{cluster=\"$cluster\"}[5m]))",
"legendFormat": "{{topic}}"
}
]
},
{
"id": 9,
"type": "timeseries",
"title": "Write-queue batch size (avg / P99)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 8, "x": 0, "y": 24 },
"targets": [
{
"expr": "rate(pulsar_lite_write_queue_batch_messages_total[1m]) / rate(pulsar_lite_write_queue_batches_total[1m])",
"legendFormat": "avg batch"
},
{
"expr": "histogram_quantile(0.99, sum by (le) (rate(pulsar_lite_write_queue_batch_size_bucket[5m])))",
"legendFormat": "P99 batch"
}
]
},
{
"id": 10,
"type": "timeseries",
"title": "Broker RSS",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 8, "x": 8, "y": 24 },
"fieldConfig": {
"defaults": { "unit": "bytes" },
"overrides": []
},
"targets": [
{
"expr": "process_resident_memory_bytes",
"legendFormat": "RSS"
}
]
},
{
"id": 11,
"type": "timeseries",
"title": "Broker CPU (seconds/s)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 8, "x": 16, "y": 24 },
"targets": [
{
"expr": "rate(process_cpu_seconds_total[1m])",
"legendFormat": "cpu"
}
]
}
]
}
Loading