Skip to content

promdb2pcp: new tool to import Prometheus node_exporter metrics - #2623

Open
Aniruddh9 wants to merge 7 commits into
performancecopilot:mainfrom
Aniruddh9:promdb2pcp
Open

promdb2pcp: new tool to import Prometheus node_exporter metrics#2623
Aniruddh9 wants to merge 7 commits into
performancecopilot:mainfrom
Aniruddh9:promdb2pcp

Conversation

@Aniruddh9

Copy link
Copy Markdown

Add promdb2pcp, a Python import tool that converts Prometheus node_exporter metrics (stored as JSON from the query_range API) into PCP archives for analysis with pmrep, pcp2csv, pmchart, etc.

Supported metric groups: CPU (per-cpu/per-mode), memory, disk I/O, network interfaces, load averages, scheduler counters, vmstat counters, and PSI pressure stall information.

Input is a directory of JSON files produced by the Prometheus HTTP API /api/v1/query_range endpoint. An optional metadata.json file provides hostname and timezone for the archive header.

Pull Request Description

Related Issues :

New feature — no existing issue.

Fixes #

Checklist

Description :
Adds promdb2pcp, a new Python import tool that converts Prometheus node_exporter metrics into PCP archives.

Background: When analyzing OpenShift/Kubernetes node performance, Prometheus node_exporter metrics are often the primary data source. Currently there is no way to import this data into PCP for analysis with tools like pmrep, pcp2csv, and pmchart. This tool bridges that gap.

What it does: Reads a directory of JSON files produced by the Prometheus HTTP API /api/v1/query_range endpoint and creates a PCP archive with proper metric metadata, instance domains, and help text.

Supported metric groups: CPU (per-cpu/per-mode), memory, disk I/O, network interfaces, load averages, scheduler counters, vmstat counters, and PSI pressure stall information.

Conventions: Follows the same patterns as guidellm2pcp and vllmbench2pcp — #!/usr/bin/pmpython shebang, GPL header, pmiID/pmiInDom for proper PMIDs, help text via pmiPutText, domain 510.

Commits :
Single commit: promdb2pcp: new tool to import Prometheus node_exporter metrics

Files added/modified:

src/promdb2pcp/promdb2pcp.py — the import tool

src/promdb2pcp/GNUmakefile — build integration

src/promdb2pcp/promdb2pcp.1 — man page

src/GNUmakefile — register promdb2pcp in the build

Documentation updated

Man page promdb2pcp.1 included with synopsis, description, options, examples, and SEE ALSO references.
Tests added/updated

Manually tested with real Prometheus data (80 timestamps, 51 metrics, 8 CPUs, 2 disk devices, 3 network interfaces).

Add promdb2pcp, a Python import tool that converts Prometheus
node_exporter metrics (stored as JSON from the query_range API)
into PCP archives for analysis with pmrep, pcp2csv, pmchart, etc.

Supported metric groups: CPU (per-cpu/per-mode), memory, disk I/O,
network interfaces, load averages, scheduler counters, vmstat
counters, and PSI pressure stall information.

Input is a directory of JSON files produced by the Prometheus HTTP
API /api/v1/query_range endpoint.  An optional metadata.json file
provides hostname and timezone for the archive header.
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 768bc4c2-3fe2-46d8-ad90-813cddd34c2b

📥 Commits

Reviewing files that changed from the base of the PR and between a5a3eca and 3416d2f.

⛔ Files ignored due to path filters (1)
  • qa/1699.out is excluded by !**/*.out
📒 Files selected for processing (2)
  • qa/1687
  • qa/1699
🚧 Files skipped from review as they are similar to previous changes (2)
  • qa/1687
  • qa/1699

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features
    • Added promdb2pcp to convert Prometheus query_range JSON files into PCP archive files, including CPU, disk, and network device metric support and optional archive metadata.
  • Documentation
    • Added a new manual page covering the tool’s purpose, supported metric groups, command-line options, and an example workflow.
  • Tests
    • Added QA coverage for basic conversion and edge cases with malformed input and invalid metadata.
  • Chores
    • Updated the build to include the new tool for default builds, installation, and linting.

Walkthrough

A new promdb2pcp tool converts Prometheus query_range JSON files into PCP archives using pmiLogImport. The change adds metric registration, CLI and build integration, documentation, JSON fixtures, and QA tests for normal and malformed inputs.

Changes

promdb2pcp tool

Layer / File(s) Summary
Build, install, and command documentation
src/GNUmakefile, src/promdb2pcp/GNUmakefile, src/promdb2pcp/promdb2pcp.1
The tool is added to top-level build flows, with conditional Python installation and lint targets. Its man page documents metrics, options, examples, and references.
Metric mappings and JSON preprocessing
src/promdb2pcp/promdb2pcp.py
Metric mapping tables, CPU mode filtering, JSON loading, instance discovery, and indexed series preparation are added.
PCP metric registration
src/promdb2pcp/promdb2pcp.py
Simple, CPU-instanced, and device-instanced metrics are registered with PCP types, units, instance domains, scaling, and timestamp payloads.
Archive orchestration and CLI
src/promdb2pcp/promdb2pcp.py
convert() applies optional metadata, aggregates timestamps, writes typed values, and commits archive samples. The CLI validates the input directory and selects the output archive path.
Fixtures and conversion QA
qa/1687, qa/1699, qa/group, qa/promdb2pcp/*
Normal and malformed Prometheus datasets are added. QA scripts verify archive structure, metrics, instances, values, metadata, malformed data handling, and invalid or empty directories.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant convert
  participant JSONFiles as Prometheus JSON files
  participant PCP as pmiLogImport
  CLI->>convert: datadir and options
  convert->>JSONFiles: load metric data and metadata
  JSONFiles-->>convert: series, labels, timestamps
  convert->>PCP: register metrics and instances
  loop sorted timestamps
    convert->>PCP: put values and write samples
  end
  PCP-->>CLI: PCP archive
Loading

Possibly related PRs

Poem

A rabbit hops through metrics galore,
Prometheus JSON becomes PCP lore.
CPU, disks, and networks align,
Timestamps march in a sorted line.
🐇 Archives bloom with every sign!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding promdb2pcp to import Prometheus node_exporter metrics.
Description check ✅ Passed The description directly matches the changeset by describing the new importer, supported metrics, build integration, man page, and QA work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/promdb2pcp/promdb2pcp.py`:
- Around line 307-313: The code at line 312 silently selects the first matching
time series with `matching[0]` without validating that exactly one series
matches the metric name. This causes multiple time series with different labels
(such as different instances or jobs) to be silently discarded. Add validation
after the `if not matching: continue` check to ensure exactly one series
matches, or add explicit filtering by label before accessing the matching list.
If multiple matches are found, either raise an error to alert the user about the
ambiguity, skip the metric, or implement label-based filtering to select the
correct series.
- Around line 316-322: The bare except blocks that catch pmi.pmiErr exceptions
and use pass are suppressing critical PCP API failures without any logging or
tracking, allowing partial archives to be created while still exiting
successfully. At each location where pmi.pmiErr is caught (in the
pmiAddMetric/pmiPutText block around lines 316-322, the instance registration
block around lines 393-399, the value write block around lines 451-457, and the
text registration block around lines 560-561), replace the empty pass statement
with proper error handling that logs the failure using the existing log facility
and tracks that a failure occurred. After processing all metrics, check if any
failures were recorded and return a non-zero exit code to signal that the import
failed, ensuring bad imports are visible and actionable rather than silently
producing partial archives.
- Around line 490-499: The json.load(f) call at line 492 can raise a
JSONDecodeError if metadata.json is malformed, causing the entire conversion to
crash even though metadata is optional. Wrap the file open and json.load
operations in a try-except block that catches JSON decode errors, and when
parsing fails, skip the metadata configuration (hostname and timezone setup) and
allow the conversion to continue with default values instead of propagating the
exception.
- Around line 259-267: The `load_json` function lacks exception handling around
the `json.load()` call and file operations, which means malformed JSON or I/O
errors will cause the entire import to fail. Wrap the `with open(filepath)`
block and `json.load(f)` call in a try-except block to catch JSONDecodeError and
other I/O exceptions. When an exception occurs, return None (or log a warning if
desired) so that the function degrades gracefully and allows other valid metric
groups to continue being processed instead of aborting the entire import.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: f6119efb-4df7-436e-8d7a-6ee36bd37f10

📥 Commits

Reviewing files that changed from the base of the PR and between 58eecb9 and 6198b81.

📒 Files selected for processing (4)
  • src/GNUmakefile
  • src/promdb2pcp/GNUmakefile
  • src/promdb2pcp/promdb2pcp.1
  • src/promdb2pcp/promdb2pcp.py

Comment thread src/promdb2pcp/promdb2pcp.py
Comment thread src/promdb2pcp/promdb2pcp.py Outdated
Comment thread src/promdb2pcp/promdb2pcp.py Outdated
Comment thread src/promdb2pcp/promdb2pcp.py
Aniruddh9 and others added 2 commits June 15, 2026 13:50
Harden promdb2pcp against malformed input files and registration failures:
- load_json(): catch OSError and JSONDecodeError instead of crashing
- register_simple_metrics(): warn on multiple matching series and report
  pmiErr details instead of silently swallowing them
- convert(): handle metadata.json parse failures gracefully with a warning
if verbose:
print('%s (%d samples)' % (pcp_name, len(values)))

cluster += 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In PCP, when a new cluster is created the item ID number should reset to 0. It looks like when you create a new cluster the item ID keeps incrementing without being reset.

Comment thread src/promdb2pcp/promdb2pcp.py Outdated
(prom_name, pcp_name, sem, units_args,
pcp_type, divisor, helptext) = entry

matching = [r for r in results

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this line above the entries loop and create a hash map for the names?
Then inside the loop we can do a quick O(1) lookup instead of iterating through the results variable each time?

Comment thread src/promdb2pcp/promdb2pcp.py Outdated
item += 1
values = {}
for ts, val in r.get('values', []):
fts = float(ts)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add try/except clause to catch malformed data resulting in TypeError or ValueError and skip it smoothly

Comment thread src/promdb2pcp/promdb2pcp.py Outdated
for mode in CPU_MODES:
pcp_name = 'kernel.percpu.cpu.' + mode
units = pmapi.pmUnits(0, 1, 0, 0, PM_TIME_MSEC, 0)
indom = log.pmiInDom(DOMAIN, serial)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These metrics should share the same instance domain. So we should move the indom line outside of the loop and the serial should be constant for these metrics.

Comment thread src/promdb2pcp/promdb2pcp.py Outdated

for cpu_num in cpu_numbers:
try:
log.pmiAddInstance(indom, 'cpu' + cpu_num, int(cpu_num))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on some systems the 'cpu' label could have a value of 'cpu-0' instead of just '0' in this case the int(cpu_num) would fail

@lmchilton

lmchilton commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

@Aniruddh9 Hi Ani! Thank you so much for your contribution :) This looks like a great addition to PCP

I left some comments in line on the files.

Along with those we should have some QA to test the functionality of the tool added to our testsuite under pcp/qa. You can use the ./new script to generate a new test number along with a skeleton qa test script. Or you can search around and see if there is an existing QA test available where it would make sense to add to it.

Let me know if you have any questions

Lauren

@Aniruddh9

Copy link
Copy Markdown
Author

hi @lmchilton , Thank you for the reviews and I am glad that you feel this is a great addition to PCP. I have been busy lately and could not look at the testing part. I will do it this week and update the PR.

Aniruddh9 and others added 3 commits July 22, 2026 19:48
- Reset item ID to 0 when starting a new cluster so each cluster's
  items are numbered from zero per PCP convention
- Build a hash map of metric names before the entries loop for O(1)
  lookup instead of iterating through results for each metric
- Wrap value parsing in try/except to skip malformed data (NaN,
  non-numeric timestamps) instead of crashing
- Share a single instance domain across all per-CPU metrics instead
  of creating a new one per mode
- Handle cpu labels like cpu-0 by extracting the trailing integer
  with a regex, so int() conversion no longer fails on non-numeric
  prefixes
Test 1687 verifies basic conversion - metric descriptors, PMID
structure with item IDs resetting per cluster, shared CPU instance
domain, correct values, and hostname from metadata.

Test 1699 verifies edge cases - malformed data (NaN values, bad
timestamps) skipped gracefully, cpu-N style labels parsed correctly,
invalid metadata.json handled with warning, and proper exit status
for missing or empty data directories.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
qa/1687 (1)

111-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the metadata hostname value.

This only verifies that a host label exists, so it passes if metadata.json is ignored. Match testhost.example.com explicitly.

Proposed fix
-pmdumplog -l $tmp 2>&1 | grep 'Performance metrics from host'
+pmdumplog -l $tmp 2>&1 | grep 'Performance metrics from host testhost.example.com'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa/1687` around lines 111 - 112, Update the hostname assertion in the
metadata verification section to match the explicit value testhost.example.com,
rather than only checking for the generic “Performance metrics from host” label.
Preserve the existing pmdumplog invocation and ensure the test fails when
metadata.json is ignored.
qa/1699 (1)

70-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise the malformed numeric-value path.

These lines only verify the bad timestamp. Add a mem.freemem check and use an unambiguously non-numeric fixture value so this test proves malformed values are skipped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa/1699` around lines 70 - 75, Update the QA scenario around the mem.physmem
check to use a fixture with an unmistakably non-numeric value, then add a
mem.freemem query that exercises and verifies the malformed numeric-value path
while retaining the existing malformed-timestamp coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@qa/1687`:
- Around line 111-112: Update the hostname assertion in the metadata
verification section to match the explicit value testhost.example.com, rather
than only checking for the generic “Performance metrics from host” label.
Preserve the existing pmdumplog invocation and ensure the test fails when
metadata.json is ignored.

In `@qa/1699`:
- Around line 70-75: Update the QA scenario around the mem.physmem check to use
a fixture with an unmistakably non-numeric value, then add a mem.freemem query
that exercises and verifies the malformed numeric-value path while retaining the
existing malformed-timestamp coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: d02a2b5c-35b4-45ad-a540-b9f41fc8388e

📥 Commits

Reviewing files that changed from the base of the PR and between d77dcc7 and a5a3eca.

⛔ Files ignored due to path filters (2)
  • qa/1687.out is excluded by !**/*.out
  • qa/1699.out is excluded by !**/*.out
📒 Files selected for processing (13)
  • qa/1687
  • qa/1699
  • qa/group
  • qa/promdb2pcp/baddata/cpu.json
  • qa/promdb2pcp/baddata/memory.json
  • qa/promdb2pcp/baddata/metadata.json
  • qa/promdb2pcp/cpu.json
  • qa/promdb2pcp/disk.json
  • qa/promdb2pcp/loadavg.json
  • qa/promdb2pcp/memory.json
  • qa/promdb2pcp/metadata.json
  • qa/promdb2pcp/network.json
  • src/promdb2pcp/promdb2pcp.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/promdb2pcp/promdb2pcp.py

@Aniruddh9

Copy link
Copy Markdown
Author

hi @lmchilton ,
Hope you are doing well. I have done proposed changes and the test cases. Let me know if they are fine or anything else need to be done.

- Test 1687: grep for explicit hostname testhost.example.com so the
  test fails if metadata.json is ignored, not just on .out diff
- Test 1699: use unmistakably non-numeric value in fixture instead of
  NaN to test the float() ValueError path directly, and add
  mem.freemem query to verify the malformed value was skipped
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants