Skip to content

add extra units to pmUnits#2598

Open
kmcdonell wants to merge 58 commits into
performancecopilot:mainfrom
kmcdonell:units
Open

add extra units to pmUnits#2598
kmcdonell wants to merge 58 commits into
performancecopilot:mainfrom
kmcdonell:units

Conversation

@kmcdonell
Copy link
Copy Markdown
Member

Description

Implement extensions to pmUnits to support units and scales for temperature, voltage, current and power (in a way that is extensible for other units orthogonal to the classical space-time-count dimensions, if we need them).

Related Issues

Addresses RFE at #2483

The attached
TODO.units.txt
provides notes as to what has been done and what remains to be done.

kmcdonell and others added 30 commits April 30, 2026 08:08
This first batch of changes ...

- pmapi.h: change pmUnits to introduce extraUnit and extraScale fields
  and associated macros for temperature, voltage, current and power

- pmda.h: rewrite the PMDA_PMUNITS() macro to make it endian-safe and
  avoid trashing the new fields (they will be explicitly 0 when this
  macro is used, like in all the legacy code)

- pmda.h: introduce a new PMDA_EXTRAUNITS() macro to set all 8 fields
  of pmUnits in an endian-safe manner

- libpcp/extraunits.c: new data-driven routines to handle extra units;
  currently working for pmUnitsStr*() and pmParseUnitsStr()

- libpcp/internal.h: extract __pmUnits (no bit fields) from units.c so
  it can be used there and in extraunits.c

- libpcp/units.c: minimal changes to support extra units, mostly
  calling routines in extraunits.c to do the work; changes currently
  done for pmUnitsStr*() and __pmParseUnitsStrPart() (the routine below
  pmParseUnitsStr())

- libpcp/units.c: add -Dpmapi diagnostics for pmUnitsStr_r()

- libpcp/logmeta.c: extend test in __pmLogAddDesc() to check extraUnit
  and extraScale fields when checking for changes in pmUnits

- sample PMDA: add metrics for all supported values of extraUnit and
  extraScale

- qa fixups for all of the above
Adding extraunits.c (for pmUnit changes) over in libpcp needs
manual GNUmakefile updates for these two derivative libraries.
Replace initialization of .pad field with initialization of .extraUnit
and .extraScale fields.

    modified:   src/perl/MMV/MMV.xs
    modified:   src/perl/PMDA/PMDA.xs

Note, this fixes a compilation issue, but we're not planning on
exposing the "extra units" features in the Perl interface layer
(pending a demonstrable need).
- rewrite the MMV_UNITS() macro to make it endian-safe and
  avoid trashing the new fields (they will be explicitly 0 when this
  macro is used, like in all the legacy code)

- introduce a new MMV_EXTRAUNITS() macro to set all 8 fields
  of pmUnits in an endian-safe manner
- __pmCheckDesc() checks the integrity of a pmDesc, including the
  pmUnits part ... this will be used to replace the similar checks
  in pmlogcheck and I expect to use it in the derived metrics and
  pmie changes for extended units, but for the moment ...

- pmDesc checks in pminfo with -v have been replaced by a call to
  __pmCheckDesc() and a new -e flag is the same as -v, just not
  reporting metrics that return no values (so the metadata issues
  are more obvious)

- __pmCheckDesc() is defined in libpcp.h and exposed via exports.in
  but is not part of the PMAPI

- shell completion and man page updates for pminfo -e

- also an extra units fix for pmlogextract snuck into this commit.
- src/pmdas/statsd/src/pmda-callbacks.c: endian-safe initialization
  of pmUnits and fix bogus metadata setting in create_pcp_metric()

- src/pmdas/statsd/src/pmdastatsd.c: endian-safe initialization
  of pmUnits

- qa/statsd/src/cases/07.py: rework check to use pminfo -fed directly
  to demonstrate metadata problem and verify fix

- qa/713: tweak filter for hex indom number (now) from pminfo output
- PMDA_PMUNITS() => PMDA_UNITS() for symmetry with MMV_UNITS()
- PMDA_PMUNITS() preserved for backwards compatibility
No support in the Perl APIs at this stage, but this change is
needed to ensure the extra fields in the pmUnits are correctly
set to zero.
Need to check the additional fields when testing for a change in
the metadata.
previous change to allow checking of markdown files introduced a typo
that broke the script for all troff source man pages.
- update pmdesc.c to ...
  + only emit initializers for dimension and scale when dimension
    is non-zero
  + emit extraUnit and extrScale initializers if required

- regenerate metrics.c (makefile updates to re-enable rule) which
  has briefer pmUnits and some additional metrics (the PMNS has
  moved on since Mark last generated metrics.c)
- new EXTRAUNITS option in METRIC clause that accepts units
  as an 8-tuple (not a 6-tuple as for the UNITS option)

- change sample.temperature.centigrade => sample.temperature.celsius
  to match international agreement in 1948.

- man page

- assorted qa changes and extensions

 Please ente `r the commit message for your changes. Lines starting
- PM_UNITS_... renamed PM_UNIT_... so we're using the singular form
  for macros, pmUnits field names ... library routines tend to use
  ExtraUnits in the routine names because the "Units" routines already
  existed and they typically relate to operations on a pmUnits struct
  (where the name is pluralised)

- fix the 4:4 to 5:3 bit field split for .extraUnit and .extraScale
  in the ! HAVE_BITFIELDS_LTOR alternate block (the sizes were round
  the wrong way)

- upgrade the Str and Parse routines to accept extra units in the
  dividend (.extraUnit > 0) or the divisor (.extraUnit < 0) which is
  starting to make use of the extra bits and signedness of .extraUnit;
  also extend qa/670 to exercise the related new code

- remade the qa/extra archive after fixing the change from a 4:4 to
  5:3 bit field split for .extraUnit and .extraScale

- pmConvScale() now understands extra units and this required a new
  table-driven __pmConvExtraScale() in src/libpcp/src/extraunits.c
  to hide the "extra" units semantics from pmConvScale() and avoid
  the nested switch mess that bidirectional N x M conversion would
  otherwise require

- document pmiExtraUnits()

- update the pmLookupDesc(3) man page to provide the definitive
  description of extra units ... this man page is cross-referenced from
  many other man pages dealing the pmUnits

- additional man page changes for all of the above

- add pmunits "group" to qa/group and update all entries for QA tests
  that dink with pmUnits

- extend qa/src/grind_conv.c to accept both a 6-tuple (classical)
  and 8-tuple (classical + extra units) specification for both input
  and output pmUnits
Conflicts:
    qa/group
pmie plays loose and fast with the metadata, so there is very little
checking of pmUnits compatibility in expressions => not much needs to
be changed here (in fact main change is removing some dead code).
- pmie propagation of pmUnits in expression trees was seriously broken,
  and had not been noticed because pmie does not check pmUnits for
  compatibility of operands in expressions (another good reason to
  prefer derived metrics over pmie expressions) ... the fixes here address
  the obvious issues and in particular safe handling of extra units and
  correct metadata for the default metrics exported from the summary PMDA
  (which runs pmie in "secret agent" mode)

- revise default expressions for the summary PMDA metrics
  summary.netif.packets and summary.netif.busy to consistently consider
  only active network interfaces, and tweak the help text to better
  match the semantics of the expressions used

- new qa/1665 and qa/1666 to exercise pmUnits handling for pmie
  and the summary PMDA

- fix incorrect pmUnits fiddling for QA's broken PMDA

- fix incorrect pmUnits fiddling in qa/src/pducheck.c

- fix leftover fallout from pminfo -e and sample PMDA pmns changes with
  some QA remakes

- qa/1426: rework to use modern network.tcp "drops" metrics rather than
  _notrun when the Irix metrics are not available

- make _triage_wait_point() look for $here/$seq.wait by default, so it
  works when a QA test has cd'd away from $here ... update COOKBOOK.md
  to match

- assorted extra unit QA remakes and fixups, some from earlier changes
  that had not been exposed to a full QA run with all the pmUnits
  changes to date
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds pmUnits.extraUnit/extraScale support and wires parsing, formatting, conversion, validation, APIs, tooling, PMDAs/sample, bindings, completions, docs, and QA; also introduces pminfo -e metadata-verification mode.

Changes

Extra units and metadata verification

Layer / File(s) Summary
Public API, headers, and constants
src/include/pcp/pmapi.h, src/include/pcp/import.h, src/include/pcp/libpcp.h, src/include/pcp/pmda.h
Adds extraUnit/extraScale to pmUnits, new PM_UNIT_* and scale constants, updated helper prototypes and PMDA/import macros and exports.
Core libpcp implementation
src/libpcp/src/extraunits.c, src/libpcp/src/units.c, src/libpcp/src/internal.h, src/libpcp/src/exports.in, src/libpcp/src/check-statics
Implements parsing/formatting and rescaling helpers, descriptor integrity checks (__pmCheckDesc), integrates extra-units conversion into pmConvScale, registers exports and static-check entries.
Units parsing & formatting
src/libpcp/src/units.c, qa/src/units-parse.c, qa/src/grind_conv.c, qa/src/pmconvscale.c
pmUnits string parse/format extended for extra-unit fields; round-trip and conversion test helpers updated; pmConvScale invokes extra-scale conversion when applicable.
Derived-metric parser & semantic checks
src/libpcp/src/derive_parser.y.in, src/libpcp/src/derive_parser.*
map_units/map_desc/check_expr updated to propagate and validate extraUnit/extraScale across arithmetic, relational, boolean, and ternary expressions.
Tooling and metadata wiring
src/pminfo/pminfo.c, src/pmlogrewrite/*, src/pmlogextract/pmlogextract.c, src/pmlogger/src/checks.c, src/pmdumptext/pmdumptext.cpp
pminfo: new -e/--metadata-errors mode calling __pmCheckDesc; pmlogrewrite grammar/lexer and main include EXTRAUNITS handling and pre-checks; log tools consider extra-unit fields when comparing descriptors.
Bindings, import, and language helpers
src/libpcp_import/src/import.c, src/libpcp_import/src/exports, src/python/*, src/perl/*, src/perl/MMV/*
Add pmiExtraUnits, ensure pmiUnits zeros extra fields, extend Python pmUnits/pmUnits_int, add Perl wrappers and constants, and export new symbols.
Sample PMDA and PMNS
src/pmdas/sample/*
Add sample.temperature/voltage/current/power PMNS, help, descriptors, fetch/store conversions using extra-unit scales.
Web/query series
src/libpcp_web/src/query.c, src/libpcp_web/src/query_parser.y.in
Series arithmetic/compatibility updated to include extra-unit equality and propagation rules; debug dumps extended.
PMIE and expression integration
src/pmie/src/dstruct.c, src/pmie/src/syntax.c, man/man1/pmie.1
instExpr made externally visible and invoked earlier; unit propagation clears scales when dimensions hit zero; pmie docs updated for comments (//) and extra-unit scaling guidance.
QA, tests, scripts and expected outputs
qa/*, qa/archives/mk.extra, qa/*out.*
Add numerous QA scripts and expected outputs exercising extra units, update filters/helpers, and add an extra archive recipe.
Docs, manpages, completions, makefiles
man/*, src/bashrc/pcp_completion.sh, src/zshrc/_pcp, src/libpcp/src/GNUmakefile, src/collectl2pcp/GNUmakefile
Man pages updated for new symbols/clauses and pmie/pminfo options; shell completions updated; makefiles include extraunits.c and remake target; man-spell updates.

Sequence Diagram(s)

sequenceDiagram
  participant UserCLI as pminfo (-e)
  participant Libpcp as libpcp::__pmCheckDesc
  participant ExtraUnits as extraunits.c::__pmConvExtraScale
  participant Rewrite as pmlogrewrite::check_metadata

  UserCLI->>Libpcp: request verify metadata (__pmCheckDesc)
  Libpcp->>ExtraUnits: validate/convert extraUnit/extraScale (if present)
  Libpcp-->>UserCLI: return metadata status + errmsg (if any)
  Rewrite->>Libpcp: pre-validate new metric descriptors via __pmCheckDesc()
  Rewrite-->>UserCLI: emit warnings/errors during config processing
Loading

Possibly related issues

Poem

"A rabbit in the code hops light and fleet,
extra units tucked in every bit and sheet,
docs sing the names of C/F/V/A/W,
tests hop along, the build runs true and new,
carrots for reviewers — ok, that's my cue! 🐇"

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
qa/1665 (1)

50-51: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the extra trailing blank line at EOF.

Line [51] indicates an extra blank line after exit; keep exactly one trailing newline at file end.

Suggested fix
-exit
-
+exit

As per coding guidelines "Ensure all text and source files end with a single trailing newline character".

🤖 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/1665` around lines 50 - 51, The file ends with an extra blank line after
the final token "exit"; remove the extra trailing blank line so the file ends
with exactly one newline character following "exit" (i.e., ensure there is a
single trailing newline at EOF and no additional empty lines).
qa/1667 (1)

