Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IzelMQ

A high-performance, lock-free system metrics broker written in C, with a real-time React dashboard. IzelMQ implements a publish-subscribe messaging pipeline from scratch — no external libraries, no runtime dependencies — and exposes live CPU, memory, and network telemetry over a hand-rolled HTTP/1.1 server.

IzelMQ Dashboard


What it does

IzelMQ continuously samples Linux kernel metrics from /proc, publishes them through an internal message broker, and serves them as JSON over HTTP with sub-millisecond latency. A React frontend polls the endpoints every second and renders live sparklines, per-core CPU bars, and a 60-second rolling history chart.

/proc/stat          →  producer_cpu     ─┐
/proc/meminfo       →  producer_memory  ─┤→  broker  →  consumer  →  snapshot  →  HTTP API
/proc/net/dev       →  producer_network ─┘

Typical observed latencies on localhost:

avg_lat=14µs  |  p99=72µs  |  peak_lat=72µs  |  dropped=0

p99 (99th percentile latency) means 99% of all requests complete in under 72µs. The remaining 1% — the worst-case tail — never exceeds 72µs either. In production observability systems, p99 is the metric that matters: averages hide outliers, and outliers are what users feel.


Why IzelMQ?

Most developers reach for top, htop, or a Prometheus/Grafana stack when they want system metrics. IzelMQ is none of those things. It exists to answer a different question: what does it actually take to build a low-latency telemetry pipeline from first principles?

vs. reading /proc directly


You can cat /proc/stat in a shell script and pipe it somewhere. That works. But it gives you raw counters with no rates, no history, no concurrency model, and no way to serve the data to anything else without rebuilding everything from scratch. IzelMQ turns those counters into a structured, queryable stream — computing deltas for rates, maintaining a rolling history, and making the data instantly available over HTTP without polling the filesystem on every request.

vs. node_exporter + Prometheus


The standard production answer is Prometheus pulling from node_exporter every 15–60 seconds, stored in a time-series database, queried with PromQL, visualized in Grafana. That stack is excellent for infrastructure at scale. It is also: two separate binaries, a database process, a query language, a dashboarding service, and a scrape interval that makes 1-second resolution impractical.

IzelMQ serves fresh data in under 100µs with a single binary and zero configuration. The entire observability stack — collection, transport, storage (60-second ring buffer), and visualization — compiles to one executable and one npm run dev.

vs. writing it in Go or Python


Go and Python are reasonable choices for this kind of tool. They would also make it trivially easy. The point of IzelMQ is the constraint: no garbage collector, no runtime, no framework. Every allocation is explicit. Every synchronization primitive is chosen deliberately. The lock-free paths are lock-free because the author understood the memory model well enough to make them so, not because a library handled it.

This matters because the techniques here — CAS loops, atomic state machines, slab allocators, edge-triggered I/O — are exactly what you find inside the systems that Go and Python are built on.

vs. using a real message broker (RabbitMQ, Kafka, Redis)


Bringing in RabbitMQ or Kafka to shuttle metrics between three threads on a single machine is like renting a warehouse to store a shoebox. Those systems solve distributed messaging across networks and processes. IzelMQ solves in-process pub/sub with a latency budget measured in microseconds. The broker here is a few hundred lines of C with no network overhead, no serialization format, no broker process — just atomic operations on shared memory.

The actual difference, in numbers

Shell script node_exporter IzelMQ
Latency to serve data N/A ~1ms (HTTP) 14µs avg, 72µs p99
Dependencies bash Go runtime none
Scrape resolution manual 15s minimum 1s, configurable
Memory footprint N/A ~20MB < 2MB
Setup none install + configure Prometheus make
Allocator libc malloc Go GC lock-free slab
I/O model blocking goroutines epoll edge-triggered
Synchronization none Go channels CAS / atomic state machine

IzelMQ is not a replacement for Prometheus in production. It is proof that you can build the core of what Prometheus does — collect, buffer, serve — in under 2000 lines of C, with no dependencies, and have it respond faster than most production systems can even acknowledge a connection.


Architecture

Broker (srcs/core/)

The broker is a topic-based pub/sub system built entirely on lock-free primitives. Producers publish typed messages to named topics; a single consumer thread drains the queues and commits snapshots for the HTTP layer to read.

Key design decisions:

  • Lock-free message passing using stdatomic.h — no mutexes in the hot path
  • Custom slab allocator (ialloc) with separate small and big block pools, avoiding malloc overhead in the metrics pipeline
  • Triple-buffer snapshot with CAS-based slot selection, allowing the HTTP server to read the latest snapshot without blocking producers

HTTP Server (srcs/transport/)

A minimal, epoll-based HTTP/1.1 server purpose-built for low-latency metric serving. Each request is handled inline in the epoll loop — no thread pool, no dynamic dispatch beyond a path comparison.

  • Non-blocking sockets with EPOLLET (edge-triggered)
  • TCP_NODELAY on every client connection
  • SO_REUSEPORT on the listen socket
  • CORS headers for local dashboard access
  • Structured request parsing with a hand-written protocol layer

Metrics producers (srcs/metrics/)

Three POSIX threads, each sampling a different /proc file at 1-second intervals:

Producer Source Fields
producer_cpu /proc/stat overall %, per-core %, load averages
producer_memory /proc/meminfo used, available, cached, buffers, swap
producer_network /proc/net/dev per-interface rx/tx bytes, rates, errors, drops

Rate metrics (e.g. rx_rate_bps) are computed as deltas between consecutive reads, giving true bytes-per-second values rather than cumulative counters.

Dashboard (dashboard/)

