一键导入
add-new-metrics
Step-by-step guide for adding new Prometheus metrics or entire metric collection modules to the klipper exporter.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Step-by-step guide for adding new Prometheus metrics or entire metric collection modules to the klipper exporter.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | add-new-metrics |
| description | Step-by-step guide for adding new Prometheus metrics or entire metric collection modules to the klipper exporter. |
This skill covers every file that must be created or modified when adding new metrics or a new collection module to the Prometheus Klipper Exporter.
Always run verification after making changes:
make build && make test && (cd docs && npm run build)
collector/<name>.go)Pattern for a simple module:
package collector
import (
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)
type MoonrakerYourModuleResponse struct {
Result struct {
SomeField string `json:"some_field"`
} `json:"result"`
}
func (c Collector) collectYourModule(ch chan<- prometheus.Metric) {
log.Infof("Collecting your_module for %s", c.target)
var result MoonrakerYourModuleResponse
if err := c.fetchFromMoonraker("/api/path", &result); err != nil {
log.Error(err)
return
}
// Unlabeled scalar gauge
c.emitGauge(ch, "klipper_your_metric", "Description.", float64Value)
// Unlabeled scalar counter
c.emitCounter(ch, "klipper_your_counter_total", "Description.", float64Value)
// Info-style gauge (value=1, state in label) — only emits if non-empty
emitStateInfoMetric(ch, "klipper_your_state_info", "Description.", "state", result.Result.SomeField)
// Labeled metric with dynamic instances
labels := []string{"sensor"}
desc := prometheus.NewDesc("klipper_your_labeled_metric", "Description", labels, nil)
for key, value := range someMap {
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, value, GetValidLabelName(key))
}
// Bool → 0/1
c.emitGauge(ch, "klipper_your_bool", "Description.", boolToFloat64(someBool))
}
Key patterns:
Moonraker<ModuleName>Response with anonymous Result struct{}printer_object.go uses custom UnmarshalJSON and mapstructure for dynamic keysmmu.go uses MMUResponse / MMUStatus namingfetch<MoonrakerObject>() helper methodsIf adding to a file that already exists (e.g. process_stats.go, printer_object.go):
collect*() methodCollect()File: collector/collector.go
Add a new slices.Contains guard in the Collect() method:
// Your Module
if slices.Contains(c.modules, "your_module") {
c.collectYourModule(ch)
}
process_stats/network_stats share /machine/proc_stats), gate them togetherlog.Errorf warningFile: main.go
If the module should be enabled by default (recommended for lightweight modules that provide universally useful metrics), add it to the default list:
modules := []string{"server_info", "process_stats", "job_queue", "system_info"}
Modules that are opt-in (heavy, niche, or have side effects): leave out of the default list.
File: docs/metrics/<name>.md
Template:
# Your Module Name
**Module:** `<module_name>` (default|optional)
**API Endpoint:** [`/api/path`](https://moonraker.readthedocs.io/en/latest/web_api/#anchor)
Brief description.
## Metrics
| Metric | Type | Description |
|--------|------|-------------|
| `klipper_your_metric` | Gauge | Description |
| `klipper_your_state_info` | Gauge=1 | Description with `label` label |
## Example PromQL
```promql
# Comment
klipper_your_metric
Type conventions:
Gauge — unlabeled scalar gaugeCounter — unlabeled scalar counterGauge=1 — info-style gauge with label (emitted via emitStateInfoMetric)File: docs/.vitepress/config.js
Add a new sidebar entry in the alphabetical position:
{ text: 'Your Module', link: '/metrics/your-module' },
File: docs/metrics/index.md
### your_module subsection with a compact metric table and [Full reference →](./your-module) linkFiles: test/grafana/provisioning/dashboards/*.json
Stat panel (numeric value):
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"mappings": [
{
"options": { "0": { "text": "Disconnected" }, "1": { "text": "Connected" } },
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "red", "value": null },
{ "color": "green", "value": 1 }
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "none",
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"textMode": "auto"
},
"targets": [{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"expr": "klipper_your_metric{job=\"$job\", instance=\"$instance\"}",
"instant": true,
"legendFormat": "__auto"
}],
"title": "Your Title",
"type": "stat"
}
Stat panel (info label display — Gauge=1 with label):
{
"options": {
"colorMode": "value",
"graphMode": "none",
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"textMode": "name"
},
"targets": [{
"expr": "klipper_your_state_info{job=\"$job\", instance=\"$instance\"}",
"instant": true,
"legendFormat": "{{state}}"
}]
}
Stat panel (count of labeled instances):
{
"targets": [{
"expr": "count(klipper_component_info{job=\"$job\", instance=\"$instance\"})",
"instant": true
}]
}
Timeseries panel:
Use "type": "timeseries" and include custom field config with axis/line settings.
Every dashboard should define job and instance template variables:
"templating": {
"list": [
{
"name": "job",
"query": { "query": "label_values(job)", "refId": "StandardVariableQuery" },
"type": "query",
"hide": 0
},
{
"name": "instance",
"query": { "query": "label_values(up{job=\"$job\"}, instance)", "refId": "StandardVariableQuery" },
"type": "query",
"hide": 0
}
]
}
Use up{job="$job"} for the instance query so it works even before module-specific
metrics exist.
test/grafana/provisioning/dashboards/test/grafana/provisioning/dashboards/klipper.yml auto-discovers all .json files in the same directory — no registration neededdocs/developers/index.md if adding a new dashboardtest/README.md if one existsFile: tests/<module>_test.go
package test
import (
"encoding/json"
"net/http/httptest"
"testing"
"github.com/prometheus/client_golang/prometheus"
"github.com/scross01/prometheus-klipper-exporter/collector"
)
func TestYourModuleMetrics(t *testing.T) {
// Load fixture or create response dynamically
fixture := `{ "result": { ... } }`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fixture))
}))
defer server.Close()
c := collector.New(r.Context(), server.Listener.Addr().String(), []string{"your_module"}, "")
ch := make(chan prometheus.Metric, 100)
go func() {
c.Collect(ch)
close(ch)
}()
var collected []string
for m := range ch {
collected = append(collected, m.Desc().String())
}
// Check expected metric names are present
// Use t.Run subtests for clarity
}
tests/<name>_response.jsonos.ReadFile + json.Unmarshaljson.Marshal in the testmake test # runs go test ./tests/...
Files:
example/prometheus.yml — production exampletest/prometheus.yml — dev/test environmentIf adding a new module, add it to the params.modules list in both files if it
should be scraped by default in those environments:
params:
modules:
- your_module
- process_stats
- ...
File: test/printer_data/config/addons/
If the module exercises a Klipper printer feature that isn't covered by existing addon configs:
docs/developers/index.md).cfg file in test/printer_data/config/addons/[include addons/your_file.cfg] to test/printer_data/config/printer.cfgdocs/developers/index.mdtest/README.mdFile: CHANGELOG.md
Add entries under the current vX.Y.Z heading using present tense bullet points:
- Add `klipper_your_metric` and `klipper_your_other_metric` metrics to `your_module` module
- Add `your_module` module with Apollo integration
- Add corresponding panels to Grafana dashboards (dashboard-name)
- Add support for `<feature>` with `<addon.cfg>` config section in virtual test environment
Use this checklist to ensure nothing is missed:
slices.Contains guard added in collector.go's Collect()main.go if appropriatedocs/metrics/<name>.md created with metric table and PromQL examplesdocs/.vitepress/config.jsdocs/metrics/index.md — overview table, default sentence, module subsectiontest/grafana/provisioning/dashboards/docs/developers/index.mdtests/<module>_test.go with fixture data and metric verificationexample/prometheus.yml and/or test/prometheus.yml updatedmake fmt && make build && make test && (cd docs && npm run build)