94-95: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the extra trailing blank line at EOF.

Line [95] adds an extra trailing blank line after exit; keep a single trailing newline only.

Suggested fix
-exit
-
+exit

As per coding guidelines "Ensure all text and source files end with a single trailing newline character".

🤖 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/1667` around lines 94 - 95, The file ends with an extra blank line after
the final token "exit"; remove the additional empty line so the file terminates
with exactly one trailing newline character. Locate the final line containing
"exit" and ensure there is only a single newline immediately after it (no extra
blank lines).
qa/1666 (1)

62-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the extra trailing blank line at EOF.

Line [63] leaves an extra blank line after exit; this file should end with a single trailing newline.

Suggested fix
-exit
-
+exit

As per coding guidelines "Ensure all text and source files end with a single trailing newline character".

🤖 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/1666` around lines 62 - 63, The file currently has an extra blank line
after the final "exit" line; update the file so it ends immediately after the
"exit" line with exactly one trailing newline (remove the extra blank line at
EOF).
qa/new (1)

63-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

-R no longer works with numbers created by -r.

-r creates the skeletal $id and $id.out files, but this new -R path rejects the reserved number as soon as either file exists. That makes the documented reserve → unreserve flow fail before the group entry is updated, and the later skeleton-generation path still assumes the files do not exist.

🤖 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/new` around lines 63 - 83, The unreserve (-R) branch incorrectly rejects
an ID if $id or $id.out exist even when the group file shows the ID is reserved
(breaking the documented reserve->unreserve flow); update the logic in the
unreserve handling (the block using unreserve=true, id="$OPTARG", grep
"^$id:reserved" group and the subsequent file checks) to allow proceeding when
the group entry shows reserved—even if $id or $id.out files exist (these may
have been created by -r); keep the check that forbids using -R after -r by
preserving the if $reserve ... error check, but remove or relax the immediate
file-existence exit-on-error so the group file can be updated to unreserved.
src/libpcp/src/derive_parser.y.in (1)

1329-1334: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve right-hand extra units in multiplication.

When only the right operand carries an extra unit and the left operand is a non-constant dimensionless metric, np->desc is copied from the left side and this branch never restores extraUnit/extraScale. The result then loses its extra-unit metadata even though the multiplication is otherwise allowed.

Suggested fix
     if (np->type == N_STAR) {
 	np->desc.units.dimCount = left->units.dimCount + right->units.dimCount;
 	np->desc.units.dimTime = left->units.dimTime + right->units.dimTime;
 	np->desc.units.dimSpace = left->units.dimSpace + right->units.dimSpace;
-	/* prior checks ensure no extra units for multiplication */
+	if (left->units.extraUnit == 0 && right->units.extraUnit != 0) {
+	    np->desc.units.extraUnit = right->units.extraUnit;
+	    np->desc.units.extraScale = right->units.extraScale;
+	}
     }
🤖 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 `@src/libpcp/src/derive_parser.y.in` around lines 1329 - 1334, The
multiplication branch for N_STAR updates dims but doesn't preserve extra
units/scales when np->desc was copied from left; modify the N_STAR handling
(around np, left, right) to set np->desc.extraUnit and np->desc.extraScale from
right when right->desc.extraUnit is present (and left has none), i.e. after
updating dimCount/dimTime/dimSpace assign extraUnit/extraScale from right->desc
into np->desc so the resulting node retains the right-hand extra unit metadata.
src/libpcp_import/src/import.c (1)

172-185: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid shared function-local static pmUnits in pmiUnits / pmiExtraUnits (shared mutable state + race risk)
Both helpers declare static pmUnits units;, overwrite its fields on each call, and return it by value—introducing shared mutable state and possible data races under concurrent callers.

Suggested fix
 pmUnits
 pmiUnits(int dimSpace, int dimTime, int dimCount, unsigned int scaleSpace, unsigned int scaleTime, unsigned int scaleCount)
 {
-    static pmUnits units;
+    pmUnits units;
     units.dimSpace = dimSpace;
     units.dimTime = dimTime;
     units.dimCount = dimCount;
     units.scaleSpace = scaleSpace;
     units.scaleTime = scaleTime;
     units.scaleCount = scaleCount;
     units.extraUnit = 0;
     units.extraScale = 0;

     return units;
 }

 pmUnits
 pmiExtraUnits(int dimSpace, int dimTime, int dimCount, unsigned int scaleSpace, unsigned int scaleTime, unsigned int scaleCount, int extraUnit, unsigned int extraScale)
 {
-    static pmUnits units;
+    pmUnits units;
     units.dimSpace = dimSpace;
     units.dimTime = dimTime;
     units.dimCount = dimCount;
     units.scaleSpace = scaleSpace;
     units.scaleTime = scaleTime;
     units.scaleCount = scaleCount;
     units.extraUnit = extraUnit;
     units.extraScale = extraScale;

     return units;
 }
🤖 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 `@src/libpcp_import/src/import.c` around lines 172 - 185, The helper uses a
shared function-local static pmUnits (in pmiUnits and similarly in
pmiExtraUnits) causing shared mutable state and data-race risk; change the
declaration to a non-static local pmUnits variable (stack-allocated) and
initialize its fields (or use a compound initializer) before returning so each
call returns an independent struct instance instead of reusing a static one.
src/pmdas/statsd/src/pmda-callbacks.c (1)

927-995: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix missing duration_stat declaration in statsd_resolve_dynamic_metric_fetch()

duration_stat is assigned/used starting at line 940, but there is no declaration in the function scope before that use in src/pmdas/statsd/src/pmda-callbacks.c, so this won’t compile. Declare it as enum DURATION_INSTANCE duration_stat alongside the other local variables.

Suggested fix
 static int
 statsd_resolve_dynamic_metric_fetch(pmdaMetric* mdesc, unsigned int instance, pmAtomValue** atom) {
+    enum DURATION_INSTANCE duration_stat;
     struct pmda_metric_helper* helper = (struct pmda_metric_helper*) mdesc->m_user;
🤖 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 `@src/pmdas/statsd/src/pmda-callbacks.c` around lines 927 - 995, The function
statsd_resolve_dynamic_metric_fetch() uses duration_stat but never declares it;
add a local declaration "enum DURATION_INSTANCE duration_stat;" near the other
locals (e.g., alongside helper, data, config, result, serial, is_default_domain,
status) so duration_stat is in scope before its first assignment in the
METRIC_TYPE_DURATION branches; ensure the enum type DURATION_INSTANCE is
included via existing headers or forward declarations already used by functions
like map_to_duration_instance and get_duration_instance.
🟠 Major comments (20)
qa/common.check-2230-2231 (1)

2230-2231: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Suppress systemctl stderr in scheduler teardown path.

At Line 2230 and Line 2231, only stdout is redirected. Missing units or stop errors can print to stderr and introduce nondeterministic noise in QA runs.

Suggested fix
-	    $__rc_sudo systemctl is-active $__i >/dev/null || continue
-	    $__rc_sudo systemctl stop $__i >/dev/null
+	    $__rc_sudo systemctl is-active $__i >/dev/null 2>&1 || continue
+	    $__rc_sudo systemctl stop $__i >/dev/null 2>&1
🤖 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/common.check` around lines 2230 - 2231, The scheduler teardown currently
only redirects stdout for the systemctl checks and stops, so error messages
still go to stderr and create nondeterministic QA noise; update the two
invocations $__rc_sudo systemctl is-active $__i and $__rc_sudo systemctl stop
$__i to also redirect stderr (append 2>/dev/null) so both stdout and stderr are
suppressed during teardown.
qa/common.check-396-396 (1)

396-396: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix redirection order for verify capture.

At Line 396, stderr is duplicated before stdout is redirected, so stderr bypasses $tmp.verify and leaks to terminal output.

Suggested fix
-    pminfo -v $__agent 2>&1 >$tmp.verify
+    pminfo -v $__agent >$tmp.verify 2>&1
🤖 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/common.check` at line 396, The current pminfo invocation redirects stderr
to the original stdout before stdout is redirected, causing stderr to leak;
change the redirection order on the pminfo command (the line invoking pminfo -v
$__agent) so stdout is first redirected into $tmp.verify and then stderr is
redirected to that stream (use >$tmp.verify 2>&1) to capture both stdout and
stderr into $tmp.verify.
qa/new-397-399 (1)

397-399: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid sed -i for portability

sed -i is not POSIX-specified, and GNU vs BSD/macOS sed differ in how -i is invoked, so sed -e "/^$id:reserved/d" -i group may be incompatible and fail on non-GNU hosts.

Proposed portable change
-    sed -e "/^$id:reserved/d" -i group
+    sed -e "/^$id:reserved/d" group >"$tmp.group"
+    mv "$tmp.group" group
🤖 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/new` around lines 397 - 399, Replace the in-place sed invocation that
deletes the ":reserved" line with a portable approach: run the sed expression
(the "/^$id:reserved/d" operation referencing the id variable) and write the
output to a temporary file (created with mktemp), then atomically move/rename
the temp file over the original "group" file and clean up the temp on error;
ensure you handle errors (non-zero exit) before overwriting and use a trap to
remove the temp on failure so the replace is safe on both GNU and BSD/macOS
systems.
qa/src/units-parse.c-45-58 (1)

45-58: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid reading reversed/reversed_multiplier when pmParseUnitsStr fails

qa/src/units-parse.c reads reversed (and prints it) even when sts != 0:

  • pmunits_roundtrip(): pmUnitsStr_r(&reversed, convertedt, ...) and the print_p fprintf use reversed/reversed_multiplier before checking sts.
  • pmunits_parse(): pmUnitsStr_r(&reversed, ...) and the fprintf use reversed/reversed_multiplier unconditionally, before any sts check.

Guard the pmUnitsStr_r call(s) and printing so they only run when sts == 0 (and avoid using reversed/reversed_multiplier otherwise).

🤖 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/src/units-parse.c` around lines 45 - 58, The test prints use of
reversed/reversed_multiplier even when pmParseUnitsStr (sts) failed; modify
pmunits_roundtrip and pmunits_parse to only call pmUnitsStr_r(&reversed, ...)
and only include reversed/reversed_multiplier in the fprintf when sts == 0
(i.e., after confirming pmParseUnitsStr returned success). In practice, wrap the
pmUnitsStr_r invocation and the portion of the fprintf that references
reversed/reversed_multiplier with an if (sts == 0) guard (or split the fprintf
into two branches) so reversed and reversed_multiplier are never read or printed
when pmParseUnitsStr failed.
src/collectl2pcp/GNUmakefile-37-40 (1)

37-40: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add pmdesc as an explicit prerequisite for metrics.c generation.

metrics.c invokes ./pmdesc but does not depend on pmdesc. In parallel builds this can race and fail intermittently.

Suggested fix
-metrics.c: metrics.h pmdesc.c
+metrics.c: metrics.h pmdesc.c pmdesc
 	PCP_DERIVED_CONFIG='' pminfo hinv kernel mem swap network disk filesys \
 	swapdev rpc nfs nfs3 nfs4 xfs pmda ipc vfs quota tmpfs sysfs proc \
 	| ./pmdesc > metrics.c
🤖 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 `@src/collectl2pcp/GNUmakefile` around lines 37 - 40, The make rule that
generates metrics.c must explicitly depend on the pmdesc executable to avoid
races: update the rule for the target metrics.c (currently listing metrics.h and
pmdesc.c) to also depend on the pmdesc binary (add "pmdesc" as a prerequisite)
because the recipe runs "./pmdesc"; ensure the prerequisite is the built
executable (not just pmdesc.c) so make will build pmdesc before running the
command that produces metrics.c.
src/libpcp/src/derive_parser.y.in-1711-1721 (1)

1711-1721: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't reject same extra units just because their scales differ.

This hard equality check makes expressions like V + mV, A > mA, or W / mW fail even though the new extra-unit model explicitly adds scale support. Existing count/time/space units are normalized before arithmetic; extra units need the same treatment instead of requiring identical extraScale values up front.

🤖 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 `@src/libpcp/src/derive_parser.y.in` around lines 1711 - 1721, The current
check in the derive parser rejects operands when left->units.extraUnit ==
right->units.extraUnit but left->units.extraScale != right->units.extraScale;
instead, implement scale normalization for extra units so differing extraScale
values are allowed when they represent the same physical unit at different
scales. Update the block that references left->units.extraUnit,
right->units.extraUnit, left->units.extraScale, right->units.extraScale and
PM_TPD(derive_errmsg) to: when both extraUnit fields are non-zero, first verify
the extraUnit identity (unit id matches), then normalize one operand’s
extraScale to the other (or compute a common scale) before proceeding; only set
PM_TPD(derive_errmsg) and goto bad if the unit identities differ or
normalization fails (e.g., incompatible scales), not merely because extraScale
values differ.
src/libpcp/src/extraunits.c-609-609 (1)

609-609: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use double precision in PM_TYPE_DOUBLE conversion.

PM_TYPE_DOUBLE currently casts via float, which truncates precision for all double conversions.

Proposed fix
 	case PM_TYPE_DOUBLE:
-	    val->d = (float)(cp->a + cp->b * (val->d + cp->c) / cp->d);
+	    val->d = (double)(cp->a + cp->b * (val->d + cp->c) / cp->d);
 	    break;
🤖 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 `@src/libpcp/src/extraunits.c` at line 609, The PM_TYPE_DOUBLE conversion
currently forces a float cast which loses precision; update the conversion
expression by removing the (float) cast and using double precision so the
computation of val->d uses full double precision (reference symbols:
PM_TYPE_DOUBLE, val->d, cp->a, cp->b, cp->c, cp->d). Ensure the result and any
intermediate values remain in double precision (not float) so double-to-double
conversion is preserved.
src/libpcp/src/units.c-1584-1591 (1)

1584-1591: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject unit strings that place extra units on both sides of /.

This block silently prefers the dividend’s extra unit and discards the divisor’s when both are present, producing an incorrect parse result.

Proposed fix
     /*
      * extra units come from either dividend or divisor, but not both
      */
+    if (dividend.extraUnit != 0 && divisor.extraUnit != 0) {
+	*errMsg = strdup("extra units cannot appear in both dividend and divisor");
+	sts = PM_ERR_CONV;
+	goto out;
+    }
     if (dividend.extraUnit != 0) {
 	out->extraUnit = dividend.extraUnit;
 	out->extraScale = dividend.extraScale;
     }
🤖 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 `@src/libpcp/src/units.c` around lines 1584 - 1591, Detect the case where both
dividend.extraUnit and divisor.extraUnit are non-zero and treat it as a parse
error instead of choosing one side; in the block that currently assigns
out->extraUnit/out->extraScale, add a check for (dividend.extraUnit != 0 &&
divisor.extraUnit != 0) and return a failure (or set the function's error state)
so the parser rejects unit strings with extra units on both sides of '/'; do not
silently prefer dividend—emit a clear error (or error code) and ensure callers
handle the failure.
src/libpcp/src/units.c-175-192 (1)

175-192: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate buflen before writing to buf.

buf[0] = '\0' is executed before the size check, so a zero-length buffer can be written out of bounds.

Proposed fix
-    buf[0] = '\0';
-
-    if (pu->extraUnit) {
-	__pmExtraUnitsStr(pu, xbuf, sizeof(xbuf));
-	extrastr = xbuf;
-    }
-
-    /*
-     * must be at least 60 bytes in buf[], then we don't need to
-     * pollute the code below with a check every time we call
-     * pmsprintf() or increment p
-     */
     if (buflen < 60) {
 	if (pmDebugOptions.pmapi) {
 	    fprintf(stderr, ":> returns NULL\n");
 	}
 	return NULL;
     }
+    buf[0] = '\0';
+
+    if (pu->extraUnit) {
+	__pmExtraUnitsStr(pu, xbuf, sizeof(xbuf));
+	extrastr = xbuf;
+    }
🤖 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 `@src/libpcp/src/units.c` around lines 175 - 192, The code writes to buf[0]
before validating buflen, which can overflow for a zero-length buffer; update
the function to validate buflen first (ensure buflen >= 60 or at least buflen >
0) before assigning buf[0] = '\0' or move the buf[0] = '\0' line to after the
buflen checks; keep the existing logic that uses buf, extrastr, xbuf and the
__pmExtraUnitsStr(pu, xbuf, sizeof(xbuf)) call but only perform any write into
buf after the size guard passes.
src/libpcp_web/src/query.c-4718-4726 (1)

4718-4726: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate extra-unit operator constraints before arithmetic loops.

These checks run after per-sample/per-instance mutation. For invalid combinations, data has already been modified before returning -EPROTO.

Move the validation to just after series_calculate_binary_check(...) and before iterating samples.

Also applies to: 4791-4803

🤖 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 `@src/libpcp_web/src/query.c` around lines 4718 - 4726, The extra-unit
validation (checks of l_units.extraUnit and r_units.extraUnit that return
-EPROTO) must be moved earlier so it runs before any per-sample or per-instance
mutation: relocate the block that inspects l_units.extraUnit and
r_units.extraUnit to immediately after the call to
series_calculate_binary_check(...) and before any loops/iteration over
samples/instances in the function (and do the same relocation for the duplicate
check around lines 4791-4803). Ensure the check still logs via infofmt/batoninfo
and sets baton->error = -EPROTO and returns without modifying data.
src/libpcp_web/src/query.c-4566-4570 (1)

4566-4570: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate extraScale with extraUnit for + and -.

Result units copy extraUnit but not extraScale, so metadata can be rewritten with an incorrect extra scale (default zero).

Suggested fix
     large_units.dimCount = l_units.dimCount;
     large_units.dimSpace = l_units.dimSpace;
     large_units.dimTime = l_units.dimTime;
     large_units.extraUnit = l_units.extraUnit;
+    large_units.extraScale = l_units.extraScale;

Apply the same change in both series_calculate_plus and series_calculate_minus.

Also applies to: 4619-4623

🤖 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 `@src/libpcp_web/src/query.c` around lines 4566 - 4570, The code copies
large_units.extraUnit from l_units.extraUnit but fails to copy
l_units.extraScale, causing incorrect metadata; update both
series_calculate_plus and series_calculate_minus to also set
large_units.extraScale = l_units.extraScale wherever large_units.extraUnit is
assigned (use the same spot where
large_units.dimCount/dimSpace/dimTime/extraUnit are set) so result units
propagate both extraUnit and extraScale consistently.
src/pmdas/sample/pmns-384-387 (1)

384-387: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rename farenheit to fahrenheit before the PMNS name is finalized.

The misspelled metric leaf becomes part of the public namespace/API. It’s much cheaper to correct now than after users/scripts depend on it.

🤖 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 `@src/pmdas/sample/pmns` around lines 384 - 387, In the PMNS definition under
the sample.temperature node, rename the misspelled leaf "farenheit" to
"fahrenheit" so the public metric name is correct; update the PMNS entry in
sample.temperature (replace the "farenheit" label with "fahrenheit" and keep the
same identifier SAMPLE:0:173), and then search for and update any references,
tests, docs or consumers that refer to sample.temperature.farenheit to use
sample.temperature.fahrenheit before the namespace is finalized.
src/pmdas/summary/summary.pmie-37-40 (1)

37-40: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against divide-by-zero when there are no active interfaces.

Both expressions divide by (count_inst $netio > 0), which can be zero and cause evaluation errors.

🤖 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 `@src/pmdas/summary/summary.pmie` around lines 37 - 40, Both expressions can
divide by zero when count_inst $netio equals 0; update summary.netif.packets and
summary.netif.busy to guard using the instance count before dividing — e.g.,
compute a local denominator from count_inst $netio and use a conditional (if
count_inst $netio > 0 then sum_inst ($netio) / count_inst $netio else 0) for
summary.netif.packets and similarly for summary.netif.busy (use the same
conditional pattern or a ternary-like expression) so you never perform division
when count_inst $netio is zero.
src/pmdas/sample/src/sample.c-2753-2755 (1)

2753-2755: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Kelvin conversion constant is wrong.

Celsius→Kelvin should add 273.15, not 237.15.

Suggested fix
-			atom.f = 237.15 + temperature;
+			atom.f = 273.15 + temperature;
🤖 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 `@src/pmdas/sample/src/sample.c` around lines 2753 - 2755, The Kelvin
conversion in the sample.temperature.kelvin case is using the wrong constant;
update the expression in the switch case handling case 174 (symbol: atom.f =
237.15 + temperature) to use 273.15 instead of 237.15 so Celsius→Kelvin uses
273.15 (i.e., change the literal in the case 174 branch).
src/pmdas/sample/src/sample.c-2789-2795 (1)

2789-2795: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Milliwatt fetch uses micro-watt scaling.

sample.power.milliwatt should multiply watts by 1000, not 1000000.

Suggested fix
-			atom.f = power * 1000000;
+			atom.f = power * 1000;
🤖 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 `@src/pmdas/sample/src/sample.c` around lines 2789 - 2795, The milliwatt case
is using the wrong scale: in the switch handling (cases for
sample.power.milliwatt and sample.power.microwatt) the code sets atom.f = power
* 1000000 for both; change the multiplier for sample.power.milliwatt (case 183)
to 1000 (i.e. atom.f = power * 1000) while leaving sample.power.microwatt (case
184) at 1000000, referencing the case labels and the atom.f assignment that use
the power variable.
src/perl/PMDA/test.pl-92-109 (1)

92-109: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update TAP plan to include the new fifth test.

This block emits ok 5/not ok 5, but the plan still declares four tests (Line 19). That causes a TAP plan mismatch.

Suggested fix
-BEGIN { $| = 1; print "1..4\n"; }
+BEGIN { $| = 1; print "1..5\n"; }
🤖 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 `@src/perl/PMDA/test.pl` around lines 92 - 109, The TAP output in test.pl now
runs a fifth test (prints "ok 5"/"not ok 5") but the TAP plan still declares
four tests; update the TAP plan declaration from 1..4 to 1..5 so the plan
matches the actual tests. Locate the TAP plan string near the top of the test
script (where the "1..4" plan is emitted) and change it to "1..5" so
pmda_extraunits test results align with the declared plan.
src/pmdas/sample/src/sample.c-3401-3436 (1)

3401-3436: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Store-side unit conversions are inverted in multiple cases.

Several assignments convert in the wrong direction (e.g., mV/µV/mA/µA multiply instead of divide; kW divides instead of multiplies; mW/µW multiply instead of divide), and Kelvin uses the wrong offset/sign.

Suggested fix
-	    case 174:	/* sample.temperature.kelvin */
-		temperature = av.f + 237.15;
+	    case 174:	/* sample.temperature.kelvin */
+		temperature = av.f - 273.15;
 		break;
@@
-	    case 176:	/* sample.voltage.millivolt */
-		voltage = av.f * 1000;
+	    case 176:	/* sample.voltage.millivolt */
+		voltage = av.f / 1000;
 		break;
-	    case 177:	/* sample.voltage.microvolt */
-		voltage = av.f * 1000000;
+	    case 177:	/* sample.voltage.microvolt */
+		voltage = av.f / 1000000;
 		break;
@@
-	    case 179:	/* sample.current.milliamp */
-		current = av.f * 1000;
+	    case 179:	/* sample.current.milliamp */
+		current = av.f / 1000;
 		break;
-	    case 180:	/* sample.current.microamp */
-		current = av.f * 1000000;
+	    case 180:	/* sample.current.microamp */
+		current = av.f / 1000000;
 		break;
@@
-	    case 181:	/* sample.power.kilowatt */
-		power = av.f / 1000;
+	    case 181:	/* sample.power.kilowatt */
+		power = av.f * 1000;
 		break;
@@
-	    case 183:	/* sample.power.milliwatt */
-		power = av.f * 1000;
+	    case 183:	/* sample.power.milliwatt */
+		power = av.f / 1000;
 		break;
-	    case 184:	/* sample.power.microwatt */
-		power = av.f * 1000000;
+	    case 184:	/* sample.power.microwatt */
+		power = av.f / 1000000;
 		break;
🤖 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 `@src/pmdas/sample/src/sample.c` around lines 3401 - 3436, The unit conversions
in sample.c are inverted: fix the formulas so values in av.f (which represent
the incoming unit) are converted to the base unit (SI) before storing into
temperature/voltage/current/power. Specifically: in case 173 keep Fahrenheit->C
as (av.f - 32) / 1.8 (verify it's intended); in case 174 set Kelvin conversion
to av.f + 273.15; for voltage change case 176 (sample.voltage.millivolt) to
voltage = av.f / 1000 and case 177 (sample.voltage.microvolt) to voltage = av.f
/ 1000000; for current change case 179 (milliamp) to current = av.f / 1000 and
case 180 (microamp) to current = av.f / 1000000; for power change case 181
(kilowatt) to power = av.f * 1000, keep case 182 as power = av.f, change case
183 (milliwatt) to power = av.f / 1000 and case 184 (microwatt) to power = av.f
/ 1000000 so all stored values are in base units.
src/pmie/src/dstruct.c-859-860 (1)

859-860: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

CND_RATE stops applying /time for zero-time-dimension inputs

This guard changes unit propagation semantics: rate() should still decrement dimTime from 0 to -1 (e.g., count → count/sec). With the new condition, those paths keep incorrect units.

Suggested fix
-		if (x->units.dimTime != 0)
-		    x->units.dimTime--;
+		x->units.dimTime--;
+		if (x->units.dimTime == 0)
+		    x->units.scaleTime = 0;
🤖 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 `@src/pmie/src/dstruct.c` around lines 859 - 860, The change prevents rate()
from decrementing time dimension when dimTime is 0, breaking unit propagation
for zero-time-dimension inputs; restore the original semantics by removing the
conditional guard so that in the CND_RATE/rate handling code you always
decrement x->units.dimTime (i.e., unconditionally do x->units.dimTime-- for the
node handling rate/CND_RATE) so values with dimTime==0 become -1
(count→count/sec).
src/pmlogrewrite/lex.l-138-138 (1)

138-138: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset in_extra and return to metric state after the second extra macro.

After extraunits, the lexer can remain in units mode (in_extra is never reset, and <extra> always does BEGIN(units)), so rescale and subsequent metric clauses are tokenized incorrectly.

Proposed fix
-<metric>"units"		{ BEGIN(units); comma_count = 0; return TOK_UNITS; }
+<metric>"units"		{ BEGIN(units); comma_count = 0; in_extra = 0; return TOK_UNITS; }
 <metric>"extraunits"	{ BEGIN(units); comma_count = 0; in_extra = 1; return TOK_EXTRAUNITS; }

-<extra>[A-Z][A-Z_]*	{ yylval.str = dupstr(yytext, 0); BEGIN(units); return TOK_MACRO; }
+<extra>[A-Z][A-Z_]*	{
+			    yylval.str = dupstr(yytext, 0);
+			    if (comma_count == 7) {
+				in_extra = 0;
+				BEGIN(metric);
+			    }
+			    else {
+				BEGIN(units);
+			    }
+			    return TOK_MACRO;
+			}

Also applies to: 206-209, 232-250

🤖 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 `@src/pmlogrewrite/lex.l` at line 138, The lexer rule for <metric> "extraunits"
(which currently does BEGIN(units); comma_count = 0; in_extra = 1; return
TOK_EXTRAUNITS;) leaves the lexer stuck in units mode because in_extra is never
cleared; update this rule and the related <extra> handling (also at the other
occurrences around the blocks noted) so that after the second/closing extra
macro you set in_extra = 0 and call BEGIN(metric) to return to the metric state
(i.e., ensure the closing/second extra macro rule clears in_extra and switches
back from units to metric rather than leaving BEGIN(units) active).
src/python/pcp/pmconfig.py-952-954 (1)

952-954: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not abort on valid extra-unit metrics.

This unconditional sys.exit(1) makes tools fail for metrics that legitimately use extra units. Prefer continuing with native units (or route through existing incompatible-metric handling) instead of hard-exiting.

Suggested minimal fix
-            if is_extra(unit):
-                sys.stderr.write("TODO extra units: %s.\n" % (self.util.metrics[metric][2]))
-                sys.exit(1)
🤖 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 `@src/python/pcp/pmconfig.py` around lines 952 - 954, The code unconditionally
exits when encountering an extra-unit metric; instead, remove the sys.exit(1)
and just emit a warning and continue—i.e., in the block where is_extra(unit) is
true (referencing is_extra(unit), self.util.metrics[metric][2], metric, unit),
replace the sys.exit call with logic that logs the message to stderr (or via the
existing logger) and falls back to using the native units or routes the metric
through the existing incompatible-metric handling path rather than aborting the
process.
🟡 Minor comments (11)
man/man3/pmlookupdesc.3-160-161 (1)

160-161: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix typo: "farenheit" should be "fahrenheit".

Line 252 contains a spelling error in the comment for PM_TEMPERATURE_F where "farenheit" should be "fahrenheit".

📝 Proposed fix
 `#define` PM_TEMPERATURE_C        1       /* celsius */
-#define PM_TEMPERATURE_F        2       /* farenheit */
+#define PM_TEMPERATURE_F        2       /* fahrenheit */
 `#define` PM_TEMPERATURE_K        3       /* kelvin */

Also applies to: 198-214, 239-263

🤖 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 `@man/man3/pmlookupdesc.3` around lines 160 - 161, Fix the spelling of
"farenheit" to "fahrenheit" in the documentation comments for PM_TEMPERATURE_F
and any other occurrences in this manpage; update the comment text around the
PM_TEMPERATURE_F symbol (and other similar temperature-entry comments in the
same document) to use the correct spelling "fahrenheit" everywhere.
man/man3/pmconvscale.3-77-87 (1)

77-87: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix typo: "mast" should be "must".

Line 84 contains a spelling error where "mast" appears instead of "must".

📝 Proposed fix
 .I extraUnit
-mast have the same value in both
+must have the same value in both
 .I iunit
🤖 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 `@man/man3/pmconvscale.3` around lines 77 - 87, The documentation contains a
typo: the word "mast" should be corrected to "must" in the sentence describing
dimensionality equality; update the text in man/man3/pmconvscale.3 where it
lists dimSpace, dimTime, dimCount and extraUnit to read "...must have the same
value in both iunit and ounit." ensuring the reference names (dimSpace, dimTime,
dimCount, extraUnit, iunit, ounit) remain unchanged.
qa/src/badUnitsStr_r.c-28-30 (1)

28-30: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard both malloc results before writing into buffer.

Each path dereferences buffer immediately, so an allocation failure turns this QA helper into a null-pointer crash instead of a useful test failure.

Proposed fix
-    buffer = (char *)malloc(MYSIZE_OK);
+    buffer = (char *)malloc(MYSIZE_OK);
+    if (buffer == NULL) {
+        perror("malloc");
+        return 1;
+    }
     buffer[0] = '\0';
@@
-    buffer = (char *)malloc(MYSIZE_BAD);
+    buffer = (char *)malloc(MYSIZE_BAD);
+    if (buffer == NULL) {
+        perror("malloc");
+        return 1;
+    }
     buffer[0] = '\0';

Also applies to: 47-49

🤖 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/src/badUnitsStr_r.c` around lines 28 - 30, The code currently dereferences
buffer right after malloc (buffer[0] = '\0') and then calls pmUnitsStr_r, so
check the result of malloc before writing or calling pmUnitsStr_r: after buffer
= (char *)malloc(MYSIZE_OK) verify buffer != NULL and handle the allocation
failure (e.g., set result to a failure/skip value, log/error out, or return
early) instead of dereferencing; apply the same null-check pattern for the other
occurrences around lines invoking pmUnitsStr_r and using MYSIZE_OK so the test
reports allocation failure instead of crashing.
qa/src/pdu-gadget.c-724-726 (1)

724-726: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix wrong field in extraUnit overflow message.

The overflow log currently prints units.scaleCount, which makes debugging misleading when extraUnit overflows.

Suggested fix
-			    fprintf(stderr, "%d: units(): extraUnit overflow %d -> %d(\n", lineno, f, units.scaleCount);
+			    fprintf(stderr, "%d: units(): extraUnit overflow %d -> %d(\n", lineno, f, units.extraUnit);
🤖 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/src/pdu-gadget.c` around lines 724 - 726, The overflow log in the units
handling prints the wrong field (it currently prints units.scaleCount) making
extraUnit overflow diagnostics misleading; update the fprintf call in the
units() handling (the branch that checks if units.extraUnit != f) to include
units.extraUnit instead of units.scaleCount so the message reports the actual
overflowing field (refer to the fprintf invocation that uses lineno, f,
units.scaleCount and change the third printed value to units.extraUnit).
qa/src/check_import.c-105-106 (1)

105-106: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing status check for pmiAddMetric("my.metric.temperature", ...)
qa/src/check_import.c assigns sts = pmiAddMetric("my.metric.temperature", ...) but does not call check(sts, "pmiAddMetric") before sts is overwritten by the next pmiAddInstance(...), so metric-add failures are silently dropped.

Suggested fix
     sts = pmiAddMetric("my.metric.float", PM_ID_NULL, PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_DISCRETE, pmiUnits(0,0,0,0,0,0));
     check(sts, "pmiAddMetric");
     sts = pmiAddMetric("my.metric.temperature", PM_ID_NULL, PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, pmiExtraUnits(0,0,0,0,0,0,PM_UNIT_TEMPERATURE,PM_TEMPERATURE_C));
+    check(sts, "pmiAddMetric");
🤖 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/src/check_import.c` around lines 105 - 106, The call to
pmiAddMetric("my.metric.temperature", ...) assigns sts but never verifies it;
add a status check by calling check(sts, "pmiAddMetric") immediately after the
pmiAddMetric call (before the subsequent pmiAddInstance call overwrites sts) so
failures are not silently dropped; modify the sequence around pmiAddMetric,
pmiAddInstance, and the sts variable to ensure check(sts, "pmiAddMetric") is
executed right after pmiAddMetric returns.
src/libpcp/src/extraunits.c-169-171 (1)

169-171: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix trailing-space consumption after parsing extra units.

The loop meant to consume trailing spaces currently consumes non-space characters instead, which can advance the parse pointer incorrectly.

Proposed fix
-			/* and gobble any trailing space(s) */
-			while (*ptr && !isspace(*ptr))
+			/* and gobble any trailing space(s) */
+			while (*ptr && isspace((unsigned char)*ptr))
 			    ptr++;
🤖 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 `@src/libpcp/src/extraunits.c` around lines 169 - 171, The loop that should
skip trailing spaces after parsing extra units currently advances over non-space
characters ("while (*ptr && !isspace(*ptr)) ptr++;") which moves the pointer
wrongly; change it to skip actual whitespace by using "while (*ptr &&
isspace((unsigned char)*ptr)) ptr++;" so ptr advances only over space characters
and use the (unsigned char) cast to safely call isspace on signed chars (the ptr
variable and the loop in extraunits.c should be updated).
src/libpcp_qmc/src/qmc_desc.cpp-190-196 (1)

190-196: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix extra-unit sign check in denominator formatting.

The denominator branch is entered for negative exponents, but this check uses pu->extraUnit > 0, so negative extra units are omitted from the denominator output.

Suggested fix
-    if (pu->extraUnit > 0) {
+    if (pu->extraUnit < 0) {
         pmsprintf(p, sizeof(buf)-(p-buf), "%s", extraString);
         while (*p) p++;
     }
🤖 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 `@src/libpcp_qmc/src/qmc_desc.cpp` around lines 190 - 196, The denominator
branch for negative exponents currently checks pu->extraUnit > 0 and thus skips
negative extraUnit values; change the condition to pu->extraUnit < 0 so
extraString is printed into the denominator when extraUnit is negative (keep
using pmsprintf and the existing pointer arithmetic with p, div, buf to append
the string).
src/perl/PMDA/cvalue.c-68-83 (1)

68-83: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Initialize forw/back before packing in extraunits().

On Line 74, extraunits() assigns named fields but does not clear the structs first. Any padding/reserved bits can leak into *(unsigned int *)&forw/back, making this verifier output nondeterministic.

Proposed fix
 void
 extraunits(void)
 {
     pmUnits forw;
     pmUnits back;
+
+    memset(&forw, 0, sizeof(forw));
+    memset(&back, 0, sizeof(back));
 
     forw.dimSpace	= back.extraScale       = 1;
🤖 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 `@src/perl/PMDA/cvalue.c` around lines 68 - 83, The extraunits() function
assigns individual fields of the pmUnits variables forw and back but never
zero-initializes them, so padding/reserved bits can leak into the packed integer
prints; before setting any fields in extraunits(), zero or otherwise initialize
both forw and back (e.g., memset to 0 or value-initialize) so all
padding/reserved bits are deterministic, then proceed to assign the named fields
and call the printf lines that reference *(unsigned int *)&forw and *(unsigned
int *)&back.
src/pmdas/summary/help-53-55 (1)

53-55: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove stray parenthesis in help text.

packets per second). has an extra ).

🤖 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 `@src/pmdas/summary/help` around lines 53 - 55, The help text contains an extra
closing parenthesis in the string "Fraction of the active network interfaces
busy serving at least 400 packets per second)." — remove the stray ")" so the
sentence reads "...400 packets per second." in the help resource (update the
help text constant or string where that exact phrase appears).
src/pmdas/sample/help-942-998 (1)

942-998: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix user-facing typos in new help entries.

There are multiple spelling/wording errors in this block (e.g., wqater, Farenheit, and the mode=1. and up. sentence). Please clean these up before release.

🤖 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 `@src/pmdas/sample/help` around lines 942 - 998, Fix typos and stray text in
the sample help entries: correct "wqater" to "water" in all sample.temperature.*
descriptions, change "Farenheit" to "Fahrenheit" in the
sample.temperature.farenheit heading and description, remove or fix the stray
fragment "mode=1. and up." (delete the "and up." piece), and ensure phrasing is
consistent (e.g., "degrees Fahrenheit/Kelvin/Celsius" and "defaults to the
freezing point of water at sea level") for sample.temperature.celsius,
sample.temperature.farenheit, sample.temperature.kelvin and similar entries;
update any matching lines for sample.voltage.*, sample.current.*, and
sample.power.* to preserve consistent wording and correct spelling.
src/python/pmapi.c-929-929 (1)

929-929: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the argument-parser function label.

The parser error context string should reference pmUnits_int, not pmSetContextOptions, otherwise Python raises misleading argument errors.

Suggested fix
-		"IIIIIIII:pmSetContextOptions", keyword_list,
+		"IIIIIIII:pmUnits_int", keyword_list,
🤖 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 `@src/python/pmapi.c` at line 929, The argument-parser context label is
incorrect: replace the string "IIIIIIII:pmSetContextOptions" used with
keyword_list in the argument parsing call with "IIIIIIII:pmUnits_int" so the
parser reports errors for the pmUnits_int function/arguments rather than
pmSetContextOptions; locate the call that passes keyword_list alongside the
label (the one currently using pmSetContextOptions) and change the label to
pmUnits_int.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: ee7b7e3f-3884-4909-8233-8af40272220e

📥 Commits

Reviewing files that changed from the base of the PR and between fa63a30 and 419eb08.

⛔ Files ignored due to path filters (34)
  • qa/029.out is excluded by !**/*.out
  • qa/1193.out is excluded by !**/*.out
  • qa/1231.out is excluded by !**/*.out
  • qa/1247.out is excluded by !**/*.out
  • qa/1291.out is excluded by !**/*.out
  • qa/139.out is excluded by !**/*.out
  • qa/1418.out is excluded by !**/*.out
  • qa/1605.out is excluded by !**/*.out
  • qa/1664.out is excluded by !**/*.out
  • qa/1665.out is excluded by !**/*.out
  • qa/1666.out is excluded by !**/*.out
  • qa/1667.out is excluded by !**/*.out
  • qa/190.out is excluded by !**/*.out
  • qa/256.out is excluded by !**/*.out
  • qa/273.out is excluded by !**/*.out
  • qa/307.out is excluded by !**/*.out
  • qa/369.out is excluded by !**/*.out
  • qa/483.out is excluded by !**/*.out
  • qa/486.out is excluded by !**/*.out
  • qa/494.out is excluded by !**/*.out
  • qa/499.out is excluded by !**/*.out
  • qa/509.out is excluded by !**/*.out
  • qa/514.out is excluded by !**/*.out
  • qa/520.out is excluded by !**/*.out
  • qa/569.out is excluded by !**/*.out
  • qa/578.out is excluded by !**/*.out
  • qa/628.out is excluded by !**/*.out
  • qa/662.out is excluded by !**/*.out
  • qa/670.out is excluded by !**/*.out
  • qa/736.out is excluded by !**/*.out
  • qa/746.out is excluded by !**/*.out
  • qa/762.out is excluded by !**/*.out
  • qa/797.out is excluded by !**/*.out
  • qa/882.out is excluded by !**/*.out
📒 Files selected for processing (112)
  • man/man1/pmie.1
  • man/man1/pminfo.1
  • man/man1/pmlogrewrite.1
  • man/man3/pmapi.3
  • man/man3/pmconvscale.3
  • man/man3/pmiaddmetric.3
  • man/man3/pmiunits.3
  • man/man3/pmlookupdesc.3
  • man/man3/pmregisterderived.3
  • qa/1067.out.1
  • qa/1193
  • qa/1231
  • qa/1247
  • qa/1267
  • qa/1426
  • qa/1664
  • qa/1665
  • qa/1666
  • qa/1667
  • qa/1713
  • qa/255
  • qa/255.out.ipv6
  • qa/307
  • qa/344.out.linux
  • qa/344.out.other
  • qa/367.out.be
  • qa/367.out.le
  • qa/483
  • qa/510
  • qa/628
  • qa/670
  • qa/736
  • qa/COOKBOOK.md
  • qa/archives/extra.0
  • qa/archives/extra.index
  • qa/archives/extra.meta
  • qa/archives/mk.extra
  • qa/common.check
  • qa/common.filter
  • qa/group
  • qa/new
  • qa/pdudata/desc
  • qa/pdudata/descs
  • qa/pmdas/broken/broken_pmda.c
  • qa/pmdas/broken/broken_pmda.v1.c
  • qa/show-me
  • qa/src/badUnitsStr_r.c
  • qa/src/check_import.c
  • qa/src/endian.c
  • qa/src/grind_conv.c
  • qa/src/multictx.c
  • qa/src/pdu-gadget.c
  • qa/src/pducheck.c
  • qa/src/pmconvscale.c
  • qa/src/units-parse.c
  • qa/statsd/src/cases/07.py
  • scripts/man-spell
  • src/bashrc/pcp_completion.sh
  • src/collectl2pcp/GNUmakefile
  • src/collectl2pcp/metrics.c
  • src/collectl2pcp/pmdesc.c
  • src/include/pcp/import.h
  • src/include/pcp/libpcp.h
  • src/include/pcp/mmv_stats.h
  • src/include/pcp/pmapi.h
  • src/include/pcp/pmda.h
  • src/libpcp/src/GNUmakefile
  • src/libpcp/src/check-statics
  • src/libpcp/src/derive_parser.y.in
  • src/libpcp/src/exports.in
  • src/libpcp/src/extraunits.c
  • src/libpcp/src/fetchgroup.c
  • src/libpcp/src/internal.h
  • src/libpcp/src/logmeta.c
  • src/libpcp/src/units.c
  • src/libpcp_fault/src/GNUmakefile
  • src/libpcp_import/src/exports
  • src/libpcp_import/src/import.c
  • src/libpcp_qmc/src/qmc_desc.cpp
  • src/libpcp_static/src/GNUmakefile
  • src/libpcp_web/src/query.c
  • src/libpcp_web/src/query_parser.y.in
  • src/perl/LogImport/LogImport.pm
  • src/perl/LogImport/LogImport.xs
  • src/perl/MMV/MMV.pm
  • src/perl/MMV/MMV.xs
  • src/perl/PMDA/PMDA.pm
  • src/perl/PMDA/PMDA.xs
  • src/perl/PMDA/cvalue.c
  • src/perl/PMDA/test.pl
  • src/pmdas/sample/help
  • src/pmdas/sample/pmns
  • src/pmdas/sample/src/sample.c
  • src/pmdas/statsd/src/pmda-callbacks.c
  • src/pmdas/statsd/src/pmdastatsd.c
  • src/pmdas/summary/help
  • src/pmdas/summary/summary.pmie
  • src/pmdumptext/pmdumptext.cpp
  • src/pmie/src/dstruct.c
  • src/pmie/src/dstruct.h
  • src/pmie/src/pragmatics.h
  • src/pmie/src/syntax.c
  • src/pminfo/pminfo.c
  • src/pmlogextract/pmlogextract.c
  • src/pmlogger/src/checks.c
  • src/pmlogrewrite/gram.y
  • src/pmlogrewrite/lex.l
  • src/pmlogrewrite/pmlogrewrite.c
  • src/python/pcp/pmapi.py
  • src/python/pcp/pmconfig.py
  • src/python/pmapi.c
  • src/zshrc/_pcp
💤 Files with no reviewable changes (1)
  • src/pmie/src/pragmatics.h

Comment thread src/collectl2pcp/pmdesc.c Outdated
Comment thread src/pmdas/statsd/src/pmdastatsd.c Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/pmdas/sample/help (1)

945-958: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix typo in new temperature help text.

wqater should be water in the newly added temperature descriptions.

Suggested fix
- (defaults to the freezing point of wqater at sea level), but the value
+ (defaults to the freezing point of water at sea level), but the value
🤖 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 `@src/pmdas/sample/help` around lines 945 - 958, Fix the typo "wqater" to
"water" in the three newly added help descriptions for
sample.temperature.celsius, sample.temperature.fahrenheit, and
sample.temperature.kelvin; update the text that reads "(defaults to the freezing
point of wqater at sea level)" to "(defaults to the freezing point of water at
sea level)" so the help entries referencing pmstore(1) are correct and
consistent.
src/pmdas/sample/src/sample.c (2)

2753-2795: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix incorrect fetch conversions for Kelvin and milliwatts.

Two conversion formulas are incorrect: Kelvin offset and milliwatt scaling.

Suggested fix
 	    case 174:	/* sample.temperature.kelvin */
-		atom.f = 237.15 + temperature;
+		atom.f = 273.15 + temperature;
 		break;
@@
 	    case 183:	/* sample.power.milliwatt */
-		atom.f = power * 1000000;
+		atom.f = power * 1000;
 		break;
🤖 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 `@src/pmdas/sample/src/sample.c` around lines 2753 - 2795, The Kelvin
conversion in case 174 and the milliwatt scaling in case 183 are wrong: update
the assignment in case 174 to use 273.15 + temperature (not 237.15), and change
case 183 to set atom.f = power * 1000 (milliwatt = watt * 1e3); verify case 184
(microwatt) is atom.f = power * 1000000 and leave case 182 (watt) and case 181
(kilowatt) unchanged; modify the atom.f assignments for the cases referenced
(case 174, case 183, and confirm case 184) accordingly.

3401-3436: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Store-path unit conversions are reversed for multiple scales.

Several inverse conversions are backwards, so pmStore writes the wrong base values for temperature/voltage/current/power.

Suggested fix
 	    case 174:	/* sample.temperature.kelvin */
-		temperature = av.f + 237.15;
+		temperature = av.f - 273.15;
 		break;
@@
 	    case 176:	/* sample.voltage.millivolt */
-		voltage = av.f * 1000;
+		voltage = av.f / 1000;
 		break;
 	    case 177:	/* sample.voltage.microvolt */
-		voltage = av.f * 1000000;
+		voltage = av.f / 1000000;
 		break;
@@
 	    case 179:	/* sample.current.milliamp */
-		current = av.f * 1000;
+		current = av.f / 1000;
 		break;
 	    case 180:	/* sample.current.microamp */
-		current = av.f * 1000000;
+		current = av.f / 1000000;
 		break;
@@
 	    case 181:	/* sample.power.kilowatt */
-		power = av.f / 1000;
+		power = av.f * 1000;
 		break;
@@
 	    case 183:	/* sample.power.milliwatt */
-		power = av.f * 1000;
+		power = av.f / 1000;
 		break;
 	    case 184:	/* sample.power.microwatt */
-		power = av.f * 1000000;
+		power = av.f / 1000000;
 		break;
🤖 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 `@src/pmdas/sample/src/sample.c` around lines 3401 - 3436, The store-path
conversions are inverted for several scaled units: in the switch cases 173–184
fix the conversion directions so stored base units are correct — keep case 173
(fahrenheit) as Celsius = (av.f - 32) / 1.8, change case 174 (kelvin) to
temperature = av.f - 273.15, for voltage change case 176 (millivolt) to voltage
= av.f / 1000 and case 177 (microvolt) to voltage = av.f / 1000000, for current
change case 179 (milliamp) to current = av.f / 1000 and case 180 (microamp) to
current = av.f / 1000000, and for power change case 181 (kilowatt) to power =
av.f * 1000, case 183 (milliwatt) to power = av.f / 1000 and case 184
(microwatt) to power = av.f / 1000000 so the variables temperature, voltage,
current, and power are converted into the correct base units before storing.
🤖 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/collectl2pcp/GNUmakefile`:
- Around line 37-40: The metrics.c make rule runs "pminfo ... | ./pmdesc >
metrics.c" which can hide failures from pminfo; change the recipe to a guarded
two-step: run pminfo and write to a temporary file, check its exit status, then
invoke ./pmdesc on that temp file and atomically move the output into metrics.c
(or remove the temp on failure). Update the rule that mentions metrics.c, pminfo
and pmdesc in the GNUmakefile to use the temp-file + explicit exit check (or set
SHELL to a shell with pipefail) so a failing pminfo will cause the make recipe
to fail instead of producing an empty/partial metrics.c.

---

Outside diff comments:
In `@src/pmdas/sample/help`:
- Around line 945-958: Fix the typo "wqater" to "water" in the three newly added
help descriptions for sample.temperature.celsius, sample.temperature.fahrenheit,
and sample.temperature.kelvin; update the text that reads "(defaults to the
freezing point of wqater at sea level)" to "(defaults to the freezing point of
water at sea level)" so the help entries referencing pmstore(1) are correct and
consistent.

In `@src/pmdas/sample/src/sample.c`:
- Around line 2753-2795: The Kelvin conversion in case 174 and the milliwatt
scaling in case 183 are wrong: update the assignment in case 174 to use 273.15 +
temperature (not 237.15), and change case 183 to set atom.f = power * 1000
(milliwatt = watt * 1e3); verify case 184 (microwatt) is atom.f = power *
1000000 and leave case 182 (watt) and case 181 (kilowatt) unchanged; modify the
atom.f assignments for the cases referenced (case 174, case 183, and confirm
case 184) accordingly.
- Around line 3401-3436: The store-path conversions are inverted for several
scaled units: in the switch cases 173–184 fix the conversion directions so
stored base units are correct — keep case 173 (fahrenheit) as Celsius = (av.f -
32) / 1.8, change case 174 (kelvin) to temperature = av.f - 273.15, for voltage
change case 176 (millivolt) to voltage = av.f / 1000 and case 177 (microvolt) to
voltage = av.f / 1000000, for current change case 179 (milliamp) to current =
av.f / 1000 and case 180 (microamp) to current = av.f / 1000000, and for power
change case 181 (kilowatt) to power = av.f * 1000, case 183 (milliwatt) to power
= av.f / 1000 and case 184 (microwatt) to power = av.f / 1000000 so the
variables temperature, voltage, current, and power are converted into the
correct base units before storing.
🪄 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: 9cb6f8cb-7d59-4e00-ae85-de3d06fb58cb

📥 Commits

Reviewing files that changed from the base of the PR and between 34fc4ef and 4242a8f.

⛔ Files ignored due to path filters (10)
  • qa/029.out is excluded by !**/*.out
  • qa/139.out is excluded by !**/*.out
  • qa/1665.out is excluded by !**/*.out
  • qa/256.out is excluded by !**/*.out
  • qa/273.out is excluded by !**/*.out
  • qa/483.out is excluded by !**/*.out
  • qa/509.out is excluded by !**/*.out
  • qa/569.out is excluded by !**/*.out
  • qa/662.out is excluded by !**/*.out
  • qa/762.out is excluded by !**/*.out
📒 Files selected for processing (18)
  • man/man1/pmie.1
  • man/man3/pmlookupdesc.3
  • qa/1067.out.1
  • qa/1665
  • qa/1667
  • qa/483
  • qa/archives/extra.0
  • qa/archives/extra.index
  • qa/archives/extra.meta
  • qa/common.check
  • src/collectl2pcp/GNUmakefile
  • src/include/pcp/pmapi.h
  • src/libpcp/src/extraunits.c
  • src/perl/MMV/MMV.pm
  • src/perl/PMDA/PMDA.pm
  • src/pmdas/sample/help
  • src/pmdas/sample/pmns
  • src/pmdas/sample/src/sample.c
✅ Files skipped from review due to trivial changes (3)
  • qa/1067.out.1
  • man/man1/pmie.1
  • man/man3/pmlookupdesc.3

Comment thread src/collectl2pcp/GNUmakefile Outdated
Comment on lines +37 to +40
metrics.c: metrics.h pmdesc.c pmdesc
PCP_DERIVED_CONFIG='' pminfo hinv kernel mem swap network disk filesys \
swapdev rpc nfs nfs3 nfs4 xfs pmda ipc vfs quota tmpfs sysfs proc \
| ./pmdesc > metrics.c
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 23, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify current recipe structure and lack of explicit left-side pipeline failure handling.
nl -ba src/collectl2pcp/GNUmakefile | sed -n '34,42p'
rg -n 'metrics\.c:|pminfo .*\\|\\| ./pmdesc|pipefail' src/collectl2pcp/GNUmakefile

Repository: performancecopilot/pcp

Length of output: 416


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print the relevant section using sed (nl isn't available here)
sed -n '30,50p' src/collectl2pcp/GNUmakefile

echo "----"
# Look for any pipefail / SHELL overrides / .ONESHELL / shell settings
rg -n --hidden -S 'pipefail|SHELL\s*=|SHELLFLAGS|\.ONESHELL|set\s+-o\s+pipefail' src/collectl2pcp/GNUmakefile || true

echo "----"
# Check if this Makefile uses a shell wrapper for recipes
rg -n --hidden -S 'sh\s+-c|/bin/sh|bash|dash' src/collectl2pcp/GNUmakefile || true

Repository: performancecopilot/pcp

Length of output: 536


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print the relevant section using sed (nl isn't available here)
sed -n '30,50p' src/collectl2pcp/GNUmakefile

echo "----"
# Look for any pipefail / SHELL overrides / .ONESHELL / shell settings
rg -n --hidden -S 'pipefail|SHELL\s*=|SHELLFLAGS|\.ONESHELL|set\s+-o\s+pipefail' src/collectl2pcp/GNUmakefile || true

echo "----"
# Check if this Makefile uses a shell wrapper for recipes
rg -n --hidden -S 'sh\s+-c|/bin/sh|bash|dash' src/collectl2pcp/GNUmakefile || true

Repository: performancecopilot/pcp

Length of output: 536


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show header where SHELL/.ONESHELL/SHELLFLAGS might be defined
sed -n '1,120p' src/collectl2pcp/GNUmakefile

echo "----"
# Search for pipefail or shell-related flags/settings in the whole Makefile
rg -n --hidden -S 'pipefail|SHELL\s*=|SHELLFLAGS|\.ONESHELL|set\s+-o\s+pipefail' src/collectl2pcp/GNUmakefile || true

echo "----"
# Show the full metrics.c target rule block
rg -n 'metrics\.c:' -n src/collectl2pcp/GNUmakefile
sed -n '30,60p' src/collectl2pcp/GNUmakefile

Repository: performancecopilot/pcp

Length of output: 2003


Harden metrics.c generation against masked pipeline failures.

The metrics.c rule in src/collectl2pcp/GNUmakefile runs pminfo ... | ./pmdesc > metrics.c as a plain POSIX shell pipeline with no local pipefail/SHELL override shown in the Makefile, so a failing pminfo can be masked and the build may still succeed while regenerating metrics.c from incomplete/empty input. Keep the guarded two-step/temp-file approach in the suggested diff (or otherwise ensure the recipe shell fails when the left side fails).

🤖 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 `@src/collectl2pcp/GNUmakefile` around lines 37 - 40, The metrics.c make rule
runs "pminfo ... | ./pmdesc > metrics.c" which can hide failures from pminfo;
change the recipe to a guarded two-step: run pminfo and write to a temporary
file, check its exit status, then invoke ./pmdesc on that temp file and
atomically move the output into metrics.c (or remove the temp on failure).
Update the rule that mentions metrics.c, pminfo and pmdesc in the GNUmakefile to
use the temp-file + explicit exit check (or set SHELL to a shell with pipefail)
so a failing pminfo will cause the make recipe to fail instead of producing an
empty/partial metrics.c.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@coderabbitai metrics.c is checked into the git tree, so this rule is not used in normal package building. The rule is used by developers to manually generate an updated metrics.c in an environment where pminfo is installed and any errors will be visible to the developer.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@kmcdonell, that makes complete sense — the comment in the Makefile itself even says # only used to remake metrics.c which is in the git repo. Since this is a developer-only convenience target (not invoked during normal package builds) and any pminfo failures will be immediately visible to the developer running it manually, the pipeline-failure hardening concern doesn't apply here. Thanks for the clarification!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/collectl2pcp/GNUmakefile (1)

40-42: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Harden metrics.c generation against masked pipeline failures.

The pipeline pminfo ... | ./pmdesc > metrics.c can silently hide a pminfo failure because Make's default shell does not enable pipefail, allowing an empty or partial metrics.c to be generated from incomplete input.

🔒 Proposed fix using temp file and explicit status check
 metrics.c: metrics.h pmdesc.c
-	PCP_DERIVED_CONFIG='' pminfo hinv kernel mem swap network disk filesys \
-	swapdev rpc nfs nfs3 nfs4 xfs pmda ipc vfs quota tmpfs sysfs proc \
-	| ./pmdesc > metrics.c
+	`@TMP`=$$(mktemp metrics.tmp.XXXXXX) && \
+	PCP_DERIVED_CONFIG='' pminfo hinv kernel mem swap network disk filesys \
+	swapdev rpc nfs nfs3 nfs4 xfs pmda ipc vfs quota tmpfs sysfs proc > "$$TMP" && \
+	./pmdesc < "$$TMP" > metrics.c && rm -f "$$TMP" || { rm -f "$$TMP"; exit 1; }
🤖 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 `@src/collectl2pcp/GNUmakefile` around lines 40 - 42, The current rule that
runs "PCP_DERIVED_CONFIG='' pminfo ... | ./pmdesc > metrics.c" can silently
produce a bad metrics.c if pminfo fails because the shell doesn't enable
pipefail; change the recipe to first run pminfo and capture its exit status
(e.g., run PCP_DERIVED_CONFIG='' pminfo ... > tmp && check the exit code), only
feed the successful output into ./pmdesc to produce metrics.c, and remove the
temp file; alternatively, run the whole pipeline under a shell with pipefail
enabled (e.g., use bash -o pipefail -c "...") so that failures in pminfo cause
the step to fail and prevent writing a partial metrics.c.
🤖 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.

Duplicate comments:
In `@src/collectl2pcp/GNUmakefile`:
- Around line 40-42: The current rule that runs "PCP_DERIVED_CONFIG='' pminfo
... | ./pmdesc > metrics.c" can silently produce a bad metrics.c if pminfo fails
because the shell doesn't enable pipefail; change the recipe to first run pminfo
and capture its exit status (e.g., run PCP_DERIVED_CONFIG='' pminfo ... > tmp &&
check the exit code), only feed the successful output into ./pmdesc to produce
metrics.c, and remove the temp file; alternatively, run the whole pipeline under
a shell with pipefail enabled (e.g., use bash -o pipefail -c "...") so that
failures in pminfo cause the step to fail and prevent writing a partial
metrics.c.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f440879-d60f-48cb-a3f1-4ae23b125e98

📥 Commits

Reviewing files that changed from the base of the PR and between 4242a8f and 60c3955.

📒 Files selected for processing (3)
  • src/collectl2pcp/GNUmakefile
  • src/collectl2pcp/metrics.c
  • src/collectl2pcp/pmdesc.c

@kmcdonell
Copy link
Copy Markdown
Member Author

@coderabbitai
At @qa/common.check around lines 2230 - 2231 - this is OK because if output is generated on stderr that is unexpected and we want the test to "fail".

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Tip

For best results, initiate chat on the files or code changes.

@kmcdonell Thanks for the clarification! That makes sense — any unexpected stderr output at that point in qa/common.check (around lines 2230–2231) is a deliberate signal that something went wrong, and letting it propagate causes the test to fail as intended.


✏️ Learnings added
Learnt from: kmcdonell
URL: https://github.com/performancecopilot/pcp/pull/2598

Timestamp: 2026-05-25T05:49:38.615Z
Learning: In `qa/common.check` around lines 2230–2231, stderr output flowing through (rather than being suppressed) is intentional: unexpected stderr output at that point is meant to cause the QA test to fail, serving as a failure signal.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread qa/src/badUnitsStr_r.c
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@coderabbitai All of the C source in the qa/src directory is used to build QA applications that are not intended for general use, hence we can tolerate loose error checking, for example checking the result from malloc() is not necessarily required for these applications, but is required elsewhere in the PCP code base.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@kmcdonell, understood — thank you for clarifying the QA-specific coding conventions. I'll keep this in mind for future reviews of code in qa/src/.


✏️ Learnings added
Learnt from: kmcdonell
Repo: performancecopilot/pcp PR: 2598
File: qa/src/badUnitsStr_r.c:1-1
Timestamp: 2026-05-25T21:44:04.850Z
Learning: In the PCP repository, all C source files under the `qa/src/` directory are used to build QA applications that are not intended for general use. As a result, loose error checking is acceptable in these files — for example, checking the return value of `malloc()` is NOT required in `qa/src/` code, even though it IS required elsewhere in the PCP codebase.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@coderabbitai For derived metrics automatic scale conversion for units in the base dimensions of Space, Time and Count have always been supported, but for extra units the complexity of the scale conversions is sufficient to (at least for the moment) mean we've opted to not support automatic scale conversion here for extra units like Temperature, Voltage, Current and Power. There are existing scaling primitives in the derived metric specification language that allow explicit scale conversions for expressions involving extra units.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@kmcdonell, thanks for the clarification. That makes sense — the conversion formulas for extra units like Temperature (°C↔°F↔K) are non-linear affine transformations (not simple multiplicative scale factors), making them fundamentally incompatible with the existing multiplicative scale-conversion machinery used for Space, Time, and Count dimensions. The rescale() function and related primitives in the derived metric language provide the necessary hook for explicit conversions in these cases.


✏️ Learnings added
Learnt from: kmcdonell
Repo: performancecopilot/pcp PR: 2598
File: src/libpcp/src/derive_parser.y.in:1-1
Timestamp: 2026-05-25T21:50:29.312Z
Learning: In PCP's derived metric parser (`src/libpcp/src/derive_parser.y.in`), automatic scale conversion (in `map_units()`) is intentionally supported only for the base dimensions Space, Time, and Count. Automatic scale conversion is NOT supported for extra units (Temperature, Voltage, Current, Power) because their conversion formulas (e.g., °C↔°F↔K) are non-linear/affine and incompatible with the multiplicative scale-conversion machinery. Users of derived metrics involving extra units must use the explicit scaling primitives (e.g., `rescale()`) available in the derived metric specification language.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@coderabbitai The summary PMDA uses pmie(1) to evaluate these expressions and pmie traps and gracefully handles any attempt to divide by zero, so there is no issue here with (count_inst $netio > 0) potentially evaluating to zero.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@kmcdonell, thanks for the clarification! That makes sense — since pmie(1) traps and gracefully handles division-by-zero cases, the expression is safe as written.


✏️ Learnings added
Learnt from: kmcdonell
Repo: performancecopilot/pcp PR: 2598
File: src/pmdas/summary/summary.pmie:1-1
Timestamp: 2026-05-25T21:54:10.595Z
Learning: In `src/pmdas/summary/summary.pmie` (PCP project), expressions are evaluated by `pmie(1)`. The `pmie` evaluator traps division by zero and handles it gracefully, so expressions like `sum_inst ($netio) / (count_inst $netio > 0)` are safe even when the divisor `(count_inst $netio > 0)` evaluates to zero.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

kmcdonell added 2 commits May 26, 2026 10:27
…opilot#2598.

Some are cosmetic, some are grammar and spelling, some are errors
in the sample PMDA calculations for extra units and some are minor
tweaks in code places touched by the extra units changes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
src/libpcp_web/src/query.c (1)

4776-4816: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't reject extra-unit ÷ dimensionless expressions.

This guard now fails any division where only one operand carries an extra unit. That contradicts the new valid corner cases added in qa/1667 (sample.temperature.fahrenheit / sampledso.long.ten and sampledso.long.hundred / sample.temperature.fahrenheit), so /series evaluation will reject expressions the rest of this PR now accepts.

🤖 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 `@src/libpcp_web/src/query.c` around lines 4776 - 4816, The current guard
rejects any division where one operand has an extra unit and the other doesn't;
update the check so it only errors when both operands have an extra unit but
those extraUnit or extraScale values differ. Concretely, replace the if
condition that references l_units.extraUnit, r_units.extraUnit,
l_units.extraScale and r_units.extraScale so it requires both extraUnit != 0
(e.g. l_units.extraUnit != 0 && r_units.extraUnit != 0) before comparing
equality; leave the rest of the division path (series_calculate_order_binary /
calculate_slash and subsequent large_units handling) unchanged.
src/libpcp/src/units.c (1)

1340-1347: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject a second extra-unit token instead of overwriting the first.

This loop now tries __pmParseExtraUnits() on every token, but pmUnits only has one extraUnit slot. Inputs like temperature (C) voltage (V) will parse successfully and silently keep only the later unit. Please fail with PM_ERR_CONV once an extra unit has already been set for the current part.

🤖 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 `@src/libpcp/src/units.c` around lines 1340 - 1347, The parser currently calls
__pmParseExtraUnits repeatedly and will overwrite an existing extra unit; change
the loop so that before invoking __pmParseExtraUnits you check whether the
current output struct (out) already has an extra unit set (e.g., out->extraUnit
or equivalent field in pmUnits) and if so return PM_ERR_CONV to fail the parse;
otherwise call __pmParseExtraUnits as before. Ensure the check is placed
immediately before the call to __pmParseExtraUnits in the loop that advances
ptr.
src/libpcp/src/extraunits.c (2)

231-271: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the lazy domain-cache initialization.

known_domain[] and onetrip are written without synchronization here. Two threads entering __pmCheckDesc() together can observe a partially populated cache and produce inconsistent validation results. Please switch this to a once-only init path (or protect it with a lock).

🤖 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 `@src/libpcp/src/extraunits.c` around lines 231 - 271, The lazy init of
known_domain[] and onetrip in __pmCheckDesc is racy; make the initialization run
once and be thread-safe by extracting the file-read logic into a dedicated init
function (e.g. init_known_domains) and invoking it via a thread-safe once
mechanism or protected by a static mutex; for POSIX use a static pthread_once_t
once_control and call pthread_once(&once_control, init_known_domains) from
__pmCheckDesc (or alternatively lock around the existing onetrip checks with a
static pthread_mutex_t) so that known_domain[] is fully populated before any
thread proceeds and still ensure known_domain[DYNAMIC_PMID] is set in the init
function.

408-443: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Accept negative extraUnit when validating divisor units.

The rest of this change set uses a negative extraUnit to represent an extra unit in the denominator, but this validator only matches positive PM_UNIT_* values. That will reject valid descriptors such as / voltage (mV) once they flow through pmDesc checks. Validate against abs(dp->units.extraUnit) and keep checking extraScale against that unit family.

🤖 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 `@src/libpcp/src/extraunits.c` around lines 408 - 443, The validator currently
only matches positive dp->units.extraUnit against extra[u].type; change the
comparisons to use the absolute value (e.g. use abs(dp->units.extraUnit) when
searching the extra[] array and when selecting the unit family for extraScale
validation) so negative extraUnit values (denominator units) are accepted, but
continue to check dp->units.extraScale against the scale table for the matched
extra[u] entry; keep the original dp->units.extraUnit value for any error
messages if you want to show the signed value.
src/perl/PMDA/cvalue.c (1)

74-86: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid type-punning pmUnits through unsigned int *
*(unsigned int *)&forw / *(unsigned int *)&back dereferences an unsigned int * that aliases a pmUnits bitfield struct, which is undefined behavior under C strict-aliasing (and can produce incorrect debug output). Copy the bytes into a same-sized integer with memcpy before formatting.

Proposed fix
+#include <string.h>
+#include <stdint.h>
+
 void
 extraunits(void)
 {
     pmUnits forw;
     pmUnits back;
+    uint32_t raw;
 
     memset(&forw, 0, sizeof(forw));
     memset(&back, 0, sizeof(back));
@@
-    printf("extra pmUnits: 1,2,3,4,5,6,7,8 = %d %08x\n", *(unsigned int *)&forw, *(unsigned int *)&forw);
-    printf("extra pmUnits: 8,7,6,5,4,3,2,1 = %d %08x\n", *(unsigned int *)&back, *(unsigned int *)&back);
+    memcpy(&raw, &forw, sizeof(raw));
+    printf("extra pmUnits: 1,2,3,4,5,6,7,8 = %u %08x\n", raw, raw);
+    memcpy(&raw, &back, sizeof(raw));
+    printf("extra pmUnits: 8,7,6,5,4,3,2,1 = %u %08x\n", raw, raw);
 }
🤖 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 `@src/perl/PMDA/cvalue.c` around lines 74 - 86, The code is type-punning the
pmUnits bitfield (forw/back) by casting to unsigned int* and dereferencing,
which violates strict-aliasing; instead, copy the bytes of the pmUnits struct
into a same-sized integer via memcpy and print that integer. Locate the pmUnits
instances forw and back in cvalue.c and replace uses of *(unsigned int *)&forw /
*(unsigned int *)&back with a local uint32_t (or unsigned int) variable,
memcpy(&var, &forw, sizeof var) / memcpy(&var, &back, sizeof var), then pass var
to printf (using the existing %08x and %u formats as appropriate). Ensure the
integer chosen is the same size as pmUnits (use sizeof to confirm).
qa/src/units-parse.c (1)

172-178: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep one zero-scale iteration for zero dimensions.

When ds, dt, or dc is 0, these bounds make the corresponding scale loop execute zero times, so scan silently skips dimensionless and partial-dimension tuples. That leaves a large hole in the round-trip coverage for the parser changes in this PR.

🤖 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/src/units-parse.c` around lines 172 - 178, When a dimension variable is
zero the inner scale loops currently run zero times; change their bounds so they
perform one zero-scale iteration: replace the ss loop bound (ds ? 16 : 0) with
(ds ? 16 : 4) and the st loop bound (dt ? 16 : 0) with (dt ? 16 : 4) so ss/st
yield a single ss==0 or st==0 when ds/dt==0, and adjust the sc loop to start at
0 when dc==0 and run once by changing its header from for (sc = -8; sc < (dc ? 8
: -8); sc +=4) to for (sc = (dc ? -8 : 0); sc < (dc ? 8 : 4); sc +=4) so sc
yields a single zero-scale iteration when dc==0; this preserves existing
iteration ranges when ds/dt/dc are nonzero and ensures scan covers
dimensionless/partial tuples.
src/python/pmapi.c (1)

922-932: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix PyArg_ParseTupleAndKeywords format/type mismatch in pmUnits_int (src/python/pmapi.c).

pmUnits_int uses the format string "IIIIIIII" where "I" is documented as converting to a C unsigned int destination. The code passes &dimSpace, &dimTime, &dimCount, and &extraUnit as int*, which violates the C-API destination-type contract; update the destination variables (and/or the format string) to match unsigned int (or whatever the underlying pmUnits fields actually are).

🤖 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 `@src/python/pmapi.c` around lines 922 - 932, The PyArg_ParseTupleAndKeywords
call in pmUnits_int uses format "IIIIIIII" (which expects unsigned int
destinations) but passes int* for dimSpace, dimTime, dimCount, extraUnit; change
the variable types (dimSpace, dimTime, dimCount, extraUnit) to unsigned int to
match the "I" format (or alternatively change the format to use "i" if you
prefer signed ints), and ensure the prototype and any uses of pmUnits_int/these
fields are updated accordingly; update declarations near the top of pmUnits_int
and keep scaleSpace, scaleTime, scaleCount, extraScale as unsigned int as
already declared so all argument types match the PyArg_ParseTupleAndKeywords
call.
🤖 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/pmie/src/dstruct.c`:
- Around line 859-870: The condition checking for "has some units" is using
x->units.extraScale instead of the correct flag x->units.extraUnit, so rate()
can skip proper handling of extra-unit metrics; modify the conditional in the
block that adjusts time units (the code referencing x->units.dimSpace, dimTime,
dimCount, extraScale) to check x->units.extraUnit rather than extraScale, and
ensure subsequent adjustments to x->units.dimTime and x->units.scaleTime remain
unchanged so extra-unit metrics get rate units applied correctly.

In `@src/pmlogrewrite/lex.l`:
- Around line 139-147: The <extra> lexer rule only matches uppercase names
([A-Z][A-Z_]*) so new mixed-case scale macros (e.g., PM_POWER_kW, PM_VOLTAGE_mV)
are not recognized; update the pattern in the <extra> rules to accept mixed-case
and digits (e.g., [A-Za-z][A-Za-z0-9_]*) so yytext is duplicated into yylval.str
and the existing logic using comma_count, in_extra, BEGIN(metric)/BEGIN(units)
and return TOK_MACRO continues to run; make the same pattern change for the
other occurrence noted (around line 258) so both places recognize mixed-case
macros.

---

Outside diff comments:
In `@qa/src/units-parse.c`:
- Around line 172-178: When a dimension variable is zero the inner scale loops
currently run zero times; change their bounds so they perform one zero-scale
iteration: replace the ss loop bound (ds ? 16 : 0) with (ds ? 16 : 4) and the st
loop bound (dt ? 16 : 0) with (dt ? 16 : 4) so ss/st yield a single ss==0 or
st==0 when ds/dt==0, and adjust the sc loop to start at 0 when dc==0 and run
once by changing its header from for (sc = -8; sc < (dc ? 8 : -8); sc +=4) to
for (sc = (dc ? -8 : 0); sc < (dc ? 8 : 4); sc +=4) so sc yields a single
zero-scale iteration when dc==0; this preserves existing iteration ranges when
ds/dt/dc are nonzero and ensures scan covers dimensionless/partial tuples.

In `@src/libpcp_web/src/query.c`:
- Around line 4776-4816: The current guard rejects any division where one
operand has an extra unit and the other doesn't; update the check so it only
errors when both operands have an extra unit but those extraUnit or extraScale
values differ. Concretely, replace the if condition that references
l_units.extraUnit, r_units.extraUnit, l_units.extraScale and r_units.extraScale
so it requires both extraUnit != 0 (e.g. l_units.extraUnit != 0 &&
r_units.extraUnit != 0) before comparing equality; leave the rest of the
division path (series_calculate_order_binary / calculate_slash and subsequent
large_units handling) unchanged.

In `@src/libpcp/src/extraunits.c`:
- Around line 231-271: The lazy init of known_domain[] and onetrip in
__pmCheckDesc is racy; make the initialization run once and be thread-safe by
extracting the file-read logic into a dedicated init function (e.g.
init_known_domains) and invoking it via a thread-safe once mechanism or
protected by a static mutex; for POSIX use a static pthread_once_t once_control
and call pthread_once(&once_control, init_known_domains) from __pmCheckDesc (or
alternatively lock around the existing onetrip checks with a static
pthread_mutex_t) so that known_domain[] is fully populated before any thread
proceeds and still ensure known_domain[DYNAMIC_PMID] is set in the init
function.
- Around line 408-443: The validator currently only matches positive
dp->units.extraUnit against extra[u].type; change the comparisons to use the
absolute value (e.g. use abs(dp->units.extraUnit) when searching the extra[]
array and when selecting the unit family for extraScale validation) so negative
extraUnit values (denominator units) are accepted, but continue to check
dp->units.extraScale against the scale table for the matched extra[u] entry;
keep the original dp->units.extraUnit value for any error messages if you want
to show the signed value.

In `@src/libpcp/src/units.c`:
- Around line 1340-1347: The parser currently calls __pmParseExtraUnits
repeatedly and will overwrite an existing extra unit; change the loop so that
before invoking __pmParseExtraUnits you check whether the current output struct
(out) already has an extra unit set (e.g., out->extraUnit or equivalent field in
pmUnits) and if so return PM_ERR_CONV to fail the parse; otherwise call
__pmParseExtraUnits as before. Ensure the check is placed immediately before the
call to __pmParseExtraUnits in the loop that advances ptr.

In `@src/perl/PMDA/cvalue.c`:
- Around line 74-86: The code is type-punning the pmUnits bitfield (forw/back)
by casting to unsigned int* and dereferencing, which violates strict-aliasing;
instead, copy the bytes of the pmUnits struct into a same-sized integer via
memcpy and print that integer. Locate the pmUnits instances forw and back in
cvalue.c and replace uses of *(unsigned int *)&forw / *(unsigned int *)&back
with a local uint32_t (or unsigned int) variable, memcpy(&var, &forw, sizeof
var) / memcpy(&var, &back, sizeof var), then pass var to printf (using the
existing %08x and %u formats as appropriate). Ensure the integer chosen is the
same size as pmUnits (use sizeof to confirm).

In `@src/python/pmapi.c`:
- Around line 922-932: The PyArg_ParseTupleAndKeywords call in pmUnits_int uses
format "IIIIIIII" (which expects unsigned int destinations) but passes int* for
dimSpace, dimTime, dimCount, extraUnit; change the variable types (dimSpace,
dimTime, dimCount, extraUnit) to unsigned int to match the "I" format (or
alternatively change the format to use "i" if you prefer signed ints), and
ensure the prototype and any uses of pmUnits_int/these fields are updated
accordingly; update declarations near the top of pmUnits_int and keep
scaleSpace, scaleTime, scaleCount, extraScale as unsigned int as already
declared so all argument types match the PyArg_ParseTupleAndKeywords call.
🪄 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: 11940177-a60d-46ef-b254-2ac30e188211

📥 Commits

Reviewing files that changed from the base of the PR and between 4242a8f and 626957e.

⛔ Files ignored due to path filters (7)
  • qa/1418.out is excluded by !**/*.out
  • qa/1666.out is excluded by !**/*.out
  • qa/1667.out is excluded by !**/*.out
  • qa/273.out is excluded by !**/*.out
  • qa/369.out is excluded by !**/*.out
  • qa/569.out is excluded by !**/*.out
  • qa/670.out is excluded by !**/*.out
📒 Files selected for processing (25)
  • man/man3/pmconvscale.3
  • qa/1667
  • qa/new
  • qa/src/check_import.c
  • qa/src/pdu-gadget.c
  • qa/src/units-parse.c
  • src/collectl2pcp/GNUmakefile
  • src/collectl2pcp/metrics.c
  • src/collectl2pcp/pmdesc.c
  • src/libpcp/src/derive_parser.y.in
  • src/libpcp/src/extraunits.c
  • src/libpcp/src/units.c
  • src/libpcp_import/src/import.c
  • src/libpcp_qmc/src/qmc_desc.cpp
  • src/libpcp_web/src/query.c
  • src/perl/PMDA/cvalue.c
  • src/perl/PMDA/test.pl
  • src/pmdas/sample/help
  • src/pmdas/sample/src/sample.c
  • src/pmdas/statsd/src/dict-callbacks.c
  • src/pmdas/statsd/src/pmda-callbacks.c
  • src/pmdas/summary/help
  • src/pmie/src/dstruct.c
  • src/pmlogrewrite/lex.l
  • src/python/pmapi.c
✅ Files skipped from review due to trivial changes (3)
  • src/pmdas/statsd/src/dict-callbacks.c
  • man/man3/pmconvscale.3
  • src/pmdas/sample/help

Comment thread src/pmie/src/dstruct.c Outdated
Comment thread src/pmlogrewrite/lex.l Outdated
Comment on lines +139 to +147
<extra>[A-Z][A-Z_]* { yylval.str = dupstr(yytext, 0);
if (comma_count == 7) {
in_extra = 0;
BEGIN(metric);
}
else
BEGIN(units);
return TOK_MACRO;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

<extra> won't match the new mixed-case scale macros.

The new extra-unit scale names in this PR include lowercase characters (PM_POWER_kW, PM_VOLTAGE_mV, PM_CURRENT_uA, ...), but both <extra> rules only accept [A-Z][A-Z_]*. In that state these tokens fall through as generic names instead of TOK_MACRO, so valid extraunits assignments will fail to parse.

Also applies to: 258-258

🤖 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 `@src/pmlogrewrite/lex.l` around lines 139 - 147, The <extra> lexer rule only
matches uppercase names ([A-Z][A-Z_]*) so new mixed-case scale macros (e.g.,
PM_POWER_kW, PM_VOLTAGE_mV) are not recognized; update the pattern in the
<extra> rules to accept mixed-case and digits (e.g., [A-Za-z][A-Za-z0-9_]*) so
yytext is duplicated into yylval.str and the existing logic using comma_count,
in_extra, BEGIN(metric)/BEGIN(units) and return TOK_MACRO continues to run; make
the same pattern change for the other occurrence noted (around line 258) so both
places recognize mixed-case macros.

Comment thread src/perl/PMDA/cvalue.c
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@coderabbitai the src/perl/PMDA/cvalue.c file is only used for QA and is not used or packaged in a regular PCP build ... we can afford to be a bit loose here with the way the pmUnits bit fields and an int are aliased.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@kmcdonell, understood — thanks for the clarification. Since src/perl/PMDA/cvalue.c is purely a QA verification helper (used by test.pl via make test) and is never packaged in a regular PCP build, the pmUnits-to-int aliasing via pointer cast is entirely acceptable in this context. I'll keep that in mind for future reviews.


✏️ Learnings added
Learnt from: kmcdonell
Repo: performancecopilot/pcp PR: 2598
File: src/perl/PMDA/cvalue.c:1-1
Timestamp: 2026-05-27T21:17:11.296Z
Learning: In the PCP repository, `src/perl/PMDA/cvalue.c` is a QA-only file used solely by `src/perl/PMDA/test.pl` via `make test`. It is not compiled or packaged as part of a regular PCP build. The file intentionally aliases a `pmUnits` bitfield struct to `unsigned int` via pointer cast (e.g., `*(unsigned int *)&forw`) to verify bitfield layout from the Perl binding side. This type-punning is acceptable in this QA-only context and should not be flagged as a portability or strict-aliasing concern during code review.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Also fixes prior error ... the existing scaleCount is signed,
not unsigned.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/libpcp_web/src/query.c (1)

4781-4816: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve extra-scale metadata when dividing with a single extra-unit operand.

On Line [4816], forcing large_units.extraScale = 0 drops valid scale info when the result still has non-zero extraUnit (e.g., extra-unit/sec or 1/extra-unit), leading to incorrect output units metadata.

Proposed fix
-    large_units.extraUnit = l_units.extraUnit - r_units.extraUnit;
-    large_units.extraScale = 0;
+    large_units.extraUnit = l_units.extraUnit - r_units.extraUnit;
+    if (large_units.extraUnit == 0) {
+	large_units.extraScale = 0;
+    } else if (l_units.extraUnit != 0 && r_units.extraUnit == 0) {
+	large_units.extraScale = l_units.extraScale;
+    } else if (l_units.extraUnit == 0 && r_units.extraUnit != 0) {
+	large_units.extraScale = r_units.extraScale;
+    }
🤖 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 `@src/libpcp_web/src/query.c` around lines 4781 - 4816, The code zeroes
large_units.extraScale unconditionally which drops valid scaling when extraUnit
remains non-zero after division; change the assignment in the division result
construction so extraScale is computed from operand scales (e.g. subtract right
from left) and only cleared when resulting extraUnit == 0. Update the block that
sets large_units.extraUnit/extraScale (used after series_calculate_order_binary
/ calculate_slash) to set large_units.extraScale = l_units.extraScale -
r_units.extraScale (and if large_units.extraUnit == 0 then set extraScale = 0).
🤖 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.

Outside diff comments:
In `@src/libpcp_web/src/query.c`:
- Around line 4781-4816: The code zeroes large_units.extraScale unconditionally
which drops valid scaling when extraUnit remains non-zero after division; change
the assignment in the division result construction so extraScale is computed
from operand scales (e.g. subtract right from left) and only cleared when
resulting extraUnit == 0. Update the block that sets
large_units.extraUnit/extraScale (used after series_calculate_order_binary /
calculate_slash) to set large_units.extraScale = l_units.extraScale -
r_units.extraScale (and if large_units.extraUnit == 0 then set extraScale = 0).

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 7da7a595-7f52-42c6-bdfc-7fccb0be905e

📥 Commits

Reviewing files that changed from the base of the PR and between a1ca90c and 2ebc275.

⛔ Files ignored due to path filters (1)
  • qa/670.out is excluded by !**/*.out
📒 Files selected for processing (12)
  • qa/670
  • qa/src/units-parse.c
  • src/include/pcp/import.h
  • src/libpcp/src/check-statics
  • src/libpcp/src/extraunits.c
  • src/libpcp/src/internal.h
  • src/libpcp/src/lock.c
  • src/libpcp/src/units.c
  • src/libpcp_import/src/import.c
  • src/libpcp_web/src/query.c
  • src/perl/LogImport/LogImport.xs
  • src/python/pmapi.c
✅ Files skipped from review due to trivial changes (1)
  • src/libpcp/src/lock.c
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/libpcp/src/internal.h
  • src/libpcp/src/check-statics
  • src/perl/LogImport/LogImport.xs
  • src/libpcp_import/src/import.c
  • src/libpcp/src/units.c
  • src/python/pmapi.c
  • qa/src/units-parse.c
  • src/libpcp/src/extraunits.c

kmcdonell added 2 commits May 28, 2026 10:02
@coderabbitai pointed out that for division the
extra-unit/sec and 1/extra-unit cases were NQR.
Thanks to Marko for this patch.

Also a new qa/1668.
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.

1 participant