██████╗██████╗ █████╗ ███████╗██╗ ██╗
██╔════╝██╔══██╗██╔══██╗╚════██║╚██╗ ██╔╝
██║ ██████╔╝███████║ ██╔╝ ╚████╔╝
██║ ██╔══██╗██╔══██║ ██╔╝ ╚██╔╝
╚██████╗██║ ██║██║ ██║ ██║ ██║
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝
██████╗ ██████╗ ██╗███╗ ██╗████████╗███████╗██████╗
██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗
██████╔╝██████╔╝██║██╔██╗ ██║ ██║ █████╗ ██████╔╝
██╔═══╝ ██╔══██╗██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗
██║ ██║ ██║██║██║ ╚████║ ██║ ███████╗██║ ██║
╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
🖨️ A P I 🔥
"Because generating 2 Terabytes of fake logs shouldn't be boring."
Crazy Printer API is a high-performance, asynchronous mock log generator written in C++20. It floods your filesystem with gigabytes of hilariously fake log files — on purpose.
It was built as a stress-testing torture device for LogGrid — a distributed log processing system. If LogGrid can survive Crazy Printer, it can survive anything.
Sample output, for the uninitiated:
[2026-08-16 03:14:15] [WARN] Printer tray 2 is philosophically empty.
[2026-08-16 03:14:16] [ERROR] Ink cartridge has achieved enlightenment and refuses to print.
[2026-08-16 03:14:17] [DEMONIC_POSSESSION] Firmware corrupted by ancient Sumerian curse. Please consult an exorcist.
[2026-08-16 03:14:18] [INFO] Restarting printer... (attempt 666 of ∞)
Tested on RAM disk (
--tmpfs) to isolate CPU performance from storage limits. 100 files × 100,000 lines ≈ 953 MB per run.
Run it yourself:
# Start the server with RAM disk (no storage bottleneck)
docker run -d --rm \
--name crazy-printer \
-p 8080:8080 \
-e OUTPUT_BASE_DIR=/data/logs/ \
--tmpfs /data/logs \
ghcr.io/mikolajkos/crazy-printer-api:v1.0.0
# Run benchmark
python3 -m venv .venv
source .venv/bin/activate
pip install -r benchmark/requirements.txt
python3 benchmark/run_benchmark.py| Configuration | Throughput |
|---|---|
| 1P / 1C | 3,179 MB/s |
| 2P / 2C | 5,398 MB/s ⬅ peak |
| 4P / 4C | 5,247 MB/s |
| 8P / 2C | 4,817 MB/s |
| 8P / 8C | 4,337 MB/s |
Key observations:
- Peak throughput of 5.4 GB/s saturates virtually any consumer SSD or HDD — at this point the bottleneck is the storage device, not the generator
- Sweet spot is 2P / 2C — diminishing returns beyond that as CPU contention on string generation and RNG starts to dominate
- 8P / 8C drops vs 2P / 2C — too many threads competing for the same CPU cores
- Even the slowest config (1P / 1C at 3.2 GB/s) exceeds the sequential write speed of most NVMe drives
💡 Tuning tip: for HDD use
consumerThreads: 1to avoid head thrashing. For NVMe:2P / 2Cor4P / 4C.
Requires Docker and Docker Compose.
# Clone and start (Release build)
git clone https://github.com/MikolajKos/crazy-printer-api.git
cd crazy-printer-api
docker compose up -d --build
# Watch live logs
docker compose logs -f# Start a print job
curl -X POST http://localhost:8080/api/printer/start \
-H "Content-Type: application/json" \
-d '{"fileCount": 100, "linesPerFile": 10000, "outputDir": "job_1", "producerThreads": 4, "consumerThreads": 4}'
# Check job status
curl http://localhost:8080/api/printer/status/1Crazy Printer uses a Producer-Consumer pipeline with independent thread pools on both sides:
POST /api/printer/start
│
▼
GeneratorService::StartJob(config)
│
├─► [Producer Pool] → generates text batches → [ThreadSafeQueue 256MB]
│ │
└─► [Consumer Pool] ←────────────────────────── pops batches, writes to disk
(Exclusive File Ownership — zero I/O locking)
- Byte-based backpressure —
ThreadSafeQueuelimits total in-flight data (default: 256MB), not element count. No OOM surprises. - Dual condition variables —
m_cv_not_fullfor producers,m_cv_not_emptyfor consumers. No thundering herd. - Exclusive file ownership — each consumer thread owns its file exclusively. Zero file-level locking, zero disk thrashing.
- Configurable thread counts — tune
producerThreadsandconsumerThreadsindependently for your hardware (HDD vs NVMe). - Dependency Injection —
PrinterControllerdepends onIGeneratorService, not the concrete implementation. Testable by design. - Zero-alloc log generation —
LogGenerator::GenerateLineappends directly into a pre-reservedstd::stringbatch. No per-line allocations at ~40M lines. - Atomic work distribution — producers and consumers claim work via
fetch_addon shared atomics. No scheduler, no mutex on the hot path.
| Component | Technology |
|---|---|
| Language | C++20 |
| HTTP Server | yhirose/cpp-httplib (vendored) |
| JSON | nlohmann/json (vendored) |
| Logging | spdlog v1.15.3 — async, structured, color |
| Build | CMake 3.25+ |
| Threads | std::jthread, std::mutex, std::condition_variable, std::atomic |
| Container | Docker + Docker Compose |
Starts an async log generation job. Returns immediately with a job ID — the printer does not wait for you.
Request body:
📝 Note:
"outputDir"is a sub-directory within the base logs directory (/data/logsby default, set viaOUTPUT_BASE_DIRenv). Providing"outputDir": "job_1"saves files to/data/logs/job_1on the mounted Docker volume. Leading slashes are stripped automatically.
{
"fileCount": 100,
"linesPerFile": 10000,
"outputDir": "job_1",
"producerThreads": 4,
"consumerThreads": 4
}Response 202 Accepted:
{
"jobId": 1,
"status": "running"
}Returns the current status of a job.
Response 200 OK — job in progress:
{
"jobId": 1,
"status": "running",
"filesWritten": 42
}Response 200 OK — job completed:
{
"jobId": 1,
"status": "done",
"filesWritten": 100,
"metrics": {
"executionTimeSeconds": 12.345
}
}Response 404 Not Found:
{
"error": "Job not found. Did the printer eat it?"
}# Release
docker compose up -d --build
# Debug — full structured log output
BUILD_TYPE=Debug docker compose up -d --build
# Debug + AddressSanitizer
BUILD_TYPE=Debug SAN_TYPE=ASAN docker compose up -d --build
# Debug + ThreadSanitizer
BUILD_TYPE=Debug SAN_TYPE=TSAN docker compose up -d --buildcmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/CrazyPrinterRequires a C++20-capable compiler (GCC 12+, Clang 14+) and internet access (CMake fetches spdlog via FetchContent).
crazy-printer-api/
├── external/
│ ├── httplib.h # HTTP server (vendored)
│ └── json.hpp # nlohmann/json v3.11.3 (vendored)
├── include/
│ ├── GeneratorService.hpp # Interface + concrete impl + data structs (JobConfig, JobContext, Batch)
│ ├── LogGenerator.hpp # Log line generator — zero-alloc append, thread_local RNG
│ ├── LoggerSetup.hpp # spdlog async init, runtime level binding, ASCII welcome screen
│ ├── PrinterController.hpp # HTTP layer (forward decl only, no httplib include)
│ ├── ThreadPool.hpp # Reusable jthread pool
│ └── ThreadSafeQueue.hpp # Byte-limited producer-consumer queue (concept-constrained template)
└── src/
├── main.cpp # Entry point — logger init, server start on 0.0.0.0:8080
├── GeneratorService.cpp # Job lifecycle, producer & consumer tasks, I/O helpers
├── PrinterController.cpp # HTTP endpoints with structured request/response logging
└── ThreadPool.cpp # jthread pool impl
✅ Fully operational. The printer is on fire (intentionally).
- Project structure & CMake setup (Release/Debug, ASAN/TSAN sanitizers)
-
ThreadSafeQueue— byte-based backpressure, dual CV,markDone()wakes both sides -
ThreadPoolwithstd::jthread -
LogGenerator— zero-allocation line append,thread_localRNG & distributions, single timestamp per batch -
StartJob— producers & consumers wired up, output dir prep, atomic file & line ownership -
PrinterController—POST /startandGET /status/:idwith latency logging -
main.cpp— server starts on0.0.0.0:8080 - Data race on
statusfield fixed (m_mutexguards both read and write viaMarkAsFinished()) - Producer deadlock fixed (
markDone()now wakes blocked producers too) - Structured logging via
spdlog— async, color, configurable level per build type - Configurable Docker build types —
BUILD_TYPE=Debug|Release,SAN_TYPE=ASAN|TSAN -
filesWrittencounter inGET /statusresponse (bothrunninganddone) -
metrics.executionTimeSecondsinGET /statusresponse whendone
Low on magenta. Always.
