libvirt bulk domain stats -> ovn_agent.py -> Redpanda -> ClickHouse
| | |
+-> OVN NBDB | +-> 5-min / hourly aggregates
+-> three Kafka-compatible topics
The agent computes CPU utilization, network deltas, and disk I/O deltas
locally, then publishes ready-to-aggregate values to Redpanda. ClickHouse
stores raw samples (6h TTL), 5-minute aggregates (7d TTL), and hourly
aggregates (3 months TTL). No window functions or delta computation in
ClickHouse — MVs are simple sum()/avg() GROUP BY.
Classical OpenStack metrics stack is Ceilometer, Gnocchi (+ Cloudkitty for rating optionally). It was hard for me to find out what are pros and cons of this stack. So I decided to implement my own metrics stack for OpenStack.
All delta/utilization computation happens in the agent, not ClickHouse:
- CPU:
cpu_pct— computed from consecutivecpu_time_nsreadings - Network:
rx_bytes,tx_bytes— per-interval deltas (not cumulative) - Disk I/O:
read_bytes,write_bytes,read_requests,write_requests— per-interval deltas - Gauges:
memory_actual_bytes,memory_rss_bytes,memory_usable_bytes,capacity_bytes,allocation_bytes,physical_bytes— sent as-is
The first sample after agent start is skipped (no previous value to diff). No is_baseline flag — the agent simply doesn't emit until it has two consecutive readings.
| Tier | Granularity | TTL | Source | Dashboard use |
|---|---|---|---|---|
| Raw samples | ~1 min | 6 hours | Kafka engine MVs | 6h view |
| 5-min aggregates | 5 min | 7 days | Refreshable MV every 5 min | 12h/24h view |
| Hourly aggregates | 1 hour | 3 months | Refreshable MV every 10 min | 7d view, billing |
All tiers aggregate directly from raw samples (parallel, not chained).
ovn_agent.py is the production collector. One poll uses libvirt
getAllDomainStats() to retrieve CPU, balloon-memory, block-device, and vNIC
statistics for all running domains. It maps each interface to a Neutron port
and network:
- Read
virtualport/parameters@interfaceidfrom the libvirt domain XML. - If absent, match
tap<port-UUID-prefix>against NBDBLogical_Switch_Port.nameandexternal_ids["neutron:device_id"]. - Read
external_ids["neutron:network_name"]to derivenetwork_uuid.
The agent talks directly to OVN Northbound through ovsdbapp IDL. It does not
invoke virsh, ovs-vsctl, or ovn-nbctl. Only active Nova compute ports
(neutron:device_owner=compute:nova) are collected.
docker build -t ovn-traffic-agent:latest .docker compose -f deploy/docker-compose.agent.yml up -dConfigure via .env (see deploy/.env.example):
KAFKA_BOOTSTRAP_SERVERS=metrics.example.com:9092
KAFKA_SASL_USERNAME=agent
KAFKA_SASL_PASSWORD=agentpass
LIBVIRT_URI=qemu:///system
LIBVIRT_USERNAME=nova
LIBVIRT_PASSWORD=<password>
OVN_NB_DB=tcp:1.1.0.1:6641
REGION_NAME=RegionOne
POLL_INTERVAL=60Or run directly:
python3 ovn_agent.py \
--libvirt-uri qemu+tcp://<libvirt-host>/system \
--libvirt-username '<username>' \
--libvirt-password '<password>' \
--ovn-nb-db tcp:<nbdb-host>:6641 \
--bootstrap-servers <redpanda-host>:9092 \
--sasl-username agent \
--sasl-password agentpass \
--region-name '<openstack-region>' \
--publish-kafkaOn restart, the agent has no previous counter values. The first poll establishes baselines for CPU, network, and disk — no samples are emitted. The second poll computes deltas normally. No data loss beyond one skipped interval per metric.
docker compose -f deploy/docker-compose.server.yml up -dRuns Redpanda (SASL/SCRAM-SHA-256), Redpanda Console, and ClickHouse.
Credentials via env vars (see deploy/.env.example).
The init container creates an agent SASL user with topic/group ACLs and
pre-creates the three topics with 10-min retention and 16MB segments.
Lightweight React + ApexCharts dashboard querying ClickHouse HTTP API.
cd dashboard && npm install && npm run devThree pages:
- Instance — per-instance CPU, RAM, Disk I/O, Network charts with 6h/12h/24h/7d range toggle
- Per Node — aggregate disk I/O and network traffic for all instances on a host
- Top Usage — top 10 instances by CPU, RAM, Network, Disk (avg over 24h from hourly tables)
The Vite dev server proxies /ch to ClickHouse localhost:8123.
agent.py generates realistic metrics for development. It sends the same
delta-based payload format as ovn_agent.py.
docker compose -f deploy/docker-compose.server.yml up -d
pip install confluent-kafka
python3 agent.py --instances 50000 --sasl-username agent --sasl-password agentpass| Table | Role | TTL |
|---|---|---|
port_stats_queue |
Kafka source for network deltas | — |
port_samples |
raw per-interval RX/TX deltas | 6 hours |
port_volume_5min |
5-min aggregated network volume | 7 days |
port_volume_hourly |
hourly aggregated network volume | 3 months |
port_usage |
period-to-date traffic view | — |
instance_stats_queue |
Kafka source for CPU/RAM | — |
instance_samples |
raw cpu_pct and RAM gauges | 6 hours |
instance_metrics_5min |
5-min CPU/RAM aggregates | 7 days |
instance_metrics_hourly |
hourly CPU/RAM aggregates | 3 months |
disk_stats_queue |
Kafka source for disk deltas | — |
disk_samples |
raw disk I/O deltas and size gauges | 6 hours |
disk_metrics_5min |
5-min disk aggregates | 7 days |
disk_metrics_hourly |
hourly disk aggregates | 3 months |
| Tier | Size |
|---|---|
| Raw samples (6h) | ~620 MB |
| 5-min aggregates (7d) | ~7.2 GB |
| Hourly aggregates (3mo) | ~8.6 GB |
| Total | ~16.4 GB |
- Redpanda has two Kafka listeners:
localhost:9092(external),redpanda:19092(internal compose network). - SASL/SCRAM-SHA-256 is enabled by default. The
adminsuperuser is bootstrapped viaRP_BOOTSTRAP_USER. Theagentuser is created by the init container. --region-nameis part of the ClickHouse aggregation key, allowing a shared deployment to report usage per OpenStack region.- For SASL-protected libvirt, use
--libvirt-username/--libvirt-passwordor--libvirt-auth-file(reads Nova'scredentials-defaultsection). TheLIBVIRT_PASSWORDenv var avoids process-list exposure.