A React 18 + Chart.js frontend with no state management library. All data flows through a single useMetrics hook that polls the three API endpoints concurrently with Promise.all. Key UI features:

  • Animated number transitions (800ms requestAnimationFrame interpolation)
  • Per-core bar chart with height proportional to utilization
  • 60-second rolling history with interactive crosshair tooltip
  • Graceful disconnection states — shows last known data when the broker is unreachable

API

The server exposes four endpoints on localhost:4444:

Endpoint Description
GET /metrics/cpu CPU utilization, load averages, per-core breakdown
GET /metrics/memory Memory breakdown in KB with swap stats
GET /metrics/network Per-interface rx/tx rates and cumulative counters
GET /health Readiness check — returns warming_up until all producers have data

All responses include Access-Control-Allow-Origin: * and Cache-Control: no-cache.

Example response from /metrics/cpu:

{
  "cpu_pct": 4.1,
  "cores": 20,
  "load_1": 1.00,
  "load_5": 1.23,
  "load_15": 0.90,
  "per_core": [6.0, 0.0, 8.0, 2.0, 8.1, 0.0, 6.1, 0.0, 8.0, 0.0, 11.1, 0.0, 6.1, 0.0, 4.1, 0.0, 6.9, 5.0, 3.1, 2.0]
}

Getting started

Prerequisites

  • Linux (reads from /proc — macOS not supported)
  • gcc, make, pthreads
  • Node.js ≥ 18, npm

Run in development

# Clone the repo
git clone git@github.com:iliassovic2003/Izel_MQ.git
cd Izel_MQ

# Install frontend dependencies, then launch broker + Vite in a tmux split
make

make (with no target) runs install then dev, which:

  1. Installs npm packages in dashboard/
  2. Opens a tmux session with the C broker on the left and vite dev on the right

Open http://localhost:5173 in your browser.

Build for production

make build          # compiles server + bundles dashboard to dashboard/dist/
make run            # starts the broker

Serve dashboard/dist/ with any static file server (nginx, caddy, etc.).

Make targets

Target Description
make Install dependencies + launch dev environment
make build Compile server + bundle dashboard
make build-server Compile C broker only
make build-dashboard Bundle React app only
make dev Launch broker + Vite in a tmux split
make run Run the compiled broker
make kill Kill the tmux session
make clean Remove object files
make fclean Full clean (binaries + node_modules + dist)
make re fclean then build
make asan Build with AddressSanitizer
make tsan Build with ThreadSanitizer

Debug mode

./srcs/izmq --DEBUG

Prints per-request latency and periodic stats:

[REQ ]  /metrics/cpu              lat=  24µs
[REQ ]  /metrics/memory           lat=   8µs
[REQ ]  /metrics/network          lat=   7µs
[STAT]  reqs=93  |  avg_lat=13µs  |  p99=72µs  |  peak_lat=72µs  |  dropped=0

Project structure

Izel_MQ/
├── dashboard
│   ├── eslint.config.js
│   ├── index.html
│   ├── package.json
│   ├── package-lock.json
│   ├── public
│   │   ├── favicon.svg
│   │   └── icons.svg
│   ├── README.md
│   ├── src
│   │   ├── App.css
│   │   ├── App.jsx
│   │   ├── assets
│   │   │   ├── hero.png
│   │   │   ├── react.svg
│   │   │   └── vite.svg
│   │   ├── index.css
│   │   └── main.jsx
│   └── vite.config.js
├── docs
│   ├── Changelogs.md
│   └── dashboard.png
├── Makefile
├── README.md
└── srcs
    ├── core
    │   ├── broker
    │   │   ├── broker.c
    │   │   └── broker.h
    │   ├── ialloc
    │   │   ├── big_pool.c
    │   │   ├── lock_free_malloc.c
    │   │   ├── lock_free_malloc.h
    │   │   └── small_pool.c
    │   ├── message
    │   │   ├── message.c
    │   │   └── message.h
    │   └── queue
    │       ├── queue.c
    │       └── queue.h
    ├── http
    │   ├── api.c
    │   ├── api.h
    │   ├── consumer.c
    │   └── consumer.h
    ├── Makefile
    ├── metrics
    │   ├── cpu.c
    │   ├── memory.c
    │   ├── metrics.h
    │   └── network.c
    ├── server.c
    └── transport
        ├── protocol.c
        ├── protocol.h
        ├── server.c
        ├── server.h
        └── snapshot.h

15 directories, 44 files


Internals worth reading

Triple-buffer snapshot (snapshot.h, consumer.c, api.c)

The snapshot uses a 3-slot buffer with a packed atomic state word encoding read_slot, write_slot, dirty_slot, and a dirty_flag bit. The consumer picks the slot that is neither read nor dirty, writes into it, then atomically swaps it to dirty with a CAS loop. The HTTP handler atomically claims the dirty slot as the new read slot. This gives wait-free reads and contention-free writes with no locks.

Lock-free allocator (srcs/core/ialloc/)

Two fixed-size pools — small blocks for control messages, big blocks for data payloads — managed with atomic bitmasks. iloc(size) routes to the appropriate pool and returns a block in O(1) without calling malloc. ifree marks the block available with a single atomic store.

epoll edge-triggered server (server.c)

The server uses EPOLLET (edge-triggered mode), which means it must drain the accept queue in a loop on each EPOLLIN event. Each client connection is non-blocking; reads that return EAGAIN are treated as complete rather than errors. Connections are closed after a single request — HTTP/1.0 semantics — keeping the event loop simple.


License

MIT License

Copyright (c) 2025 Katchuru0_0

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

A lightweight C message broker prototype with lock-free queueing, topic-based publish/consume flow, real-time Linux metrics collection (CPU/memory/network), and an epoll-driven HTTP API for exposing live system snapshots.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages