Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion man/man1/pmseries.1
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
\f3pmseries\f1 \- display information about performance metric timeseries
.SH SYNOPSIS
\fBpmseries\fR
[\fB\-adFiIlLmMnqsStvV?\fR]
[\fB\-adFGiIlLmMnNqsStvV?\fR]
[\fB\-c\fR \fIconfig\fR]
[\f3\-D\f1 \f2debug\f1]
[\fB\-g\fR \fIpattern\fR]
Expand Down Expand Up @@ -745,6 +745,36 @@ Connect to the key-value server at
.IR host ,
rather than the one the localhost.
.TP
\fB\-G\fR, \fB\-\-gc\fR
Scan for expired time series and remove their ancillary data from the
key-value cluster.
A series is considered expired when its
.B pcp:values:series:
stream key no longer exists (i.e. the
.B stream.expire
TTL has elapsed without new data being ingested).
The following keys are removed for each expired series:
.B pcp:desc:series:,
.B pcp:metric.name:series:,
.B pcp:instances:series:,
.B pcp:labelvalue:series:,
and
.B pcp:labelflags:series:.
The series identifier is also removed from the forward-lookup sets
.B pcp:series:metric.name:,
.B pcp:series:inst.name:,
.B pcp:series:label.<name>.value:,
and
.B pcp:series:context.name:.
Orphaned
.B pcp:inst:series:
hashes are cleaned up when their associated instance-name set becomes empty.
Comment on lines +748 to +771

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Document the current single-node GC scope.

Lines 749-750 promise cleanup across the “key-value cluster,” but this implementation currently scans only the first key-server node. State that limitation so operators do not assume a cluster-wide cleanup completed.

🤖 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/man1/pmseries.1` around lines 748 - 771, Update the --gc documentation
near its key-value storage description to state that garbage collection
currently scans only the first key-server node, rather than implying
cluster-wide cleanup. Preserve the existing descriptions of expired-series and
orphaned-data removal.

.TP
\fB\-N\fR, \fB\-\-dryrun\fR
Used with
.BR \-\-gc :
log which series would be removed without actually deleting anything.
.TP
\fB\-L\fR, \fB\-\-load\fR
Load timeseries metadata and data into the key-value cluster.
.TP
Expand Down
119 changes: 119 additions & 0 deletions qa/1790
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/bin/sh
# PCP QA Test No. 1790
# Exercise pmseries --gc (garbage collection) and --dryrun options.
#
# Copyright (c) 2026 Red Hat. All Rights Reserved.
#

seq=`basename $0`
echo "QA output created by $seq"

# get standard environment, filters and checks
. ./common.product
. ./common.filter
. ./common.check
. ./common.keys

_check_series

_cleanup()
{
[ -n "$key_server_port" ] && $keys_cli -p $key_server_port shutdown
_restore_config $PCP_SYSCONF_DIR/pmseries
cd $here
$sudo rm -rf $tmp $tmp.*
}

status=1 # failure is the default!
key_server_port=`_find_free_port`
options="-p $key_server_port"

trap "_cleanup; exit \$status" 0 1 2 3 15

_filter_source()
{
sed \
-e "s,$here,PATH,g" \
#end
}

# real QA test starts here
_save_config $PCP_SYSCONF_DIR/pmseries
$sudo rm -f $PCP_SYSCONF_DIR/pmseries/*

echo "Start test key server ..."
$key_server --port $key_server_port --save "" > $tmp.keys 2>&1 &
_check_key_server_ping $key_server_port
_check_key_server $key_server_port
echo

_check_key_server_version $key_server_port

echo "== Load test archive"
pmseries $options --load "{source.path: \"$here/archives/viewqa1\"}" | _filter_source

echo
echo "== GC on live data (all streams present)"
pmseries $options --gc

echo
echo "== Count series keys before expiry"
ndesc=`$keys_cli -p $key_server_port keys 'pcp:desc:series:*' | wc -l`
nvals=`$keys_cli -p $key_server_port keys 'pcp:values:series:*' | wc -l`
echo "$ndesc desc keys, $nvals value keys"

echo
echo "== Simulate expiry: delete 3 value streams"
$keys_cli -p $key_server_port keys 'pcp:values:series:*' | sort | head -3 > $tmp.expired
while read key; do
$keys_cli -p $key_server_port del "$key" > /dev/null
done < $tmp.expired
nvals=`$keys_cli -p $key_server_port keys 'pcp:values:series:*' | wc -l`
echo "$nvals value keys remain"

echo
echo "== GC dryrun (should report but not delete)"
pmseries $options --gc --dryrun > $tmp.dryrun 2>&1
ndryrun=`grep -c 'GC dryrun:' $tmp.dryrun`
echo "dryrun reported $ndryrun series"
grep 'GC dryrun:' $tmp.dryrun | sed 's/pmseries: \[Info\] //' | sort

echo
echo "== Verify dryrun preserved metadata keys"
ndesc=`$keys_cli -p $key_server_port keys 'pcp:desc:series:*' | wc -l`
echo "$ndesc desc keys (should be 11)"

echo
echo "== Actual GC (should clean 3 series)"
pmseries $options --gc

echo
echo "== Verify metadata keys removed after GC"
ndesc=`$keys_cli -p $key_server_port keys 'pcp:desc:series:*' | wc -l`
echo "$ndesc desc keys remain (should be 8)"
nmetric=`$keys_cli -p $key_server_port keys 'pcp:metric.name:series:*' | wc -l`
echo "$nmetric metric.name keys remain (should be 8)"

echo
echo "== Second GC (should find nothing to clean)"
pmseries $options --gc

echo
echo "== Error: --gc with extra query argument"
pmseries $options --gc 'kernel.all.load' 2>&1 | grep 'error'

echo
echo "== Error: --gc with extra series ID argument"
pmseries $options --gc 'da4a7a6fb41c07daa1e5d2a6acf66281a8c89e35' 2>&1 | grep 'error'

echo
echo "== Error: --dryrun without --gc"
pmseries $options --dryrun 2>&1 | grep 'error'

echo
echo "== Error: --gc with --load"
pmseries $options --gc --load "{source.path: \"$here/archives/viewqa1\"}" 2>&1 | grep 'error'

# success, all done
status=0
exit
47 changes: 47 additions & 0 deletions qa/1790.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
QA output created by 1790
Start test key server ...
PING
PONG

== Load test archive
pmseries: [Info] processed 151 archive records from PATH/archives/viewqa1

== GC on live data (all streams present)
pmseries: [Info] GC: 11 series scanned, 0 cleaned

== Count series keys before expiry
11 desc keys, 11 value keys

== Simulate expiry: delete 3 value streams
8 value keys remain

== GC dryrun (should report but not delete)
dryrun reported 3 series
GC dryrun: 03fe155a8a42dccd8534859a6cc9e3252a127099 (1 metric names, 0 instances, 1 labels)
GC dryrun: 1107fbb3f8415b6c5712cd62b2580fc23b9b8d59 (1 metric names, 1 instances, 0 labels)
GC dryrun: 55ba4725fb2dc0ddb1765366ff25f68c8a6efa44 (1 metric names, 1 instances, 0 labels)

== Verify dryrun preserved metadata keys
11 desc keys (should be 11)

== Actual GC (should clean 3 series)
pmseries: [Info] GC: 11 series scanned, 3 cleaned

== Verify metadata keys removed after GC
8 desc keys remain (should be 8)
8 metric.name keys remain (should be 8)

== Second GC (should find nothing to clean)
pmseries: [Info] GC: 8 series scanned, 0 cleaned

== Error: --gc with extra query argument
pmseries: error - --gc does not accept extra arguments

== Error: --gc with extra series ID argument
pmseries: error - --gc does not accept extra arguments

== Error: --dryrun without --gc
pmseries: error - --dryrun requires --gc

== Error: --gc with --load
pmseries: error - cannot combine --gc with other operation options
4 changes: 2 additions & 2 deletions qa/common.check
Original file line number Diff line number Diff line change
Expand Up @@ -1560,11 +1560,11 @@ _check_key_server_version()
fi


if which valkey-cli >/dev/null 2>&1 && test -n `_get_pids_by_name valkey-server`
if which valkey-cli >/dev/null 2>&1 && test -n "`_get_pids_by_name valkey-server`"
then
keys_cli=valkey-cli
keys_ver=valkey_version
elif which redis-cli >/dev/null 2>&1 && test -n _get_pids_by_name redis-server
elif which redis-cli >/dev/null 2>&1 && test -n "`_get_pids_by_name redis-server`"
then
keys_cli=redis-cli
keys_ver=redis_version
Expand Down
1 change: 1 addition & 0 deletions qa/group
Original file line number Diff line number Diff line change
Expand Up @@ -2265,6 +2265,7 @@ suse
1784 pmlogconf local
1787 pmda.opentelemetry local python
1788 pmda.linux local
1790 pmseries libpcp_web local
1791 pmrep python local
1792 pmrep python local
1793 pmrep pcp2xxx python local pmlogdump pmjson
Expand Down
2 changes: 1 addition & 1 deletion src/bashrc/pcp_completion.sh
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ _pcp_complete()
arg_regex="-[ahKcoFeDASTOstZiJ489N6XWwP0lEfqbyQBY]"
;;
pmseries)
all_args="acdDFghiIlLmMnqpsStvVwZ"
all_args="acdDFgGhiIlLmMnNqpsStvVwZ"
arg_regex="-[cDghpwZ]"
;;
pmstat)
Expand Down
2 changes: 2 additions & 0 deletions src/include/pcp/pmwebapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ typedef enum pmSeriesFlags {
PM_SERIES_FLAG_METADATA = (1 << 0), /* only load metric metadata */
PM_SERIES_FLAG_ACTIVE = (1 << 1), /* continual source updates */
PM_SERIES_FLAG_TEXT = (1 << 2), /* load metric & indom help */
PM_SERIES_FLAG_DRYRUN = (1 << 3), /* GC: log what would be removed, no writes */
PM_SERIES_FLAG_ALL = ((unsigned int)~PM_SERIES_FLAG_NONE)
} pmSeriesFlags;

Expand Down Expand Up @@ -169,6 +170,7 @@ extern int pmSeriesValues(pmSeriesSettings *, pmSeriesTimeWindow *, int, sds *,
extern int pmSeriesWindow(pmSeriesSettings *, sds, pmSeriesTimeWindow *, void *);
extern int pmSeriesQuery(pmSeriesSettings *, sds, pmSeriesFlags, void *);
extern int pmSeriesLoad(pmSeriesSettings *, sds, pmSeriesFlags, void *);
extern int pmSeriesGC(pmSeriesSettings *, pmSeriesFlags, void *);

/*
* Timer list interface - global, thread-safe
Expand Down
1 change: 1 addition & 0 deletions src/libpcp_web/src/batons.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ typedef enum {
MAGIC_NAMES,
MAGIC_LABELMAP,
MAGIC_SEARCH,
MAGIC_GC,

MAGIC_COUNT
} series_baton_magic;
Expand Down
5 changes: 5 additions & 0 deletions src/libpcp_web/src/exports.in
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,8 @@ PCP_WEB_1.23 {
dictSetVal;
keySlotsContextFree;
} PCP_WEB_1.22;

PCP_WEB_1.24 {
global:
pmSeriesGC;
} PCP_WEB_1.23;
Loading
Loading