Skip to content

Developers

This page provides detailed information for developers working on the Prometheus Klipper Exporter.

Project Structure

txt
.
├── main.go                         # HTTP server, routing, CLI flags
├── collector/
│   ├── collector.go                # Prometheus Collector interface, shared utilities
│   ├── device_power.go            # /machine/device_power (power device status)
│   ├── directory_info.go           # /server/files/directory
│   ├── history.go                  # /server/history/totals
│   ├── job_queue.go                # /server/job_queue/status
│   ├── network_stats.go            # /machine/proc_stats (network interfaces)
│   ├── printer_object.go           # /printer/objects/query
│   ├── process_stats.go            # /machine/proc_stats (CPU/memory)
│   ├── spoolman.go                # POST /server/spoolman/proxy → GET /v1/spool (Spoolman filament spools)
│   ├── system_info.go              # /machine/system_info (CPU count and service states)
│   └── mmu.go                      # /printer/objects/query (MMU objects)
├── test/
│   └── README.md                   # Quick start for test env
│   ├── docker-compose.yml          # Local test environment
│   ├── printer_data/               # Virtual Klipper printer config
│   ├── prometheus.yml              # Prometheus scrape config for local dev
├── docs/                           # VitePress documentation site
├── example/                        # Docker deployment example
└── Makefile                        # Build, fmt, test, release targets

Key Architectural Patterns

  • Multi-Target Exporter: A single exporter instance scrapes multiple Klipper hosts using the /probe?target=<host> endpoint
  • Collector Interface: Each module implements prometheus.Collector (Describe() + Collect())
  • Module Gating: Features are enabled via slices.Contains(c.modules, "name") guards in Collect()
  • API Key Priority: Header > CLI flag (-moonraker.apikey) > Environment variable (MOONRAKER_APIKEY)

Building and Testing

Prerequisites

  • Go 1.25+
  • Make

Commands

CommandDescription
make buildBuild the exporter binary
make runRun the exporter locally
make fmtFormat Go code
make testRun all tests
make releaseCross-compile for all platforms

Cross-Compilation

The make release target builds for Raspberry Pi (ARM), Linux (AMD64), macOS (AMD64/ARM64), and Windows (AMD64) using GOOS/GOARCH environment variables.

Running Tests

sh
make test

Test files live in tests/ and follow standard Go testing patterns.

Virtual Printer Test Environment

The test/ directory contains a Docker Compose-based test environment. It supports two virtual printer images that you can choose between in test/docker-compose.yml:

OptionImageDescription
A (default)ghcr.io/pedrolamas/docker-klipper-simulavrFull MCU simulation via simulavr. Supports SIMULAVR_PACING_RATE to prevent "Timer too close" errors. Config path: /printer/printer_data
Bghcr.io/mainsail-crew/virtual-klipper-printerLighter API-level simulation. No MCU simulation. Config path: /home/printer/printer_data

To switch between them, uncomment your preferred option and comment out the other in test/docker-compose.yml.

Starting the Environment

sh
docker compose up -d --build  # from test/ directory

Services

ServiceURL
Klipper/Moonrakerhttp://localhost:7125
Mainsailhttp://localhost:8080
Prometheushttp://localhost:9090
Grafanahttp://localhost:3000

Tuning SIMULAVR_PACING_RATE

When using Option A (docker-klipper-simulavr), the SIMULAVR_PACING_RATE environment variable controls how fast the simulated MCU runs. If you see MCU 'mcu' shutdown: Timer too close in the container logs:

  1. Reduce the value in test/docker-compose.yml (e.g. 0.10.050.01)
  2. Restart the container: docker compose restart virtual-klipper
  3. A lower value gives the host CPU more headroom by slowing the simulation
  4. Values above 0.2 are not recommended — they can starve the simulator and make the timer issue worse
  5. The default of 0.1 works on most modern hardware; adjust based on your system's CPU load

Virtual Printer Configuration

The virtual printer config lives in test/printer_data/config/. The printer.cfg includes addon configs from test/printer_data/config/addons/.

Pin Assignments

The virtual MCU is an AVR atmega644p with the following available pins:

  • PORTA: PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7
  • PORTB: PB0, PB1, PB2, PB3, PB4, PB5, PB6, PB7
  • PORTC: PC0, PC1, PC2, PC3, PC4, PC5, PC6, PC7
  • PORTD: PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7

Addon Configs

Addon FileSections DefinedPins Used
basic_cartesian_kinematics.cfgstepper_x, stepper_y, stepper_z, extruderstep/dir pins
basic_macros.cfgG-code macros
single_extruder.cfgextruderheater, sensor pins
heater_bed.cfgheater_bedheater, sensor pins
temp_sensors.cfgtemperature_sensor, temperature_fanPA1, PA4, PD2, PD3
miscellaneous.cfgfan, heater_fan, controller_fan, filament_motion_sensor, output_pinPB4, PB5, PB6, PC0, PC1
custom_features.cfgtemperature_probe, heater_genericPA0, PA2, PA3
input_shaper.cfginput_shaper— (no pins required)
timelapse.cfgMoonraker timelapse

Prometheus Scrape Config

The test environment's prometheus.yml scrapes these modules:

yaml
params:
  modules:
    - process_stats
    - network_stats
    - system_info
    - job_queue
    - directory_info
    - printer_objects
    - history
    - device_power

All metrics from these modules are available at http://localhost:9101/probe?target=virtual-klipper:7125.

Grafana Dashboards

The test environment includes auto-provisioned example Grafana dashboards, loaded from test/grafana/provisioning/dashboards/. They are available at http://localhost:3000 under the Klipper folder:

DashboardFocusKey Metrics
Klipper SystemSystem healthCPU, memory, uptime, network, disk, job queue, Moonraker process, service states
Klipper TemperaturesTemperature monitoringExtruder, bed, sensors, temperature fans, probes, generic heaters
Klipper Print StatusPrint progress & historyG-code progress, file position, filament used, timeline, history stats
Klipper HardwareMCU, fans, pins, TMC, Device PowerMCU task/RTT/I/O, fan speeds/RPMs, output pins, filament sensors, TMC drivers, power device status
Klipper MMUMulti-Material UnitGate/tool state, encoder data, filament status, toolchange tracking

The dashboards use job and instance template variables. For the test environment select job=klipper and instance=virtual-klipper:7125.

Each dashboard can also be imported manually into another Grafana instance from the JSON files in test/grafana/provisioning/dashboards/. The JSON uses a DS_PROMETHEUS datasource input variable — you will be prompted to map it during import.

Adding New Config Sections

When adding a new Klipper config section to exercise exporter code:

  1. Check pin conflicts: Ensure the pin isn't already used by another addon. Available pins are listed above.
  2. Add or modify an addon file: Create a new .cfg in addons/ or modify an existing one.
  3. Include it in printer.cfg: Add an [include addons/your_file.cfg] line.
  4. Restart the container: The virtual printer will reload config on restart.

Known Issues

MCU Timer too close shutdown in simulavr

When using Option A (docker-klipper-simulavr), the simulavr MCU emulator can trigger MCU shutdown: Timer too close if the host is under load. This is mitigated by setting SIMULAVR_PACING_RATE to a lower value — see the Tuning section above.

When using Option B (virtual-klipper-printer), the MCU shutdown is consistently triggered ~7 seconds after Klippy reaches the ready state by Moonraker's objects/query request. The old workaround was to restart the full stack repeatedly until a stable cycle occurred.

Symptoms (either option):

  • Klippy cycles through ready → shutdown → auto-restart every ~30-60 seconds
  • Moonraker reports klippy_state=shutdown even while Klippy produces Stats
  • The UDS socket (klippy.sock) exists but does not respond to API requests
  • Metrics continue to flow through the exporter because Moonraker caches last known values and serves them regardless of Klippy's state

Impact on development:

  • Metrics are served and scraped correctly during both ready and shutdown states, so the exporter and dashboards remain functional
  • Printer-object metrics (temperatures, fans, sensors) reflect the last cached values, not live readings, during a shutdown cycle
  • Switching to Option A with a properly tuned SIMULAVR_PACING_RATE can keep Klippy in ready state indefinitely

Collector Implementation Guide

Adding a New Module

  1. Create a new file in collector/ with:

    • A collect*() method that fetches data and emits metrics
    • Helper types for JSON response unmarshalling
    • A fetchMoonraker*() function for the API call
  2. Register the module in collector.go's Collect() method:

    go
    if slices.Contains(c.modules, "your_module") {
        c.collectYourModule(ch, target, apikey)
    }
  3. If the module should be enabled by default, add it to the default modules list in main.go.

Metric Naming Conventions

  • Prefix: klipper_*
  • Case: snake_case
  • Suffix conventions:
    • _info — labeled state gauges for enumerated values (e.g. klipper_print_state_info{state="printing"})
    • _total — counters
    • _celsius, _mm, _seconds — unit suffixes
  • Do not expose arbitrary strings (error messages, filenames) as labels

Shared Utilities (in collector.go)

FunctionPurpose
GetValidLabelName()Converts hyphens to underscores, strips invalid characters
boolToFloat64()Converts bool to 0.0/1.0 for Prometheus
emitStateInfoMetric()Emits a _info metric for string states with known values

Error Handling

Log the error with log.Error(err) and return early. An error in one module should not prevent other modules from collecting.

Documentation Site

The documentation site uses VitePress and is deployed to GitHub Pages from the main branch.

Previewing Locally

sh
cd docs
npm install
npm run dev     # hot-reload dev server
npm run build   # production build
npm run preview # preview production build

Adding a Docs Page

  1. Create the .md file in the appropriate docs/ subdirectory
  2. Register it in docs/.vitepress/config.js in the relevant sidebar section
  3. Verify the build with npm run build

Released under the MIT License.