Blame SOURCES/archive-discovery.patch

5b4e29
diff -Naurp pcp-5.0.2.orig/qa/1211.out pcp-5.0.2/qa/1211.out
5b4e29
--- pcp-5.0.2.orig/qa/1211.out	2019-12-06 15:18:26.000000000 +1100
5b4e29
+++ pcp-5.0.2/qa/1211.out	2020-02-03 13:23:15.258762963 +1100
5b4e29
@@ -144,6 +144,9 @@ kernel.uname.nodename
5b4e29
 kernel.uname.release
5b4e29
 kernel.uname.sysname
5b4e29
 kernel.uname.version
5b4e29
+pmcd.pmlogger.archive
5b4e29
+pmcd.pmlogger.host
5b4e29
+pmcd.pmlogger.port
5b4e29
 proc.fd.count
5b4e29
 proc.id.egid
5b4e29
 proc.id.egid_nm
5b4e29
@@ -267,6 +270,7 @@ List all instance names ...
5b4e29
 030016 pmlogger -P -c config.default 20110930.17.20
5b4e29
 1 minute
5b4e29
 15 minute
5b4e29
+2950
5b4e29
 5 minute
5b4e29
 cpu0
5b4e29
 cpu1
5b4e29
@@ -398,10 +402,10 @@ fecd5a4b4c6e1273eaa001287a6dd57b7bbd19f7
5b4e29
 Values fetch for a single-valued query ...
5b4e29
 
5b4e29
 d51624d12da45900bfee2fd73f1e23f3ccabb784
5b4e29
-    [Mon Oct  3 09:10:22.959242000 2011] 172598244
5b4e29
-    [Mon Oct  3 09:10:23.300460000 2011] 172598364
5b4e29
-    [Mon Oct  3 09:10:23.802930000 2011] 172598481
5b4e29
     [Mon Oct  3 09:10:24.305845000 2011] 172598559
5b4e29
+    [Mon Oct  3 09:10:23.802930000 2011] 172598481
5b4e29
+    [Mon Oct  3 09:10:23.300460000 2011] 172598364
5b4e29
+    [Mon Oct  3 09:10:22.959242000 2011] 172598244
5b4e29
 
5b4e29
 Values fetch with a one-second interval ...
5b4e29
 
5b4e29
@@ -420,15 +424,18 @@ d51624d12da45900bfee2fd73f1e23f3ccabb784
5b4e29
 Values fetch for a multi-valued query ...
5b4e29
 
5b4e29
 fecd5a4b4c6e1273eaa001287a6dd57b7bbd19f7
5b4e29
-    [Mon Oct  3 09:10:23.300460000 2011] 0.000000e+00 59181b1de54ff2b383cfd1cdd8636f86c880b69b
5b4e29
-    [Mon Oct  3 09:10:23.300460000 2011] 2.000000e-02 ab010c7d45145aa33c8f8fa681a68c9d4102ae19
5b4e29
-    [Mon Oct  3 09:10:23.300460000 2011] 5.000000e-02 9d418095c9f971ff4fd44d6828ead27f9d021dc3
5b4e29
-    [Mon Oct  3 09:10:23.802930000 2011] 0.000000e+00 59181b1de54ff2b383cfd1cdd8636f86c880b69b
5b4e29
-    [Mon Oct  3 09:10:23.802930000 2011] 2.000000e-02 ab010c7d45145aa33c8f8fa681a68c9d4102ae19
5b4e29
-    [Mon Oct  3 09:10:23.802930000 2011] 5.000000e-02 9d418095c9f971ff4fd44d6828ead27f9d021dc3
5b4e29
     [Mon Oct  3 09:10:24.305845000 2011] 0.000000e+00 59181b1de54ff2b383cfd1cdd8636f86c880b69b
5b4e29
     [Mon Oct  3 09:10:24.305845000 2011] 2.000000e-02 ab010c7d45145aa33c8f8fa681a68c9d4102ae19
5b4e29
     [Mon Oct  3 09:10:24.305845000 2011] 5.000000e-02 9d418095c9f971ff4fd44d6828ead27f9d021dc3
5b4e29
+    [Mon Oct  3 09:10:23.802930000 2011] 0.000000e+00 59181b1de54ff2b383cfd1cdd8636f86c880b69b
5b4e29
+    [Mon Oct  3 09:10:23.802930000 2011] 2.000000e-02 ab010c7d45145aa33c8f8fa681a68c9d4102ae19
5b4e29
+    [Mon Oct  3 09:10:23.802930000 2011] 5.000000e-02 9d418095c9f971ff4fd44d6828ead27f9d021dc3
5b4e29
+    [Mon Oct  3 09:10:23.300460000 2011] 0.000000e+00 59181b1de54ff2b383cfd1cdd8636f86c880b69b
5b4e29
+    [Mon Oct  3 09:10:23.300460000 2011] 2.000000e-02 ab010c7d45145aa33c8f8fa681a68c9d4102ae19
5b4e29
+    [Mon Oct  3 09:10:23.300460000 2011] 5.000000e-02 9d418095c9f971ff4fd44d6828ead27f9d021dc3
5b4e29
+    [Mon Oct  3 09:10:22.959242000 2011] 0.000000e+00 59181b1de54ff2b383cfd1cdd8636f86c880b69b
5b4e29
+    [Mon Oct  3 09:10:22.959242000 2011] 2.000000e-02 ab010c7d45145aa33c8f8fa681a68c9d4102ae19
5b4e29
+    [Mon Oct  3 09:10:22.959242000 2011] 5.000000e-02 9d418095c9f971ff4fd44d6828ead27f9d021dc3
5b4e29
 
5b4e29
 Multi-series lookups from a multi-series query ...
5b4e29
 2db1da4d276d81c42c578c2829e99188ae7cc898
5b4e29
diff -Naurp pcp-5.0.2.orig/qa/1573 pcp-5.0.2/qa/1573
5b4e29
--- pcp-5.0.2.orig/qa/1573	1970-01-01 10:00:00.000000000 +1000
5b4e29
+++ pcp-5.0.2/qa/1573	2020-02-03 13:36:17.288581801 +1100
5b4e29
@@ -0,0 +1,103 @@
5b4e29
+#!/bin/sh
5b4e29
+# PCP QA Test No. 1573
5b4e29
+# Exercise libpcp_web memory leak without a redis-server.
5b4e29
+#
5b4e29
+# Copyright (c) 2020 Red Hat.
5b4e29
+#
5b4e29
+
5b4e29
+seq=`basename $0`
5b4e29
+echo "QA output created by $seq"
5b4e29
+
5b4e29
+# get standard environment, filters and checks
5b4e29
+. ./common.product
5b4e29
+. ./common.filter
5b4e29
+. ./common.check
5b4e29
+
5b4e29
+_check_series
5b4e29
+
5b4e29
+_cleanup()
5b4e29
+{
5b4e29
+    cd $here
5b4e29
+    if $need_restore
5b4e29
+    then
5b4e29
+	need_restore=false
5b4e29
+	_service pmlogger stop >/dev/null
5b4e29
+	$sudo rm -rf $PCP_LOG_DIR/pmlogger
5b4e29
+	$sudo mv $PCP_LOG_DIR/pmlogger.$seq $PCP_LOG_DIR/pmlogger
5b4e29
+	_restore_config $PCP_ETC_DIR/pcp/pmlogger
5b4e29
+	_service pcp restart 2>&1 | _filter_pcp_stop | _filter_pcp_start
5b4e29
+	_wait_for_pmcd
5b4e29
+	_wait_for_pmlogger
5b4e29
+	echo === restarting pmproxy
5b4e29
+	_restore_config $PCP_SYSCONF_DIR/pmproxy
5b4e29
+	_service pmproxy restart 2>&1 | _filter_pcp_start
5b4e29
+	_wait_for_pmproxy
5b4e29
+    fi
5b4e29
+    $sudo rm -rf $tmp $tmp.*
5b4e29
+}
5b4e29
+
5b4e29
+status=1	# failure is the default!
5b4e29
+need_restore=false
5b4e29
+$sudo rm -rf $tmp $tmp.* $seq.full
5b4e29
+trap "_cleanup; exit \$status" 0 1 2 3 15
5b4e29
+
5b4e29
+# real QA test starts here
5b4e29
+_save_config $PCP_SYSCONF_DIR/pmproxy
5b4e29
+need_restore=true
5b4e29
+
5b4e29
+# only want the primary logger running
5b4e29
+_save_config $PCP_ETC_DIR/pcp/pmlogger
5b4e29
+_restore_pmlogger_control
5b4e29
+
5b4e29
+#$sudo rm -f $PCP_SYSCONF_DIR/pmproxy/*
5b4e29
+echo "[pmproxy]" > $tmp.conf
5b4e29
+echo "pcp.enabled = true" >> $tmp.conf
5b4e29
+echo "http.enabled = true" >> $tmp.conf
5b4e29
+echo "redis.enabled = true" >> $tmp.conf
5b4e29
+echo "[discover]" >> $tmp.conf
5b4e29
+echo "enabled = true" >> $tmp.conf
5b4e29
+echo "[pmseries]" >> $tmp.conf
5b4e29
+echo "enabled = false" >> $tmp.conf
5b4e29
+$sudo cp $tmp.conf $PCP_SYSCONF_DIR/pmproxy/pmproxy.conf
5b4e29
+
5b4e29
+_service pmlogger stop >/dev/null
5b4e29
+
5b4e29
+# move aside existing logs so we can measure base memory footprint
5b4e29
+[ -d $PCP_LOG_DIR/pmlogger.$seq ] && $sudo mv $PCP_LOG_DIR/pmlogger.$seq $PCP_LOG_DIR/pmlogger.$seq.saved
5b4e29
+$sudo mv $PCP_LOG_DIR/pmlogger $PCP_LOG_DIR/pmlogger.$seq
5b4e29
+$sudo mkdir -p $PCP_LOG_DIR/pmlogger
5b4e29
+$sudo chmod 775 $PCP_LOG_DIR/pmlogger
5b4e29
+$sudo chown $PCP_USER:$PCP_USER $PCP_LOG_DIR/pmlogger
5b4e29
+
5b4e29
+_service pmproxy restart 2>&1 | _filter_pcp_stop | _filter_pcp_start
5b4e29
+_wait_for_pmproxy
5b4e29
+
5b4e29
+pmproxy_pid=`_get_pids_by_name -a pmproxy`
5b4e29
+[ -z "$pmproxy_pid" ] && echo === pmproxy not running && status=1 && exit 1
5b4e29
+echo === extract initial rss
5b4e29
+pmproxy_rss1=`pminfo -f proc.memory.rss |
5b4e29
+	$PCP_AWK_PROG '{ if ($2 == "['$pmproxy_pid'") { print $NF} }'`
5b4e29
+
5b4e29
+echo === restarting pmlogger # primary only
5b4e29
+_service pmlogger restart 2>&1 | _filter_pcp_start
5b4e29
+_wait_for_pmlogger
5b4e29
+
5b4e29
+echo === wait for pmproxy to process filesystem events
5b4e29
+pmsleep 4.2
5b4e29
+
5b4e29
+echo === extract updated rss
5b4e29
+pmproxy_rss2=`pminfo -f proc.memory.rss |
5b4e29
+	$PCP_AWK_PROG '{ if ($2 == "['$pmproxy_pid'") { print $NF} }'`
5b4e29
+
5b4e29
+echo === checking rss within tolerance
5b4e29
+_within_tolerance "rss" $pmproxy_rss1 $pmproxy_rss2 10%
5b4e29
+[ $pmproxy_rss2 -gt 10000 ] && echo "Unexpected pmproxy RSS: $pmproxy_rss2, was initially $pmproxy_rss1"
5b4e29
+
5b4e29
+echo "RSS1 for PID $pmproxy_pid is $pmproxy_rss1" >> $here/$seq.full
5b4e29
+echo "RSS2 for PID $pmproxy_pid is $pmproxy_rss2" >> $here/$seq.full
5b4e29
+cat $PCP_LOG_DIR/pmproxy/pmproxy.log >>$seq.full
5b4e29
+echo === see $seq.full for pmproxy rss and logs
5b4e29
+
5b4e29
+# success, all done
5b4e29
+status=0
5b4e29
+exit
5b4e29
diff -Naurp pcp-5.0.2.orig/qa/1573.out pcp-5.0.2/qa/1573.out
5b4e29
--- pcp-5.0.2.orig/qa/1573.out	1970-01-01 10:00:00.000000000 +1000
5b4e29
+++ pcp-5.0.2/qa/1573.out	2020-02-03 13:23:15.259762953 +1100
5b4e29
@@ -0,0 +1,8 @@
5b4e29
+QA output created by 1573
5b4e29
+=== extract initial rss
5b4e29
+=== restarting pmlogger
5b4e29
+=== wait for pmproxy to process filesystem events
5b4e29
+=== extract updated rss
5b4e29
+=== checking rss within tolerance
5b4e29
+=== see 1573.full for pmproxy rss and logs
5b4e29
+=== restarting pmproxy
5b4e29
diff -Naurp pcp-5.0.2.orig/qa/1600 pcp-5.0.2/qa/1600
5b4e29
--- pcp-5.0.2.orig/qa/1600	2019-12-10 17:49:05.000000000 +1100
5b4e29
+++ pcp-5.0.2/qa/1600	2020-02-03 13:23:15.260762942 +1100
5b4e29
@@ -82,7 +82,11 @@ _filter_values()
5b4e29
 _filter_label_values()
5b4e29
 {
5b4e29
     sed \
5b4e29
+	-e "s/^domainname: \"${domainname}\"/domainname: \"DOMAIN\"/g" \
5b4e29
+	-e "s/^machineid: \"${machineid}\"/machineid: \"MACHINE\"/g" \
5b4e29
 	-e "s/^hostname: \"${hostname}\"/hostname: \"HOSTNAME\"/g" \
5b4e29
+	-e "s/^groupid: $groupid/groupid: GID/g" \
5b4e29
+	-e "s/^userid: $userid/userid: UID/g" \
5b4e29
 	-e "s/changed: false, true/changed: false/g" \
5b4e29
 	-e "/metric_label: null/d" \
5b4e29
     #end
5b4e29
diff -Naurp pcp-5.0.2.orig/qa/1600.out pcp-5.0.2/qa/1600.out
5b4e29
--- pcp-5.0.2.orig/qa/1600.out	2019-12-10 10:46:20.000000000 +1100
5b4e29
+++ pcp-5.0.2/qa/1600.out	2020-02-03 13:23:15.260762942 +1100
5b4e29
@@ -27,15 +27,15 @@ TIMESERIES
5b4e29
 == verify metric labels
5b4e29
 
5b4e29
 TIMESERIES
5b4e29
-    inst [100 or "bin-100"] labels {"agent":"sample","hostname":"HOST","role":"testing"}
5b4e29
-    inst [200 or "bin-200"] labels {"agent":"sample","hostname":"HOST","role":"testing"}
5b4e29
-    inst [300 or "bin-300"] labels {"agent":"sample","hostname":"HOST","role":"testing"}
5b4e29
-    inst [400 or "bin-400"] labels {"agent":"sample","hostname":"HOST","role":"testing"}
5b4e29
-    inst [500 or "bin-500"] labels {"agent":"sample","hostname":"HOST","role":"testing"}
5b4e29
-    inst [600 or "bin-600"] labels {"agent":"sample","hostname":"HOST","role":"testing"}
5b4e29
-    inst [700 or "bin-700"] labels {"agent":"sample","hostname":"HOST","role":"testing"}
5b4e29
-    inst [800 or "bin-800"] labels {"agent":"sample","hostname":"HOST","role":"testing"}
5b4e29
-    inst [900 or "bin-900"] labels {"agent":"sample","hostname":"HOST","role":"testing"}
5b4e29
+    inst [100 or "bin-100"] labels {"agent":"sample","bin":100,"domainname":"DOMAIN","groupid":GID,"hostname":"HOST","latitude":-25.28496,"longitude":152.87886,"machineid":"MACHINE","role":"testing","userid":UID}
5b4e29
+    inst [200 or "bin-200"] labels {"agent":"sample","bin":200,"domainname":"DOMAIN","groupid":GID,"hostname":"HOST","latitude":-25.28496,"longitude":152.87886,"machineid":"MACHINE","role":"testing","userid":UID}
5b4e29
+    inst [300 or "bin-300"] labels {"agent":"sample","bin":300,"domainname":"DOMAIN","groupid":GID,"hostname":"HOST","latitude":-25.28496,"longitude":152.87886,"machineid":"MACHINE","role":"testing","userid":UID}
5b4e29
+    inst [400 or "bin-400"] labels {"agent":"sample","bin":400,"domainname":"DOMAIN","groupid":GID,"hostname":"HOST","latitude":-25.28496,"longitude":152.87886,"machineid":"MACHINE","role":"testing","userid":UID}
5b4e29
+    inst [500 or "bin-500"] labels {"agent":"sample","bin":500,"domainname":"DOMAIN","groupid":GID,"hostname":"HOST","latitude":-25.28496,"longitude":152.87886,"machineid":"MACHINE","role":"testing","userid":UID}
5b4e29
+    inst [600 or "bin-600"] labels {"agent":"sample","bin":600,"domainname":"DOMAIN","groupid":GID,"hostname":"HOST","latitude":-25.28496,"longitude":152.87886,"machineid":"MACHINE","role":"testing","userid":UID}
5b4e29
+    inst [700 or "bin-700"] labels {"agent":"sample","bin":700,"domainname":"DOMAIN","groupid":GID,"hostname":"HOST","latitude":-25.28496,"longitude":152.87886,"machineid":"MACHINE","role":"testing","userid":UID}
5b4e29
+    inst [800 or "bin-800"] labels {"agent":"sample","bin":800,"domainname":"DOMAIN","groupid":GID,"hostname":"HOST","latitude":-25.28496,"longitude":152.87886,"machineid":"MACHINE","role":"testing","userid":UID}
5b4e29
+    inst [900 or "bin-900"] labels {"agent":"sample","bin":900,"domainname":"DOMAIN","groupid":GID,"hostname":"HOST","latitude":-25.28496,"longitude":152.87886,"machineid":"MACHINE","role":"testing","userid":UID}
5b4e29
 == verify metric values
5b4e29
 
5b4e29
 TIMESERIES
5b4e29
@@ -43,15 +43,24 @@ TIMESERIES
5b4e29
     [TIMESTAMP] VALUE
5b4e29
 == verify label names and values
5b4e29
 agent: "mmv", "sample", "pmcd"
5b4e29
+bin: 100, 200, 300, 400, 500, 600, 700, 800, 900
5b4e29
 changed: false
5b4e29
 clan: "mcdonell"
5b4e29
 cluster: "zero"
5b4e29
+domainname: "DOMAIN"
5b4e29
+groupid: GID
5b4e29
 hostname: "HOSTNAME"
5b4e29
 indom_label: 42.001
5b4e29
+latitude: -25.28496
5b4e29
+longitude: 152.87886
5b4e29
+machineid: "MACHINE"
5b4e29
 measure: "speed"
5b4e29
 model: "RGB"
5b4e29
+registry_label: "string"
5b4e29
 role: "testing"
5b4e29
+transient: false, true
5b4e29
 units: "metres per second"
5b4e29
 unitsystem: "SI"
5b4e29
+userid: UID
5b4e29
 == verify archive removal
5b4e29
 == all done
5b4e29
diff -Naurp pcp-5.0.2.orig/qa/1601.out pcp-5.0.2/qa/1601.out
5b4e29
--- pcp-5.0.2.orig/qa/1601.out	2019-11-27 16:01:34.000000000 +1100
5b4e29
+++ pcp-5.0.2/qa/1601.out	2020-02-03 13:23:15.261762932 +1100
5b4e29
@@ -131,7 +131,7 @@ Using series 01d8bc7fa75aaff98a08aa0b1c0
5b4e29
     {
5b4e29
         "series": "605fc77742cd0317597291329561ac4e50c0dd12",
5b4e29
         "instance": "c3795d8b757506a2901c6b08b489ba56cae7f0d4",
5b4e29
-        "timestamp": 1317633023300.460,
5b4e29
+        "timestamp": 1317633024305.845,
5b4e29
         "value": "71661"
5b4e29
     },
5b4e29
     {
5b4e29
@@ -147,7 +147,7 @@ Using series 01d8bc7fa75aaff98a08aa0b1c0
5b4e29
         {
5b4e29
             "series": "605fc77742cd0317597291329561ac4e50c0dd12",
5b4e29
             "instance": "c3795d8b757506a2901c6b08b489ba56cae7f0d4",
5b4e29
-            "timestamp": 1317633023300.460,
5b4e29
+            "timestamp": 1317633024305.845,
5b4e29
             "value": "71661"
5b4e29
         },
5b4e29
         {
5b4e29
@@ -163,7 +163,7 @@ Using series 01d8bc7fa75aaff98a08aa0b1c0
5b4e29
     {
5b4e29
         "series": "605fc77742cd0317597291329561ac4e50c0dd12",
5b4e29
         "instance": "c3795d8b757506a2901c6b08b489ba56cae7f0d4",
5b4e29
-        "timestamp": 1317633023300.460,
5b4e29
+        "timestamp": 1317633024305.845,
5b4e29
         "value": "71661"
5b4e29
     },
5b4e29
     {
5b4e29
@@ -179,7 +179,7 @@ Using series 01d8bc7fa75aaff98a08aa0b1c0
5b4e29
         {
5b4e29
             "series": "605fc77742cd0317597291329561ac4e50c0dd12",
5b4e29
             "instance": "c3795d8b757506a2901c6b08b489ba56cae7f0d4",
5b4e29
-            "timestamp": 1317633023300.460,
5b4e29
+            "timestamp": 1317633024305.845,
5b4e29
             "value": "71661"
5b4e29
         },
5b4e29
         {
5b4e29
diff -Naurp pcp-5.0.2.orig/qa/1661 pcp-5.0.2/qa/1661
5b4e29
--- pcp-5.0.2.orig/qa/1661	2019-12-10 17:04:20.000000000 +1100
5b4e29
+++ pcp-5.0.2/qa/1661	2020-02-03 13:23:15.261762932 +1100
5b4e29
@@ -41,8 +41,7 @@ _restore_pmlogger_control
5b4e29
 echo;echo === restarting pmproxy service to ensure sane starting condition 
5b4e29
 _service pmlogger stop 2>&1 | _filter_pcp_stop
5b4e29
 _service pmproxy restart 2>&1 | _filter_pcp_stop | _filter_pcp_start
5b4e29
-# give pmproxy a chance to startup
5b4e29
-pmsleep 2; _wait_for_pmproxy
5b4e29
+_wait_for_pmproxy
5b4e29
 
5b4e29
 pmproxy_pid=`_get_pids_by_name -a pmproxy`
5b4e29
 [ -z "$pmproxy_pid" ] && echo === pmproxy not running && status=1 && exit 1
5b4e29
diff -Naurp pcp-5.0.2.orig/qa/group pcp-5.0.2/qa/group
5b4e29
--- pcp-5.0.2.orig/qa/group	2019-12-11 14:06:06.000000000 +1100
5b4e29
+++ pcp-5.0.2/qa/group	2020-02-03 13:23:15.261762932 +1100
5b4e29
@@ -1688,6 +1688,7 @@ BAD
5b4e29
 1545 pcp2xml python pcp2xxx local
5b4e29
 1546 pmrep python local
5b4e29
 1547 pmrep python local
5b4e29
+1573 pmproxy libpcp_web pmlogger local
5b4e29
 1588 python pmiostat local
5b4e29
 1598 pmda.statsd local
5b4e29
 1599 pmda.statsd local
5b4e29
diff -Naurp pcp-5.0.2.orig/src/include/pcp/libpcp.h pcp-5.0.2/src/include/pcp/libpcp.h
5b4e29
--- pcp-5.0.2.orig/src/include/pcp/libpcp.h	2019-09-24 17:23:36.000000000 +1000
5b4e29
+++ pcp-5.0.2/src/include/pcp/libpcp.h	2020-02-03 13:23:15.261762932 +1100
5b4e29
@@ -7,7 +7,7 @@
5b4e29
  *	remain fixed across releases, and they may not work, or may
5b4e29
  *	provide different semantics at some point in the future.
5b4e29
  *
5b4e29
- * Copyright (c) 2012-2019 Red Hat.
5b4e29
+ * Copyright (c) 2012-2020 Red Hat.
5b4e29
  * Copyright (c) 2008-2009 Aconex.  All Rights Reserved.
5b4e29
  * Copyright (c) 1995-2002 Silicon Graphics, Inc.  All Rights Reserved.
5b4e29
  *
5b4e29
@@ -846,6 +846,13 @@ PCP_CALL extern int __pmLogPutText(__pmA
5b4e29
 PCP_CALL extern int __pmLogWriteLabel(__pmFILE *, const __pmLogLabel *);
5b4e29
 PCP_CALL extern int __pmLogLoadLabel(__pmArchCtl *, const char *);
5b4e29
 PCP_CALL extern int __pmLogLoadMeta(__pmArchCtl *);
5b4e29
+PCP_CALL extern int __pmLogAddDesc(__pmArchCtl *, const pmDesc *);
5b4e29
+PCP_CALL extern int __pmLogAddInDom(__pmArchCtl *, const pmTimespec *, const pmInResult *, int *, int);
5b4e29
+PCP_CALL extern int __pmLogAddPMNSNode(__pmArchCtl *, pmID, const char *);
5b4e29
+PCP_CALL extern int __pmLogAddLabelSets(__pmArchCtl *, const pmTimespec *, unsigned int, unsigned int, int, pmLabelSet *);
5b4e29
+PCP_CALL extern int __pmLogAddText(__pmArchCtl *, unsigned int, unsigned int, const char *);
5b4e29
+PCP_CALL extern int __pmLogAddVolume(__pmArchCtl *, unsigned int);
5b4e29
+
5b4e29
 #define PMLOGREAD_NEXT		0
5b4e29
 #define PMLOGREAD_TO_EOF	1
5b4e29
 PCP_CALL extern int __pmLogRead(__pmArchCtl *, int, __pmFILE *, pmResult **, int);
5b4e29
@@ -862,7 +869,9 @@ PCP_CALL extern int __pmLogLookupText(__
5b4e29
 PCP_CALL extern int __pmLogNameInDom(__pmArchCtl *, pmInDom, pmTimeval *, int, char **);
5b4e29
 PCP_CALL extern const char *__pmLogLocalSocketDefault(int, char *buf, size_t bufSize);
5b4e29
 PCP_CALL extern const char *__pmLogLocalSocketUser(int, char *buf, size_t bufSize);
5b4e29
+PCP_CALL extern int __pmLogCompressedSuffix(const char *);
5b4e29
 PCP_CALL extern char *__pmLogBaseName(char *);
5b4e29
+PCP_CALL extern char *__pmLogBaseNameVol(char *, int *);
5b4e29
 PCP_DATA extern int __pmLogReads;
5b4e29
 
5b4e29
 /* Convert opaque context handle to __pmContext pointer */
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp/src/exports.master pcp-5.0.2/src/libpcp/src/exports.master
5b4e29
--- pcp-5.0.2.orig/src/libpcp/src/exports.master	2019-10-02 14:40:30.000000000 +1000
5b4e29
+++ pcp-5.0.2/src/libpcp/src/exports.master	2020-02-03 13:23:15.262762921 +1100
5b4e29
@@ -683,3 +683,15 @@ PCP_3.26 {
5b4e29
   global:
5b4e29
     __pmDupLabelSets;
5b4e29
 } PCP_3.25;
5b4e29
+
5b4e29
+PCP_3.26_1 {
5b4e29
+  global:
5b4e29
+    __pmLogAddDesc;
5b4e29
+    __pmLogAddInDom;
5b4e29
+    __pmLogAddPMNSNode;
5b4e29
+    __pmLogAddLabelSets;
5b4e29
+    __pmLogAddText;
5b4e29
+    __pmLogAddVolume;
5b4e29
+    __pmLogCompressedSuffix;
5b4e29
+    __pmLogBaseNameVol;
5b4e29
+} PCP_3.26;
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp/src/io.c pcp-5.0.2/src/libpcp/src/io.c
5b4e29
--- pcp-5.0.2.orig/src/libpcp/src/io.c	2018-06-09 11:43:34.000000000 +1000
5b4e29
+++ pcp-5.0.2/src/libpcp/src/io.c	2020-02-03 13:23:15.262762921 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2017-2018 Red Hat.
5b4e29
+ * Copyright (c) 2017-2018,2020 Red Hat.
5b4e29
  * 
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
  * under the terms of the GNU Lesser General Public License as published
5b4e29
@@ -47,7 +47,7 @@ extern __pm_fops __pm_xz;
5b4e29
 #endif
5b4e29
 
5b4e29
 static const struct {
5b4e29
-    const char	*suff;
5b4e29
+    const char	*suffix;
5b4e29
     const int	appl;
5b4e29
     __pm_fops   *handler;
5b4e29
 } compress_ctl[] = {
5b4e29
@@ -61,40 +61,43 @@ static const struct {
5b4e29
 };
5b4e29
 static const int ncompress = sizeof(compress_ctl) / sizeof(compress_ctl[0]);
5b4e29
 
5b4e29
+int
5b4e29
+__pmLogCompressedSuffix(const char *suffix)
5b4e29
+{
5b4e29
+    int		i;
5b4e29
+
5b4e29
+    for (i = 0; i < ncompress; i++)
5b4e29
+	if (strcmp(suffix, compress_ctl[i].suffix) == 0)
5b4e29
+	    return 1;
5b4e29
+    return 0;
5b4e29
+}
5b4e29
+
5b4e29
 /*
5b4e29
- * If name contains '.' and the suffix is "index", "meta" or a string of
5b4e29
- * digits, all optionally followed by one of the compression suffixes,
5b4e29
- * strip the suffix.
5b4e29
- *
5b4e29
- * Modifications are performed on the argument string in-place. If modifications
5b4e29
- * are made, a pointer to the start of the modified string is returned.
5b4e29
- * Otherwise, NULL is returned.
5b4e29
+ * Variant of __pmLogBaseName() - see below that also returns log
5b4e29
+ * the volume number if the file name is an archive log volume.
5b4e29
+ * If the vol argument is NULL it will be ignored.
5b4e29
  */
5b4e29
 char *
5b4e29
-__pmLogBaseName(char *name)
5b4e29
+__pmLogBaseNameVol(char *name, int *vol)
5b4e29
 {
5b4e29
-    char *q;
5b4e29
-    int   strip;
5b4e29
-    int   i;
5b4e29
+    char	*q, *q2;
5b4e29
+    int		strip = 0;
5b4e29
 
5b4e29
-    strip = 0;
5b4e29
+    if (vol)
5b4e29
+	*vol = -1;
5b4e29
     if ((q = strrchr(name, '.')) != NULL) {
5b4e29
-	for (i = 0; i < ncompress; i++) {
5b4e29
-	    if (strcmp(q, compress_ctl[i].suff) == 0) {
5b4e29
-		char	*q2;
5b4e29
-		/*
5b4e29
-		 * The name ends with one of the supported compressed file
5b4e29
-		 * suffixes. Strip it before checking for other known suffixes.
5b4e29
-		 */
5b4e29
-		*q = '\0';
5b4e29
-		if ((q2 = strrchr(name, '.')) == NULL) {
5b4e29
-		    /* no . to the left of the suffix */
5b4e29
-		    *q = '.';
5b4e29
-		    goto done;
5b4e29
-		}
5b4e29
-		q = q2;
5b4e29
-		break;
5b4e29
+	if (__pmLogCompressedSuffix(q)) {
5b4e29
+	    /*
5b4e29
+	     * The name ends with one of the supported compressed file
5b4e29
+	     * suffixes. Strip it before checking for other known suffixes.
5b4e29
+	     */
5b4e29
+	    *q = '\0';
5b4e29
+	    if ((q2 = strrchr(name, '.')) == NULL) {
5b4e29
+		/* no . to the left of the suffix */
5b4e29
+		*q = '.';
5b4e29
+		goto done;
5b4e29
 	    }
5b4e29
+	    q = q2;
5b4e29
 	}
5b4e29
 	if (strcmp(q, ".index") == 0) {
5b4e29
 	    strip = 1;
5b4e29
@@ -109,16 +112,10 @@ __pmLogBaseName(char *name)
5b4e29
 	 */
5b4e29
 	if (q[1] != '\0') {
5b4e29
 	    char	*end;
5b4e29
-	    /*
5b4e29
-	     * Below we don't care about the value from strtol(),
5b4e29
-	     * we're interested in updating the pointer "end".
5b4e29
-	     * The messiness is thanks to gcc and glibc ... strtol()
5b4e29
-	     * is marked __attribute__((warn_unused_result)) ...
5b4e29
-	     * to avoid warnings on all platforms, assign to a
5b4e29
-	     * dummy variable that is explicitly marked unused.
5b4e29
-	     */
5b4e29
-	    long	tmpl __attribute__((unused));
5b4e29
+	    long	tmpl;
5b4e29
 	    tmpl = strtol(q+1, &end, 10);
5b4e29
+	    if (vol)
5b4e29
+		*vol = tmpl;
5b4e29
 	    if (*end == '\0') strip = 1;
5b4e29
 	}
5b4e29
     }
5b4e29
@@ -131,6 +128,21 @@ done:
5b4e29
     return NULL; /* not the name of an archive file. */
5b4e29
 }
5b4e29
 
5b4e29
+/*
5b4e29
+ * If name contains '.' and the suffix is "index", "meta" or a string of
5b4e29
+ * digits, all optionally followed by one of the compression suffixes,
5b4e29
+ * strip the suffix.
5b4e29
+ *
5b4e29
+ * Modifications are performed on the argument string in-place. If modifications
5b4e29
+ * are made, a pointer to the start of the modified string is returned.
5b4e29
+ * Otherwise, NULL is returned.
5b4e29
+ */
5b4e29
+char *
5b4e29
+__pmLogBaseName(char *name)
5b4e29
+{
5b4e29
+    return __pmLogBaseNameVol(name, NULL);
5b4e29
+}
5b4e29
+
5b4e29
 static int
5b4e29
 popen_uncompress(const char *cmd, const char *arg, const char *fname, int fd)
5b4e29
 {
5b4e29
@@ -319,7 +331,7 @@ __pmCompressedFileIndex(char *fname, siz
5b4e29
     char	tmpname[MAXPATHLEN];
5b4e29
 
5b4e29
     for (i = 0; i < ncompress; i++) {
5b4e29
-	suffix = compress_ctl[i].suff;
5b4e29
+	suffix = compress_ctl[i].suffix;
5b4e29
 	pmsprintf(tmpname, sizeof(tmpname), "%s%s", fname, suffix);
5b4e29
 	sts = access(tmpname, R_OK);
5b4e29
 	if (sts == 0 || (errno != ENOENT && errno != ENOTDIR)) {
5b4e29
@@ -358,7 +370,7 @@ index_compress(char *fname, size_t flen)
5b4e29
     suffix = strrchr(fname, '.');
5b4e29
     if (suffix != NULL) {
5b4e29
 	for (i = 0; i < ncompress; i++) {
5b4e29
-	    if (strcmp(suffix, compress_ctl[i].suff) == 0)
5b4e29
+	    if (strcmp(suffix, compress_ctl[i].suffix) == 0)
5b4e29
 		return i;
5b4e29
 	}
5b4e29
     }
5b4e29
@@ -731,7 +743,7 @@ compress_suffix_list(void)
5b4e29
 	const char	*q;
5b4e29
 
5b4e29
 	for (i = 0; i < ncompress; i++) {
5b4e29
-	    q = compress_ctl[i].suff;
5b4e29
+	    q = compress_ctl[i].suffix;
5b4e29
 	    if (i > 0)
5b4e29
 		*p++ = ' ';
5b4e29
 	    while (*q) {
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp/src/logmeta.c pcp-5.0.2/src/libpcp/src/logmeta.c
5b4e29
--- pcp-5.0.2.orig/src/libpcp/src/logmeta.c	2018-09-14 10:22:56.000000000 +1000
5b4e29
+++ pcp-5.0.2/src/libpcp/src/logmeta.c	2020-02-03 13:23:15.262762921 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2013-2018 Red Hat.
5b4e29
+ * Copyright (c) 2013-2018, 2020 Red Hat.
5b4e29
  * Copyright (c) 1995-2002 Silicon Graphics, Inc.  All Rights Reserved.
5b4e29
  * 
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
@@ -490,7 +490,7 @@ check_dup_labels(const __pmArchCtl *acp)
5b4e29
 }
5b4e29
 
5b4e29
 static int
5b4e29
-addtext(__pmArchCtl *acp, unsigned int ident, unsigned int type, char *buffer)
5b4e29
+addtext(__pmArchCtl *acp, unsigned int ident, unsigned int type, const char *buffer)
5b4e29
 {
5b4e29
     __pmLogCtl		*lcp = acp->ac_log;
5b4e29
     __pmHashNode	*hp;
5b4e29
@@ -553,6 +553,92 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":15",
5b4e29
     return sts;
5b4e29
 }
5b4e29
 
5b4e29
+int
5b4e29
+__pmLogAddDesc(__pmArchCtl *acp, const pmDesc *newdp)
5b4e29
+{
5b4e29
+    __pmHashNode	*hp;
5b4e29
+    __pmLogCtl		*lcp = acp->ac_log;
5b4e29
+    pmDesc		*dp, *olddp;
5b4e29
+
5b4e29
+    if ((hp = __pmHashSearch((int)newdp->pmid, &lcp->l_hashpmid)) != NULL) {
5b4e29
+	/* PMID is already in the hash table - check for conflicts. */
5b4e29
+	olddp = (pmDesc *)hp->data;
5b4e29
+	if (newdp->type != olddp->type)
5b4e29
+	    return PM_ERR_LOGCHANGETYPE;
5b4e29
+	if (newdp->sem != olddp->sem)
5b4e29
+	    return PM_ERR_LOGCHANGESEM;
5b4e29
+	if (newdp->indom != olddp->indom)
5b4e29
+	    return PM_ERR_LOGCHANGEINDOM;
5b4e29
+	if (newdp->units.dimSpace != olddp->units.dimSpace ||
5b4e29
+	    newdp->units.dimTime != olddp->units.dimTime ||
5b4e29
+	    newdp->units.dimCount != olddp->units.dimCount ||
5b4e29
+	    newdp->units.scaleSpace != olddp->units.scaleSpace ||
5b4e29
+	    newdp->units.scaleTime != olddp->units.scaleTime ||
5b4e29
+	    newdp->units.scaleCount != olddp->units.scaleCount)
5b4e29
+	    return PM_ERR_LOGCHANGEUNITS;
5b4e29
+
5b4e29
+	/* PMID is already known and checks out - we're done here. */
5b4e29
+	return 0;
5b4e29
+    }
5b4e29
+
5b4e29
+    /* Add a copy of the descriptor into the PMID:desc hash table. */
5b4e29
+PM_FAULT_POINT("libpcp/" __FILE__ ":2", PM_FAULT_ALLOC);
5b4e29
+    if ((dp = (pmDesc *)malloc(sizeof(pmDesc))) == NULL)
5b4e29
+	return -oserror();
5b4e29
+    *dp = *newdp;
5b4e29
+
5b4e29
+    return __pmHashAdd((int)dp->pmid, (void *)dp, &lcp->l_hashpmid);
5b4e29
+}
5b4e29
+
5b4e29
+int
5b4e29
+__pmLogAddPMNSNode(__pmArchCtl *acp, pmID pmid, const char *name)
5b4e29
+{
5b4e29
+    __pmLogCtl		*lcp = acp->ac_log;
5b4e29
+    int			sts;
5b4e29
+
5b4e29
+    /*
5b4e29
+     * If we see a duplicate name with a different PMID, its a
5b4e29
+     * recoverable error.
5b4e29
+     * We wont be able to see all of the data in the log, but
5b4e29
+     * its better to provide access to some rather than none,
5b4e29
+     * esp. when only one or two metric IDs may be corrupted
5b4e29
+     * in this way (which we may not be interested in anyway).
5b4e29
+     */
5b4e29
+    sts = __pmAddPMNSNode(lcp->l_pmns, pmid, name);
5b4e29
+    if (sts == PM_ERR_PMID)
5b4e29
+	sts = 0;
5b4e29
+    return sts;
5b4e29
+}
5b4e29
+
5b4e29
+int
5b4e29
+__pmLogAddInDom(__pmArchCtl *acp, const pmTimespec *when, const pmInResult *in,
5b4e29
+		int *tbuf, int allinbuf)
5b4e29
+{
5b4e29
+    pmTimeval		tv;
5b4e29
+
5b4e29
+    tv.tv_sec = when->tv_sec;
5b4e29
+    tv.tv_usec = when->tv_nsec / 1000;
5b4e29
+    return addindom(acp->ac_log, in->indom, &tv,
5b4e29
+		    in->numinst, in->instlist, in->namelist, tbuf, allinbuf);
5b4e29
+}
5b4e29
+
5b4e29
+int
5b4e29
+__pmLogAddLabelSets(__pmArchCtl *acp, const pmTimespec *when, unsigned int type,
5b4e29
+		unsigned int ident, int nsets, pmLabelSet *labelsets)
5b4e29
+{
5b4e29
+    pmTimeval		tv;
5b4e29
+
5b4e29
+    tv.tv_sec = when->tv_sec;
5b4e29
+    tv.tv_usec = when->tv_nsec / 1000;
5b4e29
+    return addlabel(acp, type, ident, nsets, labelsets, &tv;;
5b4e29
+}
5b4e29
+
5b4e29
+int
5b4e29
+__pmLogAddText(__pmArchCtl *acp, unsigned int ident, unsigned int type, const char *buffer)
5b4e29
+{
5b4e29
+    return addtext(acp, ident, type, buffer);
5b4e29
+}
5b4e29
+
5b4e29
 /*
5b4e29
  * Load _all_ of the hashed pmDesc and __pmLogInDom structures from the metadata
5b4e29
  * log file -- used at the initialization (NewContext) of an archive.
5b4e29
@@ -563,11 +649,8 @@ int
5b4e29
 __pmLogLoadMeta(__pmArchCtl *acp)
5b4e29
 {
5b4e29
     __pmLogCtl		*lcp = acp->ac_log;
5b4e29
-    __pmHashNode	*hp;
5b4e29
     int			rlen;
5b4e29
     int			check;
5b4e29
-    pmDesc		*dp;
5b4e29
-    pmDesc		*olddp;
5b4e29
     int			sts = 0;
5b4e29
     __pmLogHdr		h;
5b4e29
     __pmFILE		*f = lcp->l_mdfp;
5b4e29
@@ -615,13 +698,10 @@ __pmLogLoadMeta(__pmArchCtl *acp)
5b4e29
 	}
5b4e29
 	rlen = h.len - (int)sizeof(__pmLogHdr) - (int)sizeof(int);
5b4e29
 	if (h.type == TYPE_DESC) {
5b4e29
+	    pmDesc		desc;
5b4e29
+
5b4e29
 	    numpmid++;
5b4e29
-PM_FAULT_POINT("libpcp/" __FILE__ ":2", PM_FAULT_ALLOC);
5b4e29
-	    if ((dp = (pmDesc *)malloc(sizeof(pmDesc))) == NULL) {
5b4e29
-		sts = -oserror();
5b4e29
-		goto end;
5b4e29
-	    }
5b4e29
-	    if ((n = (int)__pmFread(dp, 1, sizeof(pmDesc), f)) != sizeof(pmDesc)) {
5b4e29
+	    if ((n = (int)__pmFread(&desc, 1, sizeof(pmDesc), f)) != sizeof(pmDesc)) {
5b4e29
 		if (pmDebugOptions.logmeta) {
5b4e29
 		    fprintf(stderr, "__pmLogLoadMeta: pmDesc read -> %d: expected: %d\n",
5b4e29
 			    n, (int)sizeof(pmDesc));
5b4e29
@@ -632,67 +712,25 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":2",
5b4e29
 		}
5b4e29
 		else
5b4e29
 		    sts = PM_ERR_LOGREC;
5b4e29
-		free(dp);
5b4e29
 		goto end;
5b4e29
 	    }
5b4e29
-	    else {
5b4e29
-		/* swab desc */
5b4e29
-		dp->type = ntohl(dp->type);
5b4e29
-		dp->sem = ntohl(dp->sem);
5b4e29
-		dp->indom = __ntohpmInDom(dp->indom);
5b4e29
-		dp->units = __ntohpmUnits(dp->units);
5b4e29
-		dp->pmid = __ntohpmID(dp->pmid);
5b4e29
-	    }
5b4e29
 
5b4e29
-	    /* Add it to the hash pmid hash table. */
5b4e29
-	    if ((hp = __pmHashSearch((int)dp->pmid, &lcp->l_hashpmid)) != NULL) {
5b4e29
-		/*
5b4e29
-		 * This pmid is already in the hash table. Check for conflicts.
5b4e29
-		 */
5b4e29
-		olddp = (pmDesc *)hp->data;
5b4e29
-		if (dp->type != olddp->type) {
5b4e29
-		    sts = PM_ERR_LOGCHANGETYPE;
5b4e29
-		    free(dp);
5b4e29
-		    goto end;
5b4e29
-		}
5b4e29
-		if (dp->sem != olddp->sem) {
5b4e29
-		    sts = PM_ERR_LOGCHANGESEM;
5b4e29
-		    free(dp);
5b4e29
-		    goto end;
5b4e29
-		}
5b4e29
-		if (dp->indom != olddp->indom) {
5b4e29
-		    sts = PM_ERR_LOGCHANGEINDOM;
5b4e29
-		    free(dp);
5b4e29
-		    goto end;
5b4e29
-		}
5b4e29
-		if (dp->units.dimSpace != olddp->units.dimSpace ||
5b4e29
-		    dp->units.dimTime != olddp->units.dimTime ||
5b4e29
-		    dp->units.dimCount != olddp->units.dimCount ||
5b4e29
-		    dp->units.scaleSpace != olddp->units.scaleSpace ||
5b4e29
-		    dp->units.scaleTime != olddp->units.scaleTime ||
5b4e29
-		    dp->units.scaleCount != olddp->units.scaleCount) {
5b4e29
-		    sts = PM_ERR_LOGCHANGEUNITS;
5b4e29
-		    free(dp);
5b4e29
-		    goto end;
5b4e29
-		}
5b4e29
-                /*
5b4e29
-                 * This pmid is already known, and matches.  We can free the newly
5b4e29
-                 * read copy and use the one in the hash table. 
5b4e29
-                 */
5b4e29
-                free(dp);
5b4e29
-                dp = olddp;
5b4e29
-	    }
5b4e29
-	    else if ((sts = __pmHashAdd((int)dp->pmid, (void *)dp, &lcp->l_hashpmid)) < 0) {
5b4e29
-		free(dp);
5b4e29
+	    /* swab desc */
5b4e29
+	    desc.type = ntohl(desc.type);
5b4e29
+	    desc.sem = ntohl(desc.sem);
5b4e29
+	    desc.indom = __ntohpmInDom(desc.indom);
5b4e29
+	    desc.units = __ntohpmUnits(desc.units);
5b4e29
+	    desc.pmid = __ntohpmID(desc.pmid);
5b4e29
+
5b4e29
+	    if ((sts = __pmLogAddDesc(acp, &desc)) < 0)
5b4e29
 		goto end;
5b4e29
-	    }
5b4e29
 
5b4e29
 	    /* read in the names & store in PMNS tree ... */
5b4e29
 	    if ((n = (int)__pmFread(&numnames, 1, sizeof(numnames), f)) != 
5b4e29
 		sizeof(numnames)) {
5b4e29
 		if (pmDebugOptions.logmeta) {
5b4e29
-		    fprintf(stderr, "__pmLogLoadMeta: numnames read -> %d: expected: %d\n",
5b4e29
-			    n, (int)sizeof(numnames));
5b4e29
+		    fprintf(stderr, "%s: numnames read -> %d: expected: %d\n",
5b4e29
+			    "__pmLogLoadMeta", n, (int)sizeof(numnames));
5b4e29
 		}
5b4e29
 		if (__pmFerror(f)) {
5b4e29
 		    __pmClearerr(f);
5b4e29
@@ -711,8 +749,8 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":2",
5b4e29
 		if ((n = (int)__pmFread(&len, 1, sizeof(len), f)) != 
5b4e29
 		    sizeof(len)) {
5b4e29
 		    if (pmDebugOptions.logmeta) {
5b4e29
-			fprintf(stderr, "__pmLogLoadMeta: len name[%d] read -> %d: expected: %d\n",
5b4e29
-				i, n, (int)sizeof(len));
5b4e29
+			fprintf(stderr, "%s: len name[%d] read -> %d: expected: %d\n",
5b4e29
+				"__pmLogLoadMeta", i, n, (int)sizeof(len));
5b4e29
 		    }
5b4e29
 		    if (__pmFerror(f)) {
5b4e29
 			__pmClearerr(f);
5b4e29
@@ -729,8 +767,8 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":2",
5b4e29
 
5b4e29
 		if ((n = (int)__pmFread(name, 1, len, f)) != len) {
5b4e29
 		    if (pmDebugOptions.logmeta) {
5b4e29
-			fprintf(stderr, "__pmLogLoadMeta: name[%d] read -> %d: expected: %d\n",
5b4e29
-				i, n, len);
5b4e29
+			fprintf(stderr, "%s: name[%d] read -> %d: expected: %d\n",
5b4e29
+				"__pmLogLoadMeta", i, n, len);
5b4e29
 		    }
5b4e29
 		    if (__pmFerror(f)) {
5b4e29
 			__pmClearerr(f);
5b4e29
@@ -743,36 +781,23 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":2",
5b4e29
 		name[len] = '\0';
5b4e29
 		if (pmDebugOptions.logmeta) {
5b4e29
 		    char	strbuf[20];
5b4e29
-		    fprintf(stderr, "__pmLogLoadMeta: PMID: %s name: %s\n",
5b4e29
-			    pmIDStr_r(dp->pmid, strbuf, sizeof(strbuf)), name);
5b4e29
+		    fprintf(stderr, "%s: PMID: %s name: %s\n",
5b4e29
+			    "__pmLogLoadMeta",
5b4e29
+			    pmIDStr_r(desc.pmid, strbuf, sizeof(strbuf)), name);
5b4e29
 		}
5b4e29
-		/* Add the new PMNS node */
5b4e29
-		if ((sts = __pmAddPMNSNode(lcp->l_pmns, dp->pmid, name)) < 0) {
5b4e29
-		    /*
5b4e29
-		     * If we see a duplicate name with a different PMID, its a
5b4e29
-		     * recoverable error.
5b4e29
-		     * We wont be able to see all of the data in the log, but
5b4e29
-		     * its better to provide access to some rather than none,
5b4e29
-		     * esp. when only one or two metric IDs may be corrupted
5b4e29
-		     * in this way (which we may not be interested in anyway).
5b4e29
-		     */
5b4e29
-		    if (sts != PM_ERR_PMID)
5b4e29
-			goto end;
5b4e29
-		} 
5b4e29
+
5b4e29
+		/* Add the new PMNS node into this context */
5b4e29
+		if ((sts = __pmLogAddPMNSNode(acp, desc.pmid, name)) < 0)
5b4e29
+		    goto end;
5b4e29
 	    }/*for*/
5b4e29
 	}
5b4e29
 	else if (h.type == TYPE_INDOM) {
5b4e29
-	    int			*tbuf;
5b4e29
-	    pmInDom		indom;
5b4e29
-	    pmTimeval		*when;
5b4e29
-	    int			numinst;
5b4e29
-	    int			*instlist;
5b4e29
-	    char		**namelist;
5b4e29
+	    pmTimeval		*tv;
5b4e29
+	    pmTimespec		when;
5b4e29
+	    pmInResult		in;
5b4e29
 	    char		*namebase;
5b4e29
-	    int			*stridx;
5b4e29
-	    int			i;
5b4e29
-	    int			k;
5b4e29
-	    int			allinbuf = 0;
5b4e29
+	    int			*tbuf, *stridx;
5b4e29
+	    int			i, k, allinbuf = 0;
5b4e29
 
5b4e29
 PM_FAULT_POINT("libpcp/" __FILE__ ":3", PM_FAULT_ALLOC);
5b4e29
 	    if ((tbuf = (int *)malloc(rlen)) == NULL) {
5b4e29
@@ -781,8 +806,8 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":3",
5b4e29
 	    }
5b4e29
 	    if ((n = (int)__pmFread(tbuf, 1, rlen, f)) != rlen) {
5b4e29
 		if (pmDebugOptions.logmeta) {
5b4e29
-		    fprintf(stderr, "__pmLogLoadMeta: indom read -> %d: expected: %d\n",
5b4e29
-			    n, rlen);
5b4e29
+		    fprintf(stderr, "%s: indom read -> %d: expected: %d\n",
5b4e29
+			    "__pmLogLoadMeta", n, rlen);
5b4e29
 		}
5b4e29
 		if (__pmFerror(f)) {
5b4e29
 		    __pmClearerr(f);
5b4e29
@@ -795,44 +820,44 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":3",
5b4e29
 	    }
5b4e29
 
5b4e29
 	    k = 0;
5b4e29
-	    when = (pmTimeval *)&tbuf[k];
5b4e29
-	    when->tv_sec = ntohl(when->tv_sec);
5b4e29
-	    when->tv_usec = ntohl(when->tv_usec);
5b4e29
-	    k += sizeof(*when)/sizeof(int);
5b4e29
-	    indom = __ntohpmInDom((unsigned int)tbuf[k++]);
5b4e29
-	    numinst = ntohl(tbuf[k++]);
5b4e29
-	    if (numinst > 0) {
5b4e29
-		instlist = &tbuf[k];
5b4e29
-		k += numinst;
5b4e29
+	    tv = (pmTimeval *)&tbuf[k];
5b4e29
+	    when.tv_sec = ntohl(tv->tv_sec);
5b4e29
+	    when.tv_nsec = ntohl(tv->tv_usec) * 1000;
5b4e29
+	    k += sizeof(*tv)/sizeof(int);
5b4e29
+	    in.indom = __ntohpmInDom((unsigned int)tbuf[k++]);
5b4e29
+	    in.numinst = ntohl(tbuf[k++]);
5b4e29
+	    if (in.numinst > 0) {
5b4e29
+		in.instlist = &tbuf[k];
5b4e29
+		k += in.numinst;
5b4e29
 		stridx = &tbuf[k];
5b4e29
 #if defined(HAVE_32BIT_PTR)
5b4e29
-		namelist = (char **)stridx;
5b4e29
+		in.namelist = (char **)stridx;
5b4e29
 		allinbuf = 1; /* allocation is all in tbuf */
5b4e29
 #else
5b4e29
 		allinbuf = 0; /* allocation for namelist + tbuf */
5b4e29
 		/* need to allocate to hold the pointers */
5b4e29
 PM_FAULT_POINT("libpcp/" __FILE__ ":4", PM_FAULT_ALLOC);
5b4e29
-		namelist = (char **)malloc(numinst*sizeof(char*));
5b4e29
-		if (namelist == NULL) {
5b4e29
+		in.namelist = (char **)malloc(in.numinst * sizeof(char*));
5b4e29
+		if (in.namelist == NULL) {
5b4e29
 		    sts = -oserror();
5b4e29
 		    free(tbuf);
5b4e29
 		    goto end;
5b4e29
 		}
5b4e29
 #endif
5b4e29
-		k += numinst;
5b4e29
+		k += in.numinst;
5b4e29
 		namebase = (char *)&tbuf[k];
5b4e29
-	        for (i = 0; i < numinst; i++) {
5b4e29
-		    instlist[i] = ntohl(instlist[i]);
5b4e29
-	            namelist[i] = &namebase[ntohl(stridx[i])];
5b4e29
+	        for (i = 0; i < in.numinst; i++) {
5b4e29
+		    in.instlist[i] = ntohl(in.instlist[i]);
5b4e29
+	            in.namelist[i] = &namebase[ntohl(stridx[i])];
5b4e29
 		}
5b4e29
-		if ((sts = addindom(lcp, indom, when, numinst, instlist, namelist, tbuf, allinbuf)) < 0)
5b4e29
+		if ((sts = __pmLogAddInDom(acp, &when, &in, tbuf, allinbuf)) < 0)
5b4e29
 		    goto end;
5b4e29
 		/* If this indom was a duplicate, then we need to free tbuf and
5b4e29
 		   namelist, as appropriate. */
5b4e29
 		if (sts == PMLOGPUTINDOM_DUP) {
5b4e29
 		    free(tbuf);
5b4e29
-		    if (namelist != NULL && !allinbuf)
5b4e29
-			free(namelist);
5b4e29
+		    if (in.namelist != NULL && !allinbuf)
5b4e29
+			free(in.namelist);
5b4e29
 		}
5b4e29
 	    }
5b4e29
 	    else {
5b4e29
@@ -860,8 +885,8 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":11",
5b4e29
 	    }
5b4e29
 	    if ((n = (int)__pmFread(tbuf, 1, rlen, f)) != rlen) {
5b4e29
 		if (pmDebugOptions.logmeta) {
5b4e29
-		    fprintf(stderr, "__pmLogLoadMeta: label read -> %d: expected: %d\n",
5b4e29
-			    n, rlen);
5b4e29
+		    fprintf(stderr, "%s: label read -> %d: expected: %d\n",
5b4e29
+			    "__pmLogLoadMeta", n, rlen);
5b4e29
 		}
5b4e29
 		if (__pmFerror(f)) {
5b4e29
 		    __pmClearerr(f);
5b4e29
@@ -908,7 +933,8 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":11",
5b4e29
 
5b4e29
 		if (jsonlen < 0 || jsonlen > PM_MAXLABELJSONLEN) {
5b4e29
 		    if (pmDebugOptions.logmeta)
5b4e29
-			fprintf(stderr, "__pmLogLoadMeta: corrupted json in labelset. jsonlen=%d\n", jsonlen);
5b4e29
+			fprintf(stderr, "%s: corrupted json in labelset. jsonlen=%d\n",
5b4e29
+					"__pmLogLoadMeta", jsonlen);
5b4e29
 		    sts = PM_ERR_LOGREC;
5b4e29
 		    free(labelsets);
5b4e29
 		    free(tbuf);
5b4e29
@@ -935,7 +961,8 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":11",
5b4e29
 		    if (nlabels > PM_MAXLABELS || k + nlabels * sizeof(pmLabel) > rlen) {
5b4e29
 			/* corrupt archive metadata detected. GH #475 */
5b4e29
 			if (pmDebugOptions.logmeta)
5b4e29
-			    fprintf(stderr, "__pmLogLoadMeta: corrupted labelset. nlabels=%d\n", nlabels);
5b4e29
+			    fprintf(stderr, "%s: corrupted labelset. nlabels=%d\n",
5b4e29
+					    "__pmLogLoadMeta", nlabels);
5b4e29
 			sts = PM_ERR_LOGREC;
5b4e29
 			free(labelsets);
5b4e29
 			free(tbuf);
5b4e29
@@ -975,8 +1002,8 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":16",
5b4e29
 	    }
5b4e29
 	    if ((n = (int)__pmFread(tbuf, 1, rlen, f)) != rlen) {
5b4e29
 		if (pmDebugOptions.logmeta) {
5b4e29
-		    fprintf(stderr, "__pmLogLoadMeta: text read -> %d: expected: %d\n",
5b4e29
-			    n, rlen);
5b4e29
+		    fprintf(stderr, "%s: text read -> %d: expected: %d\n",
5b4e29
+				    "__pmLogLoadMeta", n, rlen);
5b4e29
 		}
5b4e29
 		if (__pmFerror(f)) {
5b4e29
 		    __pmClearerr(f);
5b4e29
@@ -1005,8 +1032,8 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":16",
5b4e29
 		ident = __ntohpmID(*((unsigned int *)&tbuf[k]));
5b4e29
 	    else {
5b4e29
 		if (pmDebugOptions.logmeta) {
5b4e29
-		    fprintf(stderr, "__pmLogLoadMeta: bad text ident -> %x\n",
5b4e29
-			    type);
5b4e29
+		    fprintf(stderr, "%s: bad text ident -> %x\n",
5b4e29
+				    "__pmLogLoadMeta", type);
5b4e29
 		}
5b4e29
 		free(tbuf);
5b4e29
 		continue;
5b4e29
@@ -1024,8 +1051,9 @@ PM_FAULT_POINT("libpcp/" __FILE__ ":16",
5b4e29
 	check = ntohl(check);
5b4e29
 	if (n != sizeof(check) || h.len != check) {
5b4e29
 	    if (pmDebugOptions.logmeta) {
5b4e29
-		fprintf(stderr, "__pmLogLoadMeta: trailer read -> %d or len=%d: expected %d @ offset=%d\n",
5b4e29
-		    n, check, h.len, (int)(__pmFtell(f) - sizeof(check)));
5b4e29
+		fprintf(stderr, "%s: trailer read -> %d or len=%d: "
5b4e29
+				"expected %d @ offset=%d\n", "__pmLogLoadMeta",
5b4e29
+			n, check, h.len, (int)(__pmFtell(f) - sizeof(check)));
5b4e29
 	    }
5b4e29
 	    if (__pmFerror(f)) {
5b4e29
 		__pmClearerr(f);
5b4e29
@@ -1046,7 +1074,7 @@ end:
5b4e29
     if (sts == 0) {
5b4e29
 	if (numpmid == 0) {
5b4e29
 	    if (pmDebugOptions.logmeta) {
5b4e29
-		fprintf(stderr, "__pmLogLoadMeta: no metrics found?\n");
5b4e29
+		fprintf(stderr, "%s: no metrics found?\n", "__pmLogLoadMeta");
5b4e29
 	    }
5b4e29
 	    sts = PM_ERR_LOGREC;
5b4e29
 	}
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp/src/logutil.c pcp-5.0.2/src/libpcp/src/logutil.c
5b4e29
--- pcp-5.0.2.orig/src/libpcp/src/logutil.c	2018-07-08 10:58:08.000000000 +1000
5b4e29
+++ pcp-5.0.2/src/libpcp/src/logutil.c	2020-02-03 13:23:15.263762911 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2012-2017 Red Hat.
5b4e29
+ * Copyright (c) 2012-2017,2020 Red Hat.
5b4e29
  * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc.  All Rights Reserved.
5b4e29
  * 
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
@@ -764,6 +764,22 @@ __pmLogClose(__pmArchCtl *acp)
5b4e29
 }
5b4e29
 
5b4e29
 int
5b4e29
+__pmLogAddVolume(__pmArchCtl *acp, unsigned int vol)
5b4e29
+{
5b4e29
+    __pmLogCtl	*lcp = acp->ac_log;
5b4e29
+
5b4e29
+    if (lcp->l_minvol == -1) {
5b4e29
+	lcp->l_minvol = vol;
5b4e29
+	lcp->l_maxvol = vol;
5b4e29
+    } else if (vol < lcp->l_minvol) {
5b4e29
+	lcp->l_minvol = vol;
5b4e29
+    } else if (vol > lcp->l_maxvol) {
5b4e29
+	lcp->l_maxvol = vol;
5b4e29
+    }
5b4e29
+    return 0;
5b4e29
+}
5b4e29
+
5b4e29
+int
5b4e29
 __pmLogLoadLabel(__pmArchCtl *acp, const char *name)
5b4e29
 {
5b4e29
     __pmLogCtl	*lcp = acp->ac_log;
5b4e29
@@ -876,21 +892,14 @@ __pmLogLoadLabel(__pmArchCtl *acp, const
5b4e29
 		}
5b4e29
 	    }
5b4e29
 	    else {
5b4e29
-		char	*q;
5b4e29
-		int	vol;
5b4e29
-		vol = (int)strtol(tp, &q, 10);
5b4e29
+		char		*q;
5b4e29
+		unsigned int	vol;
5b4e29
+
5b4e29
+		vol = (unsigned int)strtoul(tp, &q, 10);
5b4e29
 		if (*q == '\0') {
5b4e29
 		    exists = 1;
5b4e29
-		    if (lcp->l_minvol == -1) {
5b4e29
-			lcp->l_minvol = vol;
5b4e29
-			lcp->l_maxvol = vol;
5b4e29
-		    }
5b4e29
-		    else {
5b4e29
-			if (vol < lcp->l_minvol)
5b4e29
-			    lcp->l_minvol = vol;
5b4e29
-			if (vol > lcp->l_maxvol)
5b4e29
-			    lcp->l_maxvol = vol;
5b4e29
-		    }
5b4e29
+		    if ((sts = __pmLogAddVolume(acp, vol)) < 0)
5b4e29
+			goto cleanup;
5b4e29
 		}
5b4e29
 	    }
5b4e29
 	}
5b4e29
@@ -2282,7 +2291,7 @@ __pmLogSetTime(__pmContext *ctxp)
5b4e29
 	int		match = 0;
5b4e29
 	int		vol;
5b4e29
 	int		numti = lcp->l_numti;
5b4e29
-	__pmFILE		*f;
5b4e29
+	__pmFILE	*f;
5b4e29
 	__pmLogTI	*tip = lcp->l_ti;
5b4e29
 	double		t_lo;
5b4e29
 	struct stat	sbuf;
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/discover.c pcp-5.0.2/src/libpcp_web/src/discover.c
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/discover.c	2019-12-10 17:04:20.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/discover.c	2020-02-03 13:36:11.958637560 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2018-2019 Red Hat.
5b4e29
+ * Copyright (c) 2018-2020 Red Hat.
5b4e29
  *
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
  * under the terms of the GNU Lesser General Public License as published
5b4e29
@@ -14,6 +14,8 @@
5b4e29
 #include "discover.h"
5b4e29
 #include "slots.h"
5b4e29
 #include "util.h"
5b4e29
+#include <dirent.h>
5b4e29
+#include <sys/stat.h>
5b4e29
 
5b4e29
 /* Decode various archive metafile records (desc, indom, labels, helptext) */
5b4e29
 static int pmDiscoverDecodeMetaDesc(uint32_t *, int, pmDesc *, int *, char ***);
5b4e29
@@ -24,11 +26,15 @@ static int pmDiscoverDecodeMetaLabelSet(
5b4e29
 /* array of registered callbacks, see pmDiscoverSetup() */
5b4e29
 static int discoverCallBackTableSize;
5b4e29
 static pmDiscoverCallBacks **discoverCallBackTable;
5b4e29
+static char *pmDiscoverFlagsStr(pmDiscover *);
5b4e29
 
5b4e29
 /* internal hash table of discovered paths */
5b4e29
-#define PM_DISCOVER_HASHTAB_SIZE 64
5b4e29
+#define PM_DISCOVER_HASHTAB_SIZE 16
5b4e29
 static pmDiscover *discover_hashtable[PM_DISCOVER_HASHTAB_SIZE];
5b4e29
 
5b4e29
+/* pmlogger_daily log-roll lock count */
5b4e29
+static int lockcnt = 0;
5b4e29
+
5b4e29
 /* FNV string hash algorithm. Return unsigned in range 0 .. limit-1 */
5b4e29
 static unsigned int
5b4e29
 strhash(const char *s, unsigned int limit)
5b4e29
@@ -43,18 +49,38 @@ strhash(const char *s, unsigned int limi
5b4e29
     return h % limit;
5b4e29
 }
5b4e29
 
5b4e29
+/* ctime string - note static buf is returned */
5b4e29
+static char *
5b4e29
+stamp(void)
5b4e29
+{
5b4e29
+    time_t now = time(NULL);
5b4e29
+    char *p, *c = ctime(&now;;
5b4e29
+
5b4e29
+    if ((p = strrchr(c, '\n')) != NULL)
5b4e29
+    	*p = '\0';
5b4e29
+    return c;
5b4e29
+}
5b4e29
+
5b4e29
 /*
5b4e29
- * Lookup or Add a discovered file path (directory or PCP archive file)
5b4e29
+ * Lookup or Add a discovered file path (directory or PCP archive file).
5b4e29
+ * Note: the fullpath suffix (.meta, .[0-9]+) should already be stripped.
5b4e29
  * Return path table entry (new or existing).
5b4e29
  */
5b4e29
 static pmDiscover *
5b4e29
-pmDiscoverLookupAdd(const char *path, pmDiscoverModule *module, void *arg)
5b4e29
+pmDiscoverLookupAdd(const char *fullpath, pmDiscoverModule *module, void *arg)
5b4e29
 {
5b4e29
     pmDiscover		*p, *h;
5b4e29
-    unsigned int	k = strhash(path, PM_DISCOVER_HASHTAB_SIZE);
5b4e29
+    unsigned int	k;
5b4e29
+    sds			name;
5b4e29
+
5b4e29
+    name = sdsnew(fullpath);
5b4e29
+    k = strhash(name, PM_DISCOVER_HASHTAB_SIZE);
5b4e29
+
5b4e29
+    if (pmDebugOptions.discovery)
5b4e29
+	fprintf(stderr, "pmDiscoverLookupAdd: name=%s\n", name);
5b4e29
 
5b4e29
     for (p = NULL, h = discover_hashtable[k]; h != NULL; p = h, h = h->next) {
5b4e29
-    	if (strcmp(h->context.name, path) == 0)
5b4e29
+    	if (sdscmp(h->context.name, name) == 0)
5b4e29
 	    break;
5b4e29
     }
5b4e29
 
5b4e29
@@ -65,14 +91,24 @@ pmDiscoverLookupAdd(const char *path, pm
5b4e29
 	h->ctx = -1; /* no PMAPI context initially */
5b4e29
 	h->flags = PM_DISCOVER_FLAGS_NEW;
5b4e29
 	h->context.type = PM_CONTEXT_ARCHIVE;
5b4e29
-	h->context.name = sdsnew(path);
5b4e29
+	h->context.name = name;
5b4e29
 	h->module = module;
5b4e29
 	h->data = arg;
5b4e29
 	if (p == NULL)
5b4e29
 	    discover_hashtable[k] = h;
5b4e29
 	else
5b4e29
 	    p->next = h;
5b4e29
+	if (pmDebugOptions.discovery)
5b4e29
+	    fprintf(stderr, "pmDiscoverLookupAdd: --> new entry %s\n", name);
5b4e29
+
5b4e29
+    }
5b4e29
+    else {
5b4e29
+	/* already in hash table, so free the buffer */
5b4e29
+	if (pmDebugOptions.discovery)
5b4e29
+	    fprintf(stderr, "pmDiscoverLookupAdd: --> existing entry %s\n", name);
5b4e29
+    	sdsfree(name);
5b4e29
     }
5b4e29
+
5b4e29
     return h;
5b4e29
 }
5b4e29
 
5b4e29
@@ -82,12 +118,6 @@ pmDiscoverLookup(const char *path)
5b4e29
     return pmDiscoverLookupAdd(path, NULL, NULL);
5b4e29
 }
5b4e29
 
5b4e29
-static pmDiscover *
5b4e29
-pmDiscoverAdd(const char *path, pmDiscoverModule *module, void *arg)
5b4e29
-{
5b4e29
-    return pmDiscoverLookupAdd(path, module, arg);
5b4e29
-}
5b4e29
-
5b4e29
 static void
5b4e29
 pmDiscoverFree(pmDiscover *p)
5b4e29
 {
5b4e29
@@ -101,39 +131,42 @@ pmDiscoverFree(pmDiscover *p)
5b4e29
 	sdsfree(p->context.source);
5b4e29
     if (p->context.labelset)
5b4e29
 	pmFreeLabelSets(p->context.labelset, 1);
5b4e29
+    if (p->event_handle) {
5b4e29
+	uv_fs_event_stop(p->event_handle);
5b4e29
+	free(p->event_handle);
5b4e29
+	p->event_handle = NULL;
5b4e29
+    }
5b4e29
+
5b4e29
     memset(p, 0, sizeof(*p));
5b4e29
     free(p);
5b4e29
 }
5b4e29
 
5b4e29
 /*
5b4e29
- * Delete tracking of a previously discovered path. Frees resources and
5b4e29
- * destroy PCP context (if any).
5b4e29
+ * Traverse and invoke callback for all paths matching any bit
5b4e29
+ * in the flags bitmap. Callback can be NULL to just get a count.
5b4e29
+ * Return count of matching paths, may be 0.
5b4e29
  */
5b4e29
-static void
5b4e29
-pmDiscoverDelete(sds path)
5b4e29
+static int
5b4e29
+pmDiscoverTraverse(unsigned int flags, void (*callback)(pmDiscover *))
5b4e29
 {
5b4e29
-    pmDiscover		*p, *h;
5b4e29
-    unsigned int	k = strhash(path, PM_DISCOVER_HASHTAB_SIZE);
5b4e29
+    int			count = 0, i;
5b4e29
+    pmDiscover		*p;
5b4e29
 
5b4e29
-    for (p = NULL, h = discover_hashtable[k]; h != NULL; p = h, h = h->next) {
5b4e29
-    	if (sdscmp(h->context.name, path) == 0) {
5b4e29
-	    if (p == NULL)
5b4e29
-	    	discover_hashtable[k] = NULL;
5b4e29
-	    else
5b4e29
-	    	p->next = h->next;
5b4e29
-	    pmDiscoverFree(h);
5b4e29
-	    break;
5b4e29
+    for (i = 0; i < PM_DISCOVER_HASHTAB_SIZE; i++) {
5b4e29
+    	for (p = discover_hashtable[i]; p; p = p->next) {
5b4e29
+	    if (p->flags & flags) {
5b4e29
+		if (callback)
5b4e29
+		    callback(p);
5b4e29
+		count++;
5b4e29
+	    }
5b4e29
 	}
5b4e29
     }
5b4e29
+    return count;
5b4e29
 }
5b4e29
 
5b4e29
-/*
5b4e29
- * Traverse and invoke callback for all paths matching any bit
5b4e29
- * in the flags bitmap. Callback can be NULL to just get a count.
5b4e29
- * Return count of matching paths, may be 0.
5b4e29
- */
5b4e29
+/* as above, but with an extra (void *)arg passed to the cb */
5b4e29
 static int
5b4e29
-pmDiscoverTraverse(unsigned int flags, void (*callback)(pmDiscover *))
5b4e29
+pmDiscoverTraverseArg(unsigned int flags, void (*callback)(pmDiscover *, void *), void *arg)
5b4e29
 {
5b4e29
     int			count = 0, i;
5b4e29
     pmDiscover		*p;
5b4e29
@@ -142,7 +175,7 @@ pmDiscoverTraverse(unsigned int flags, v
5b4e29
     	for (p = discover_hashtable[i]; p; p = p->next) {
5b4e29
 	    if (p->flags & flags) {
5b4e29
 		if (callback)
5b4e29
-		    callback(p);
5b4e29
+		    callback(p, arg);
5b4e29
 		count++;
5b4e29
 	    }
5b4e29
 	}
5b4e29
@@ -150,6 +183,7 @@ pmDiscoverTraverse(unsigned int flags, v
5b4e29
     return count;
5b4e29
 }
5b4e29
 
5b4e29
+
5b4e29
 /*
5b4e29
  * Traverse and purge deleted entries
5b4e29
  * Return count of purged entries.
5b4e29
@@ -173,6 +207,9 @@ pmDiscoverPurgeDeleted(void)
5b4e29
 		    prev->next = next;
5b4e29
 		else
5b4e29
 		    discover_hashtable[i] = next;
5b4e29
+		if (pmDebugOptions.discovery)
5b4e29
+		    fprintf(stderr, "pmDiscoverPurgeDeleted: deleted %s %s\n",
5b4e29
+		    	p->context.name, pmDiscoverFlagsStr(p));
5b4e29
 		pmDiscoverFree(p);
5b4e29
 		count++;
5b4e29
 	    }
5b4e29
@@ -180,14 +217,32 @@ pmDiscoverPurgeDeleted(void)
5b4e29
 	}
5b4e29
     }
5b4e29
 
5b4e29
-    if (pmDebugOptions.discovery)
5b4e29
-	fprintf(stderr, "%s: purged %d entries\n",
5b4e29
-			"pmDiscoverPurgeDeleted", count);
5b4e29
-
5b4e29
     return count;
5b4e29
 }
5b4e29
 
5b4e29
 /*
5b4e29
+ * if string ends with given suffix then return pointer
5b4e29
+ * to start of suffix in string, else NULL
5b4e29
+ */
5b4e29
+static char *
5b4e29
+strsuffix(char *s, const char *suffix)
5b4e29
+{
5b4e29
+    int slen, suflen;
5b4e29
+    char *ret = NULL;
5b4e29
+
5b4e29
+    if (s && suffix) {
5b4e29
+    	slen = strlen(s);
5b4e29
+	suflen = strlen(suffix);
5b4e29
+	if (slen >= suflen) {
5b4e29
+	    ret = s + (slen - suflen);
5b4e29
+	    if (strncmp(ret, suffix, suflen) != 0)
5b4e29
+	    	ret = NULL;
5b4e29
+	}
5b4e29
+    }
5b4e29
+    return ret;
5b4e29
+}
5b4e29
+
5b4e29
+/*
5b4e29
  * Discover dirs and archives - add new entries or refresh existing.
5b4e29
  * Call this for each top-level directory. Discovered paths are not
5b4e29
  * automatically monitored. After discovery, need to traverse and
5b4e29
@@ -196,44 +251,88 @@ pmDiscoverPurgeDeleted(void)
5b4e29
 static int
5b4e29
 pmDiscoverArchives(const char *dir, pmDiscoverModule *module, void *arg)
5b4e29
 {
5b4e29
-    uv_fs_t		sreq, req;
5b4e29
-    uv_dirent_t		dent;
5b4e29
-    uv_stat_t		*s;
5b4e29
+    DIR			*dirp;
5b4e29
+    struct dirent	*dent;
5b4e29
+    struct stat		*s;
5b4e29
+    struct stat		statbuf;
5b4e29
     pmDiscover		*a;
5b4e29
+    char		*suffix;
5b4e29
     char		path[MAXNAMELEN];
5b4e29
-    char		basepath[MAXNAMELEN];
5b4e29
     int			sep = pmPathSeparator();
5b4e29
+    int			vol;
5b4e29
+
5b4e29
+    /*
5b4e29
+     * note: pmDiscoverLookupAdd sets PM_DISCOVER_FLAGS_NEW
5b4e29
+     * if this is a newly discovered archive or directory
5b4e29
+     */
5b4e29
+    a = pmDiscoverLookupAdd(dir, module, arg);
5b4e29
+    a->flags |= PM_DISCOVER_FLAGS_DIRECTORY;
5b4e29
 
5b4e29
-    if (uv_fs_scandir(NULL, &req, dir, 0, NULL) < 0)
5b4e29
+    if ((dirp = opendir(dir)) == NULL) {
5b4e29
+	if (pmDebugOptions.discovery)
5b4e29
+	    fprintf(stderr, "pmDiscoverArchives: opendir %s failed %s: err %d\n", dir, path, errno);
5b4e29
 	return -ESRCH;
5b4e29
+    }
5b4e29
 
5b4e29
-    a = pmDiscoverAdd(dir, module, arg);
5b4e29
-    a->flags |= PM_DISCOVER_FLAGS_DIRECTORY;
5b4e29
+    while ((dent = readdir(dirp)) != NULL) {
5b4e29
+	if (dent->d_name[0] == '.')
5b4e29
+	    continue;
5b4e29
+	pmsprintf(path, sizeof(path), "%s%c%s", dir, sep, dent->d_name);
5b4e29
+
5b4e29
+	if (pmDebugOptions.discovery)
5b4e29
+	    fprintf(stderr, "pmDiscoverArchives: readdir found %s\n", path);
5b4e29
 
5b4e29
-    while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
5b4e29
-	pmsprintf(path, sizeof(path), "%s%c%s", dir, sep, dent.name);
5b4e29
-	if (uv_fs_stat(NULL, &sreq, path, NULL) < 0)
5b4e29
+	if (stat(path, &statbuf) < 0) {
5b4e29
+	    if (pmDebugOptions.discovery)
5b4e29
+		fprintf(stderr, "pmDiscoverArchives: stat failed %s, err %d\n", path, errno);
5b4e29
 	    continue;
5b4e29
-	s = &sreq.statbuf;
5b4e29
-	strncpy(basepath, path, sizeof(basepath)); /* __pmLogBaseName modifies it's argument */
5b4e29
-	if (S_ISREG(s->st_mode) && __pmLogBaseName(basepath) != NULL) {
5b4e29
-	    /*
5b4e29
-	     * An archive file (index, meta or data vol). If compressed, then
5b4e29
-	     * it is read-only and we don't have to monitor it for growth.
5b4e29
-	     */
5b4e29
-	    a = pmDiscoverAdd(path, module, arg);
5b4e29
-	    a->flags &= ~PM_DISCOVER_FLAGS_DELETED;
5b4e29
+	}
5b4e29
 
5b4e29
-	    if (strstr(path, ".meta"))
5b4e29
-	    	a->flags |= PM_DISCOVER_FLAGS_META;
5b4e29
-	    else if (strstr(path, ".index"))
5b4e29
-	    	a->flags |= PM_DISCOVER_FLAGS_INDEX;
5b4e29
-	    else
5b4e29
-	    	a->flags |= PM_DISCOVER_FLAGS_DATAVOL;
5b4e29
-
5b4e29
-	    /* compare to libpcp io.c for suffix list */
5b4e29
-	    if (strstr(path, ".xz") || strstr(path, ".gz"))
5b4e29
-	    	a->flags |= PM_DISCOVER_FLAGS_COMPRESSED;
5b4e29
+	s = &statbuf;
5b4e29
+	if (S_ISREG(s->st_mode)) {
5b4e29
+	    if ((suffix = strsuffix(path, ".meta")) != NULL) {
5b4e29
+		/*
5b4e29
+		 * An uncompressed PCP archive meta file. Track the meta
5b4e29
+		 * file - the matching logvol filename varies because logvols
5b4e29
+		 * are periodically rolled by pmlogger. Importantly, process all
5b4e29
+		 * available metadata to EOF before processing any logvol data.
5b4e29
+		 */
5b4e29
+		*suffix = '\0'; /* strip suffix from path giving archive name */
5b4e29
+		a = pmDiscoverLookupAdd(path, module, arg);
5b4e29
+
5b4e29
+		/*
5b4e29
+		 * note: pmDiscoverLookupAdd sets PM_DISCOVER_FLAGS_NEW
5b4e29
+		 * if this is a newly discovered archive, otherwise we're
5b4e29
+		 * already tracking this archive.
5b4e29
+		 */
5b4e29
+		a->flags |= PM_DISCOVER_FLAGS_META;
5b4e29
+	    }
5b4e29
+	    else if ((suffix = __pmLogBaseNameVol(path, &vol)) != NULL && vol >= 0) {
5b4e29
+		/*
5b4e29
+		 * An archive logvol. This logvol may have been created since
5b4e29
+		 * the context was first opened. Update the context maxvol
5b4e29
+		 * to be sure pmFetchArchive can switch to it in due course.
5b4e29
+		 */
5b4e29
+		if ((a = pmDiscoverLookup(path)) != NULL) {
5b4e29
+		    a->flags |= PM_DISCOVER_FLAGS_DATAVOL;
5b4e29
+		    /* ensure archive context knows about this volume */
5b4e29
+		    if (pmDebugOptions.discovery)
5b4e29
+			fprintf(stderr, "pmDiscoverArchives: found logvol %s %s vol=%d\n",
5b4e29
+			    a->context.name, pmDiscoverFlagsStr(a), vol);
5b4e29
+		    if (a->ctx >= 0 && vol >= 0) {
5b4e29
+			__pmContext *ctxp = __pmHandleToPtr(a->ctx);
5b4e29
+			__pmArchCtl *acp = ctxp->c_archctl;
5b4e29
+
5b4e29
+		    	__pmLogAddVolume(acp, vol);
5b4e29
+			PM_UNLOCK(ctxp->c_lock);
5b4e29
+		    }
5b4e29
+		    if (pmDebugOptions.discovery)
5b4e29
+			fprintf(stderr, "pmDiscoverArchives: added logvol %s %s vol=%d\n",
5b4e29
+			    a->context.name, pmDiscoverFlagsStr(a), vol);
5b4e29
+		}
5b4e29
+	    } else if (pmDebugOptions.discovery) {
5b4e29
+		fprintf(stderr, "pmDiscoverArchives: ignored regular file %s\n", path);
5b4e29
+	    }
5b4e29
 	}
5b4e29
 	else if (S_ISDIR(s->st_mode)) {
5b4e29
 	    /*
5b4e29
@@ -241,29 +340,117 @@ pmDiscoverArchives(const char *dir, pmDi
5b4e29
 	     */
5b4e29
 	    pmDiscoverArchives(path, module, arg);
5b4e29
 	}
5b4e29
-	uv_fs_req_cleanup(&sreq);
5b4e29
     }
5b4e29
-    uv_fs_req_cleanup(&req;;
5b4e29
+    if (dirp)
5b4e29
+	closedir(dirp);
5b4e29
 
5b4e29
     /* success */
5b4e29
     return 0;
5b4e29
 }
5b4e29
 
5b4e29
+/*
5b4e29
+ * Return 1 if monitored path has been deleted.
5b4e29
+ * For archives, we only check the meta file because
5b4e29
+ * a logvol can be deleted (e.g. via compression when
5b4e29
+ * the logvol is rolled to a new volume) without
5b4e29
+ * actually deleting the archive.
5b4e29
+ */
5b4e29
+static int
5b4e29
+is_deleted(pmDiscover *p, struct stat *sbuf)
5b4e29
+{
5b4e29
+    int			ret = 0;
5b4e29
+
5b4e29
+    if (p->flags & PM_DISCOVER_FLAGS_DIRECTORY) {
5b4e29
+	if (stat(p->context.name, sbuf) < 0)
5b4e29
+	    ret = 1; /* directory has been deleted */
5b4e29
+    }
5b4e29
+
5b4e29
+    if (p->flags & (PM_DISCOVER_FLAGS_META|PM_DISCOVER_FLAGS_DATAVOL)) {
5b4e29
+    	sds meta = sdsnew(p->context.name);
5b4e29
+	meta = sdscat(meta, ".meta");
5b4e29
+	if (stat(meta, sbuf) < 0) {
5b4e29
+	    /*
5b4e29
+	     * Archive metadata file has been deleted (or compressed)
5b4e29
+	     * hence consider the archive to be deleted because there
5b4e29
+	     * is no more data to logtail.
5b4e29
+	     */
5b4e29
+	    ret = 1;
5b4e29
+	}
5b4e29
+	sdsfree(meta);
5b4e29
+    }
5b4e29
+
5b4e29
+    if (pmDebugOptions.discovery) {
5b4e29
+	fprintf(stderr, "is_deleted: checking %s (%s) -> %s\n",
5b4e29
+		p->context.name, pmDiscoverFlagsStr(p), ret ? "DELETED" : "no");
5b4e29
+    }
5b4e29
+
5b4e29
+    return ret;
5b4e29
+}
5b4e29
+
5b4e29
+static void
5b4e29
+logdir_is_locked_callBack(pmDiscover *p, void *arg)
5b4e29
+{
5b4e29
+    int			*cntp = (int *)arg;
5b4e29
+    char		sep = pmPathSeparator();
5b4e29
+    char		path[MAXNAMELEN];
5b4e29
+
5b4e29
+    pmsprintf(path, sizeof(path), "%s%c%s", p->context.name, sep, "lock");
5b4e29
+    if (access(path, F_OK) == 0)
5b4e29
+    	(*cntp)++;
5b4e29
+}
5b4e29
+
5b4e29
+static void
5b4e29
+check_deleted(pmDiscover *p)
5b4e29
+{
5b4e29
+    struct stat sbuf;
5b4e29
+    if (!(p->flags & PM_DISCOVER_FLAGS_DELETED) && is_deleted(p, &sbuf))
5b4e29
+    	p->flags |= PM_DISCOVER_FLAGS_DELETED;
5b4e29
+}
5b4e29
+
5b4e29
 static void
5b4e29
 fs_change_callBack(uv_fs_event_t *handle, const char *filename, int events, int status)
5b4e29
 {
5b4e29
     char		buffer[MAXNAMELEN];
5b4e29
     size_t		bytes = sizeof(buffer) - 1;
5b4e29
     pmDiscover		*p;
5b4e29
-    uv_fs_t		sreq;
5b4e29
+    char		*s;
5b4e29
     sds			path;
5b4e29
-    int			path_changed = 0;
5b4e29
+    int			count = 0;
5b4e29
+    struct stat		statbuf;
5b4e29
+
5b4e29
+    /*
5b4e29
+     * check if logs are currently being rolled by pmlogger_daily et al
5b4e29
+     * in any of the directories we are tracking. For mutex, the log control
5b4e29
+     * scripts use a 'lock' file in each directory as it is processed.
5b4e29
+     */
5b4e29
+    pmDiscoverTraverseArg(PM_DISCOVER_FLAGS_DIRECTORY,
5b4e29
+    	logdir_is_locked_callBack, (void *)&count);
5b4e29
+
5b4e29
+    if (lockcnt == 0 && count > 0) {
5b4e29
+	/* log-rolling has started */
5b4e29
+    	fprintf(stderr, "%s discovery callback ignored: log-rolling is now in progress\n", stamp());
5b4e29
+	lockcnt = count;
5b4e29
+	return;
5b4e29
+    }
5b4e29
+
5b4e29
+    if (lockcnt > 0 && count > 0) {
5b4e29
+	/* log-rolling is still in progress */
5b4e29
+	lockcnt = count;
5b4e29
+	return;
5b4e29
+    }
5b4e29
+
5b4e29
+    if (lockcnt > 0 && count == 0) {
5b4e29
+    	/* log-rolling is finished: check what got deleted, and then purge */
5b4e29
+    	fprintf(stderr, "%s discovery callback: finished log-rolling\n", stamp());
5b4e29
+	pmDiscoverTraverse(PM_DISCOVER_FLAGS_META|PM_DISCOVER_FLAGS_DATAVOL, check_deleted);
5b4e29
+    }
5b4e29
+    lockcnt = count;
5b4e29
 
5b4e29
     uv_fs_event_getpath(handle, buffer, &bytes);
5b4e29
     path = sdsnewlen(buffer, bytes);
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery) {
5b4e29
-	fprintf(stderr, "%s: event on %s -", "fs_change_callBack", path);
5b4e29
+	fprintf(stderr, "fs_change_callBack: event on %s -", path);
5b4e29
 	if (events & UV_RENAME)
5b4e29
 	    fprintf(stderr, " renamed");
5b4e29
 	if (events & UV_CHANGE)
5b4e29
@@ -271,38 +458,40 @@ fs_change_callBack(uv_fs_event_t *handle
5b4e29
 	fputc('\n', stderr);
5b4e29
     }
5b4e29
 
5b4e29
+    	
5b4e29
     /*
5b4e29
-     * Lookup the path, stat and update it's flags accordingly. If the
5b4e29
-     * path has been deleted, stop it's event monitor and free the req buffer.
5b4e29
-     * Then call the pmDiscovery callback.
5b4e29
+     * Strip ".meta" suffix (if any) and lookup the path. stat and update it's
5b4e29
+     * flags accordingly. If the path has been deleted, stop it's event monitor
5b4e29
+     * and free the req buffer, else call the pmDiscovery callback.
5b4e29
      */
5b4e29
-    if ((p = pmDiscoverLookup(path)) == NULL) {
5b4e29
+    if ((s = strsuffix(path, ".meta")) != NULL)
5b4e29
+	*s = '\0';
5b4e29
+
5b4e29
+    p = pmDiscoverLookup(path);
5b4e29
+    if (p && pmDebugOptions.discovery) {
5b4e29
+	fprintf(stderr, "fs_change_callBack: ---> found entry %s (%s)\n",
5b4e29
+		p->context.name, pmDiscoverFlagsStr(p));
5b4e29
+    }
5b4e29
+
5b4e29
+    if (p == NULL) {
5b4e29
 	if (pmDebugOptions.discovery)
5b4e29
-	    fprintf(stderr, "%s: filename %s lookup failed\n",
5b4e29
-		    "fs_change_callBack", filename);
5b4e29
+	    fprintf(stderr, "fs_change_callBack: %s lookup failed\n", filename);
5b4e29
     }
5b4e29
-    else if (uv_fs_stat(NULL, &sreq, p->context.name, NULL) < 0) {
5b4e29
-    	p->flags |= PM_DISCOVER_FLAGS_DELETED;
5b4e29
-	if (p->event_handle) {
5b4e29
-	    uv_fs_event_stop(p->event_handle);
5b4e29
-	    free(p->event_handle);
5b4e29
-	    p->event_handle = NULL;
5b4e29
-	}
5b4e29
+    else if (is_deleted(p, &statbuf)) {
5b4e29
 	/* path has been deleted. statbuf is invalid */
5b4e29
+    	p->flags |= PM_DISCOVER_FLAGS_DELETED;
5b4e29
 	memset(&p->statbuf, 0, sizeof(p->statbuf));
5b4e29
-	path_changed = 1;
5b4e29
-    }
5b4e29
-    else {
5b4e29
-	/* avoid spurious events. only call the callBack if it really changed */
5b4e29
-	if (p->statbuf.st_mtim.tv_sec != sreq.statbuf.st_mtim.tv_sec ||
5b4e29
-	    p->statbuf.st_mtim.tv_nsec != sreq.statbuf.st_mtim.tv_nsec)
5b4e29
-	    path_changed = 1;
5b4e29
-	p->statbuf = sreq.statbuf; /* struct copy */
5b4e29
-	uv_fs_req_cleanup(&sreq);
5b4e29
+	if (pmDebugOptions.discovery)
5b4e29
+	    fprintf(stderr, "fs_change_callBack: %s (%s) has been deleted",
5b4e29
+	    	p->context.name, pmDiscoverFlagsStr(p));
5b4e29
     }
5b4e29
 
5b4e29
-    if (p && p->changed && path_changed && !(p->flags & PM_DISCOVER_FLAGS_DELETED))
5b4e29
-	p->changed(p);
5b4e29
+    /*
5b4e29
+     * Something in the directory changed - new or deleted archive, or
5b4e29
+     * a tracked archive meta data file or logvolume grew
5b4e29
+     */
5b4e29
+    if (p)
5b4e29
+	p->changed(p); /* returns immediately if PM_DISCOVER_FLAGS_DELETED */
5b4e29
 
5b4e29
     sdsfree(path);
5b4e29
 }
5b4e29
@@ -316,9 +505,14 @@ pmDiscoverMonitor(sds path, void (*callb
5b4e29
 {
5b4e29
     discoverModuleData	*data;
5b4e29
     pmDiscover		*p;
5b4e29
+    sds			eventfilename;
5b4e29
 
5b4e29
-    if ((p = pmDiscoverLookup(path)) == NULL)
5b4e29
+    if ((p = pmDiscoverLookup(path)) == NULL) {
5b4e29
+	if (pmDebugOptions.discovery) {
5b4e29
+	    fprintf(stderr, "pmDiscoverMonitor: lookup failed for %s\n", path);
5b4e29
+	}
5b4e29
 	return -ESRCH;
5b4e29
+    }
5b4e29
     data = getDiscoverModuleData(p->module);
5b4e29
 
5b4e29
     /* save the discovery callback to be invoked */
5b4e29
@@ -330,9 +524,29 @@ pmDiscoverMonitor(sds path, void (*callb
5b4e29
 	 * Start monitoring, using given uv loop. Up to the caller to create
5b4e29
 	 * a PCP PMAPI context and to fetch/logtail in the changed callback.
5b4e29
 	 */
5b4e29
+	eventfilename = sdsnew(p->context.name);
5b4e29
 	uv_fs_event_init(data->events, p->event_handle);
5b4e29
-	uv_fs_event_start(p->event_handle, fs_change_callBack, p->context.name,
5b4e29
+
5b4e29
+	if (p->flags & PM_DISCOVER_FLAGS_DIRECTORY) {
5b4e29
+	    uv_fs_event_start(p->event_handle, fs_change_callBack, eventfilename,
5b4e29
+			    UV_FS_EVENT_WATCH_ENTRY);
5b4e29
+	}
5b4e29
+	else {
5b4e29
+	    /*
5b4e29
+	     * Monitor an archive file. This tracks the archive meta file
5b4e29
+	     * but the change callback processes both meta and logvol on
5b4e29
+	     * every callback (meta before logvol).
5b4e29
+	     */
5b4e29
+	    eventfilename = sdscat(eventfilename, ".meta");
5b4e29
+	    uv_fs_event_start(p->event_handle, fs_change_callBack, eventfilename,
5b4e29
 			UV_FS_EVENT_WATCH_ENTRY);
5b4e29
+	}
5b4e29
+
5b4e29
+	if (pmDebugOptions.discovery) {
5b4e29
+	    fprintf(stderr, "pmDiscoverMonitor: added event for %s (%s)\n",
5b4e29
+	    	eventfilename, pmDiscoverFlagsStr(p));
5b4e29
+	}
5b4e29
+	sdsfree(eventfilename);
5b4e29
     }
5b4e29
 
5b4e29
     return 0;
5b4e29
@@ -411,41 +625,23 @@ static void changed_callback(pmDiscover
5b4e29
 static void
5b4e29
 created_callback(pmDiscover *p)
5b4e29
 {
5b4e29
+    if (p->flags & (PM_DISCOVER_FLAGS_COMPRESSED|PM_DISCOVER_FLAGS_INDEX))
5b4e29
+    	return; /* compressed archives don't grow and we ignore archive index files */
5b4e29
+
5b4e29
     if (pmDebugOptions.discovery)
5b4e29
 	fprintf(stderr, "CREATED %s, %s\n", p->context.name, pmDiscoverFlagsStr(p));
5b4e29
 
5b4e29
-    p->flags &= ~PM_DISCOVER_FLAGS_NEW;
5b4e29
-
5b4e29
-    if (p->flags & PM_DISCOVER_FLAGS_COMPRESSED)
5b4e29
-    	return; /* compressed archives don't grow */
5b4e29
-
5b4e29
     if (p->flags & PM_DISCOVER_FLAGS_DIRECTORY) {
5b4e29
 	if (pmDebugOptions.discovery)
5b4e29
 	    fprintf(stderr, "MONITOR directory %s\n", p->context.name);
5b4e29
 	pmDiscoverMonitor(p->context.name, changed_callback);
5b4e29
     }
5b4e29
-
5b4e29
-    if (p->flags & PM_DISCOVER_FLAGS_DATAVOL) {
5b4e29
+    else if (p->flags & (PM_DISCOVER_FLAGS_META|PM_DISCOVER_FLAGS_DATAVOL)) {
5b4e29
 	if (pmDebugOptions.discovery)
5b4e29
-	    fprintf(stderr, "MONITOR logvol %s\n", p->context.name);
5b4e29
+	    fprintf(stderr, "MONITOR archive %s\n", p->context.name);
5b4e29
 	pmDiscoverMonitor(p->context.name, changed_callback);
5b4e29
     }
5b4e29
-
5b4e29
-    if (p->flags & PM_DISCOVER_FLAGS_META) {
5b4e29
-	if (pmDebugOptions.discovery)
5b4e29
-	    fprintf(stderr, "MONITOR metadata %s\n", p->context.name);
5b4e29
-	pmDiscoverMonitor(p->context.name, changed_callback);
5b4e29
-    }
5b4e29
-}
5b4e29
-
5b4e29
-static void
5b4e29
-deleted_callback(pmDiscover *p)
5b4e29
-{
5b4e29
-    if (pmDebugOptions.discovery)
5b4e29
-	fprintf(stderr, "DELETED %s (%s)\n", p->context.name,
5b4e29
-			pmDiscoverFlagsStr(p));
5b4e29
-    pmDiscoverDelete(p->context.name);
5b4e29
-    /* p is now no longer valid */
5b4e29
+    p->flags &= ~PM_DISCOVER_FLAGS_NEW;
5b4e29
 }
5b4e29
 
5b4e29
 static void
5b4e29
@@ -509,37 +705,84 @@ static void
5b4e29
 pmDiscoverInvokeMetricCallBacks(pmDiscover *p, pmTimespec *ts, pmDesc *desc,
5b4e29
 		int numnames, char **names)
5b4e29
 {
5b4e29
+    discoverModuleData	*data = getDiscoverModuleData(p->module);
5b4e29
     pmDiscoverCallBacks	*callbacks;
5b4e29
     pmDiscoverEvent	event;
5b4e29
     char		buf[32];
5b4e29
-    int			i;
5b4e29
+    int			i, sts;
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery) {
5b4e29
 	fprintf(stderr, "%s[%s]: %s name%s", "pmDiscoverInvokeMetricCallBacks",
5b4e29
 			timespec_str(ts, buf, sizeof(buf)),
5b4e29
 			p->context.source, numnames > 0 ? " " : "(none)\n");
5b4e29
 	for (i = 0; i < numnames; i++)
5b4e29
-	    printf("\"%s\"%s", names[i], i < numnames - 1 ? ", " : "\n");
5b4e29
+	    fprintf(stderr, "[%u/%u] \"%s\"%s", i+1, numnames, names[i],
5b4e29
+			    i < numnames - 1 ? ", " : "\n");
5b4e29
 	pmPrintDesc(stderr, desc);
5b4e29
 	if (pmDebugOptions.labels)
5b4e29
 	    fprintf(stderr, "context labels %s\n", p->context.labelset->json);
5b4e29
     }
5b4e29
 
5b4e29
+    if (data->pmids) {
5b4e29
+	if (dictFind(data->pmids, &desc->pmid) != NULL)
5b4e29
+	    goto out;	/* metric contains an already excluded PMID */
5b4e29
+	for (i = 0; i < numnames; i++) {
5b4e29
+	    if (regexec(&data->exclude_names, names[i], 0, NULL, 0) == 0)
5b4e29
+		break;
5b4e29
+	}
5b4e29
+	if (i != numnames) {
5b4e29
+	    if (pmDebugOptions.discovery)
5b4e29
+		fprintf(stderr, "%s: excluding metric %s\n",
5b4e29
+				"pmDiscoverInvokeMetricCallBacks", names[i]);
5b4e29
+	    /* add this pmid to the exclusion list and return early */
5b4e29
+	    dictAdd(data->pmids, &desc->pmid, NULL);
5b4e29
+	    goto out;
5b4e29
+	}
5b4e29
+    }
5b4e29
+    if (data->indoms) {
5b4e29
+	if (dictFind(data->indoms, &desc->indom) != NULL)
5b4e29
+	    goto out;	/* metric contains an already excluded InDom */
5b4e29
+    }
5b4e29
+
5b4e29
+    if (p->ctx >= 0 && p->context.type == PM_CONTEXT_ARCHIVE) {
5b4e29
+	__pmContext	*ctxp = __pmHandleToPtr(p->ctx);
5b4e29
+	__pmArchCtl	*acp = ctxp->c_archctl;
5b4e29
+	char		idstr[32];
5b4e29
+
5b4e29
+	if ((sts = __pmLogAddDesc(acp, desc)) < 0)
5b4e29
+	    fprintf(stderr, "%s: failed to add metric descriptor for %s\n",
5b4e29
+			    "pmDiscoverInvokeMetricCallBacks",
5b4e29
+			    pmIDStr_r(desc->pmid, idstr, sizeof(idstr)));
5b4e29
+	for (i = 0; i < numnames; i++) {
5b4e29
+	    if ((sts = __pmLogAddPMNSNode(acp, desc->pmid, names[i])) < 0)
5b4e29
+		fprintf(stderr, "%s: failed to add metric name %s for %s\n",
5b4e29
+				"pmDiscoverInvokeMetricCallBacks", names[i],
5b4e29
+				pmIDStr_r(desc->pmid, idstr, sizeof(idstr)));
5b4e29
+	}
5b4e29
+	PM_UNLOCK(ctxp->c_lock);
5b4e29
+    }
5b4e29
+
5b4e29
     discover_event_init(p, ts, &event);
5b4e29
     for (i = 0; i < discoverCallBackTableSize; i++) {
5b4e29
 	if ((callbacks = discoverCallBackTable[i]) &&
5b4e29
 	    callbacks->on_metric != NULL)
5b4e29
 	    callbacks->on_metric(&event, desc, numnames, names, p->data);
5b4e29
     }
5b4e29
+
5b4e29
+out:
5b4e29
+    for (i = 0; i < numnames; i++)
5b4e29
+	free(names[i]);
5b4e29
+    free(names);
5b4e29
 }
5b4e29
 
5b4e29
 static void
5b4e29
 pmDiscoverInvokeInDomCallBacks(pmDiscover *p, pmTimespec *ts, pmInResult *in)
5b4e29
 {
5b4e29
+    discoverModuleData	*data = getDiscoverModuleData(p->module);
5b4e29
     pmDiscoverCallBacks	*callbacks;
5b4e29
     pmDiscoverEvent	event;
5b4e29
     char		buf[32], inbuf[32];
5b4e29
-    int			i;
5b4e29
+    int			i, sts = PMLOGPUTINDOM_DUP; /* free after callbacks */
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery) {
5b4e29
 	fprintf(stderr, "%s[%s]: %s numinst %d indom %s\n",
5b4e29
@@ -551,22 +794,48 @@ pmDiscoverInvokeInDomCallBacks(pmDiscove
5b4e29
 	    fprintf(stderr, "context labels %s\n", p->context.labelset->json);
5b4e29
     }
5b4e29
 
5b4e29
+    if (data->indoms) {
5b4e29
+	if (dictFind(data->indoms, &in->indom) != NULL)
5b4e29
+	    goto out;	/* excluded InDom */
5b4e29
+    }
5b4e29
+
5b4e29
+    if (p->ctx >= 0 && p->context.type == PM_CONTEXT_ARCHIVE) {
5b4e29
+	__pmContext	*ctxp = __pmHandleToPtr(p->ctx);
5b4e29
+	__pmArchCtl	*acp = ctxp->c_archctl;
5b4e29
+	char		errmsg[PM_MAXERRMSGLEN];
5b4e29
+
5b4e29
+	if ((sts = __pmLogAddInDom(acp, ts, in, NULL, 0)) < 0)
5b4e29
+	    fprintf(stderr, "%s: failed to add indom for %s: %s\n",
5b4e29
+			"pmDiscoverInvokeInDomCallBacks", pmIDStr(in->indom),
5b4e29
+			pmErrStr_r(sts, errmsg, sizeof(errmsg)));
5b4e29
+	PM_UNLOCK(ctxp->c_lock);
5b4e29
+    }
5b4e29
+
5b4e29
     discover_event_init(p, ts, &event);
5b4e29
     for (i = 0; i < discoverCallBackTableSize; i++) {
5b4e29
 	if ((callbacks = discoverCallBackTable[i]) &&
5b4e29
 	    callbacks->on_indom != NULL)
5b4e29
 	    callbacks->on_indom(&event, in, p->data);
5b4e29
     }
5b4e29
+
5b4e29
+out:
5b4e29
+    if (sts == PMLOGPUTINDOM_DUP) {
5b4e29
+	for (i = 0; i < in->numinst; i++)
5b4e29
+	    free(in->namelist[i]);
5b4e29
+	free(in->namelist);
5b4e29
+	free(in->instlist);
5b4e29
+    }
5b4e29
 }
5b4e29
 
5b4e29
 static void
5b4e29
 pmDiscoverInvokeLabelsCallBacks(pmDiscover *p, pmTimespec *ts,
5b4e29
 		int ident, int type, pmLabelSet *sets, int nsets)
5b4e29
 {
5b4e29
+    discoverModuleData	*data = getDiscoverModuleData(p->module);
5b4e29
     pmDiscoverCallBacks	*callbacks;
5b4e29
     pmDiscoverEvent	event;
5b4e29
     char		buf[32], idbuf[64];
5b4e29
-    int			i;
5b4e29
+    int			i, sts = -EAGAIN; /* free labelsets after callbacks */
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery) {
5b4e29
 	__pmLabelIdentString(ident, type, idbuf, sizeof(idbuf));
5b4e29
@@ -579,22 +848,48 @@ pmDiscoverInvokeLabelsCallBacks(pmDiscov
5b4e29
 	    fprintf(stderr, "context labels %s\n", p->context.labelset->json);
5b4e29
     }
5b4e29
 
5b4e29
+    if ((type & PM_LABEL_ITEM) && data->pmids) {
5b4e29
+	if (dictFind(data->pmids, &ident) != NULL)
5b4e29
+	    goto out;	/* text from an already excluded InDom */
5b4e29
+    }
5b4e29
+    if ((type & (PM_LABEL_INDOM|PM_LABEL_INSTANCES)) && data->indoms) {
5b4e29
+	if (dictFind(data->indoms, &ident) != NULL)
5b4e29
+	    goto out;	/* text from an already excluded InDom */
5b4e29
+    }
5b4e29
+
5b4e29
+    if (p->ctx >= 0 && p->context.type == PM_CONTEXT_ARCHIVE) {
5b4e29
+	__pmContext	*ctxp = __pmHandleToPtr(p->ctx);
5b4e29
+	__pmArchCtl	*acp = ctxp->c_archctl;
5b4e29
+	char		errmsg[PM_MAXERRMSGLEN];
5b4e29
+
5b4e29
+	if ((sts = __pmLogAddLabelSets(acp, ts, type, ident, nsets, sets)) < 0)
5b4e29
+	    fprintf(stderr, "%s: failed to add log labelset: %s\n",
5b4e29
+			"pmDiscoverInvokeLabelsCallBacks",
5b4e29
+			pmErrStr_r(sts, errmsg, sizeof(errmsg)));
5b4e29
+	PM_UNLOCK(ctxp->c_lock);
5b4e29
+    }
5b4e29
+
5b4e29
     discover_event_init(p, ts, &event);
5b4e29
     for (i = 0; i < discoverCallBackTableSize; i++) {
5b4e29
 	if ((callbacks = discoverCallBackTable[i]) &&
5b4e29
 	    callbacks->on_labels != NULL)
5b4e29
 	    callbacks->on_labels(&event, ident, type, sets, nsets, p->data);
5b4e29
     }
5b4e29
+
5b4e29
+out:
5b4e29
+    if (sts < 0)
5b4e29
+	pmFreeLabelSets(sets, nsets);
5b4e29
 }
5b4e29
 
5b4e29
 static void
5b4e29
 pmDiscoverInvokeTextCallBacks(pmDiscover *p, pmTimespec *ts,
5b4e29
 		int ident, int type, char *text)
5b4e29
 {
5b4e29
+    discoverModuleData	*data = getDiscoverModuleData(p->module);
5b4e29
     pmDiscoverCallBacks	*callbacks;
5b4e29
     pmDiscoverEvent	event;
5b4e29
     char		buf[32];
5b4e29
-    int			i;
5b4e29
+    int			i, sts;
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery) {
5b4e29
 	fprintf(stderr, "%s[%s]: %s ", "pmDiscoverInvokeTextCallBacks",
5b4e29
@@ -612,12 +907,36 @@ pmDiscoverInvokeTextCallBacks(pmDiscover
5b4e29
 	    fprintf(stderr, "context labels %s\n", p->context.labelset->json);
5b4e29
     }
5b4e29
 
5b4e29
+    if ((type & PM_TEXT_PMID) && data->pmids) {
5b4e29
+	if (dictFind(data->pmids, &ident) != NULL)
5b4e29
+	    goto out;	/* text from an already excluded InDom */
5b4e29
+    }
5b4e29
+    if ((type & PM_TEXT_INDOM) && data->indoms) {
5b4e29
+	if (dictFind(data->indoms, &ident) != NULL)
5b4e29
+	    goto out;	/* text from an already excluded InDom */
5b4e29
+    }
5b4e29
+
5b4e29
+    if (p->ctx >= 0 && p->context.type == PM_CONTEXT_ARCHIVE) {
5b4e29
+	__pmContext	*ctxp = __pmHandleToPtr(p->ctx);
5b4e29
+	__pmArchCtl	*acp = ctxp->c_archctl;
5b4e29
+	char		errmsg[PM_MAXERRMSGLEN];
5b4e29
+
5b4e29
+	if ((sts = __pmLogAddText(acp, ident, type, text)) < 0)
5b4e29
+	    fprintf(stderr, "%s: failed to add %u text for %u: %s\n",
5b4e29
+	               "pmDiscoverInvokeTextCallBacks", type, ident,
5b4e29
+			pmErrStr_r(sts, errmsg, sizeof(errmsg)));
5b4e29
+	PM_UNLOCK(ctxp->c_lock);
5b4e29
+    }
5b4e29
+
5b4e29
     discover_event_init(p, ts, &event);
5b4e29
     for (i = 0; i < discoverCallBackTableSize; i++) {
5b4e29
 	if ((callbacks = discoverCallBackTable[i]) &&
5b4e29
 	    callbacks->on_text != NULL)
5b4e29
 	    callbacks->on_text(&event, ident, type, text, p->data);
5b4e29
     }
5b4e29
+
5b4e29
+out:
5b4e29
+    free(text);
5b4e29
 }
5b4e29
 
5b4e29
 static void
5b4e29
@@ -645,8 +964,8 @@ pmDiscoverNewSource(pmDiscover *p, int c
5b4e29
     p->context.labelset = labelset;
5b4e29
 
5b4e29
     /* use timestamp from file creation as starting time */
5b4e29
-    timestamp.tv_sec = p->statbuf.st_birthtim.tv_sec;
5b4e29
-    timestamp.tv_nsec = p->statbuf.st_birthtim.tv_nsec;
5b4e29
+    timestamp.tv_sec = p->statbuf.st_ctim.tv_sec;
5b4e29
+    timestamp.tv_nsec = p->statbuf.st_ctim.tv_nsec;
5b4e29
 
5b4e29
     /* inform utilities that a source has been discovered */
5b4e29
     pmDiscoverInvokeSourceCallBacks(p, &timestamp);
5b4e29
@@ -664,7 +983,7 @@ process_metadata(pmDiscover *p)
5b4e29
     pmDesc		desc;
5b4e29
     off_t		off;
5b4e29
     char		*buffer;
5b4e29
-    int			e, i, nb, len, nsets;
5b4e29
+    int			e, nb, len, nsets;
5b4e29
     int			type, id; /* pmID or pmInDom */
5b4e29
     int			nnames;
5b4e29
     char		**names;
5b4e29
@@ -674,6 +993,8 @@ process_metadata(pmDiscover *p)
5b4e29
     __pmLogHdr		hdr;
5b4e29
     sds			msg, source;
5b4e29
     static uint32_t	*buf = NULL;
5b4e29
+    int			deleted;
5b4e29
+    struct stat		sbuf;
5b4e29
     static int		buflen = 0;
5b4e29
 
5b4e29
     /*
5b4e29
@@ -683,14 +1004,17 @@ process_metadata(pmDiscover *p)
5b4e29
      */
5b4e29
     p->flags |= PM_DISCOVER_FLAGS_META_IN_PROGRESS;
5b4e29
     if (pmDebugOptions.discovery)
5b4e29
-	fprintf(stderr, "%s: in progress, flags=%s\n",
5b4e29
-			"process_metadata", pmDiscoverFlagsStr(p));
5b4e29
+	fprintf(stderr, "process_metadata: %s in progress %s\n",
5b4e29
+		p->context.name, pmDiscoverFlagsStr(p));
5b4e29
     for (;;) {
5b4e29
 	off = lseek(p->fd, 0, SEEK_CUR);
5b4e29
 	nb = read(p->fd, &hdr, sizeof(__pmLogHdr));
5b4e29
 
5b4e29
-	if (nb <= 0) {
5b4e29
-	    /* we're at EOF or an error. But may still be part way through a record */
5b4e29
+	deleted = is_deleted(p, &sbuf);
5b4e29
+	if (nb <= 0 || deleted) {
5b4e29
+	    /* we're at EOF or an error, or deleted. But may still be part way through a record */
5b4e29
+	    if (deleted)
5b4e29
+	    	p->flags |= PM_DISCOVER_FLAGS_DELETED;
5b4e29
 	    break;
5b4e29
 	}
5b4e29
 
5b4e29
@@ -750,10 +1074,6 @@ process_metadata(pmDiscover *p)
5b4e29
 	    ts.tv_sec = p->statbuf.st_mtim.tv_sec;
5b4e29
 	    ts.tv_nsec = p->statbuf.st_mtim.tv_nsec;
5b4e29
 	    pmDiscoverInvokeMetricCallBacks(p, &ts, &desc, nnames, names);
5b4e29
-	    for (i = 0; i < nnames; i++)
5b4e29
-		free(names[i]);
5b4e29
-	    if (names)
5b4e29
-		free(names);
5b4e29
 	    break;
5b4e29
 
5b4e29
 	case TYPE_INDOM:
5b4e29
@@ -765,12 +1085,6 @@ process_metadata(pmDiscover *p)
5b4e29
 		break;
5b4e29
 	    }
5b4e29
 	    pmDiscoverInvokeInDomCallBacks(p, &ts, &inresult);
5b4e29
-	    if (inresult.numinst > 0) {
5b4e29
-		for (i = 0; i < inresult.numinst; i++)
5b4e29
-		    free(inresult.namelist[i]);
5b4e29
-		free(inresult.namelist);
5b4e29
-		free(inresult.instlist);
5b4e29
-	    }
5b4e29
 	    break;
5b4e29
 
5b4e29
 	case TYPE_LABEL:
5b4e29
@@ -795,13 +1109,13 @@ process_metadata(pmDiscover *p)
5b4e29
 		} else {
5b4e29
 		    sdsfree(p->context.source);
5b4e29
 		    p->context.source = source;
5b4e29
-		    p->context.labelset = labelset;
5b4e29
+		    if (p->context.labelset)
5b4e29
+			pmFreeLabelSets(p->context.labelset, 1);
5b4e29
+		    p->context.labelset = __pmDupLabelSets(labelset, 1);
5b4e29
 		    pmDiscoverInvokeSourceCallBacks(p, &ts);
5b4e29
 		}
5b4e29
 	    }
5b4e29
 	    pmDiscoverInvokeLabelsCallBacks(p, &ts, id, type, labelset, nsets);
5b4e29
-	    if (labelset != p->context.labelset)
5b4e29
-		pmFreeLabelSets(labelset, nsets);
5b4e29
 	    break;
5b4e29
 
5b4e29
 	case TYPE_TEXT:
5b4e29
@@ -819,8 +1133,6 @@ process_metadata(pmDiscover *p)
5b4e29
 	    ts.tv_sec = p->statbuf.st_mtim.tv_sec;
5b4e29
 	    ts.tv_nsec = p->statbuf.st_mtim.tv_nsec;
5b4e29
 	    pmDiscoverInvokeTextCallBacks(p, &ts, id, type, buffer);
5b4e29
-	    if (buffer)
5b4e29
-		free(buffer);
5b4e29
 	    break;
5b4e29
 
5b4e29
 	default:
5b4e29
@@ -833,38 +1145,89 @@ process_metadata(pmDiscover *p)
5b4e29
     }
5b4e29
 
5b4e29
     if (partial == 0)
5b4e29
-	/* flag that all available metadata has been now been read */
5b4e29
+	/* flag that all available metadata has now been read */
5b4e29
 	p->flags &= ~PM_DISCOVER_FLAGS_META_IN_PROGRESS;
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery)
5b4e29
-	fprintf(stderr, "%s : completed, partial=%d flags=%s\n",
5b4e29
-			"process_metadata", partial, pmDiscoverFlagsStr(p));
5b4e29
+	fprintf(stderr, "%s: completed, partial=%d %s %s\n",
5b4e29
+			"process_metadata", partial, p->context.name, pmDiscoverFlagsStr(p));
5b4e29
 }
5b4e29
 
5b4e29
 /*
5b4e29
- * fetch metric values to EOF and call all registered callbacks
5b4e29
+ * Fetch metric values to EOF and call all registered callbacks.
5b4e29
+ * Always process metadata thru to EOF before any logvol data.
5b4e29
  */
5b4e29
 static void
5b4e29
-process_logvol_callback(pmDiscover *p)
5b4e29
+process_logvol(pmDiscover *p)
5b4e29
 {
5b4e29
+    int			sts;
5b4e29
     pmResult		*r;
5b4e29
     pmTimespec		ts;
5b4e29
+    int			oldcurvol;
5b4e29
+    __pmContext		*ctxp;
5b4e29
+    __pmArchCtl		*acp;
5b4e29
+
5b4e29
+    for (;;) {
5b4e29
+	pmUseContext(p->ctx);
5b4e29
+	ctxp = __pmHandleToPtr(p->ctx);
5b4e29
+	acp = ctxp->c_archctl;
5b4e29
+	oldcurvol = acp->ac_curvol;
5b4e29
+	PM_UNLOCK(ctxp->c_lock);
5b4e29
+
5b4e29
+	if ((sts = pmFetchArchive(&r)) < 0) {
5b4e29
+	    /* err handling to skip to the next vol */
5b4e29
+	    ctxp = __pmHandleToPtr(p->ctx);
5b4e29
+	    acp = ctxp->c_archctl;
5b4e29
+	    if (oldcurvol < acp->ac_curvol) {
5b4e29
+	    	__pmLogChangeVol(acp, acp->ac_curvol);
5b4e29
+		acp->ac_offset = 0; /* __pmLogFetch will fix it up */
5b4e29
+	    }
5b4e29
+	    PM_UNLOCK(ctxp->c_lock);
5b4e29
+
5b4e29
+	    if (sts == PM_ERR_EOL) {
5b4e29
+		if (pmDebugOptions.discovery)
5b4e29
+		    fprintf(stderr, "process_logvol: %s end of archive reached\n",
5b4e29
+		    	p->context.name);
5b4e29
+
5b4e29
+		/* succesfully processed to current end of log */
5b4e29
+		break;
5b4e29
+	    } else {
5b4e29
+		/* 
5b4e29
+		 * This log vol was probably deleted (likely compressed)
5b4e29
+		 * under our feet. Try and skip to the next volume.
5b4e29
+		 * We hold the context lock during error recovery here.
5b4e29
+		 */
5b4e29
+		if (pmDebugOptions.discovery)
5b4e29
+		    fprintf(stderr, "process_logvol: %s fetch failed:%s\n",
5b4e29
+			p->context.name, pmErrStr(sts));
5b4e29
+	    }
5b4e29
 
5b4e29
-    pmUseContext(p->ctx);
5b4e29
-    while (pmFetchArchive(&r) == 0) {
5b4e29
+	    /* we are done - return and wait for another callback */
5b4e29
+	    break;
5b4e29
+	}
5b4e29
+
5b4e29
+	/*
5b4e29
+	 * Fetch succeeded - call the values callback and continue
5b4e29
+	 */
5b4e29
 	if (pmDebugOptions.discovery) {
5b4e29
 	    char		tbuf[64], bufs[64];
5b4e29
 
5b4e29
-	    fprintf(stderr, "FETCHED @%s [%s] %d metrics\n",
5b4e29
-		    timeval_str(&r->timestamp, tbuf, sizeof(tbuf)),
5b4e29
+	    fprintf(stderr, "process_logvol: %s FETCHED @%s [%s] %d metrics\n",
5b4e29
+		    p->context.name, timeval_str(&r->timestamp, tbuf, sizeof(tbuf)),
5b4e29
 		    timeval_stream_str(&r->timestamp, bufs, sizeof(bufs)),
5b4e29
 		    r->numpmid);
5b4e29
 	}
5b4e29
+
5b4e29
+	/*
5b4e29
+	 * TODO: persistently save current timestamp, so after being restarted,
5b4e29
+	 * pmproxy can resume where it left off for each archive.
5b4e29
+	 */
5b4e29
 	ts.tv_sec = r->timestamp.tv_sec;
5b4e29
 	ts.tv_nsec = r->timestamp.tv_usec * 1000;
5b4e29
 	pmDiscoverInvokeValuesCallBack(p, &ts, r);
5b4e29
 	pmFreeResult(r);
5b4e29
     }
5b4e29
+
5b4e29
     /* datavol is now up-to-date and at EOF */
5b4e29
     p->flags &= ~PM_DISCOVER_FLAGS_DATAVOL_READY;
5b4e29
 }
5b4e29
@@ -874,12 +1237,13 @@ pmDiscoverInvokeCallBacks(pmDiscover *p)
5b4e29
 {
5b4e29
     int			sts;
5b4e29
     sds			msg;
5b4e29
+    sds			metaname;
5b4e29
 
5b4e29
     if (p->ctx < 0) {
5b4e29
 	/*
5b4e29
 	 * once off initialization on the first event
5b4e29
 	 */
5b4e29
-	if (p->flags & PM_DISCOVER_FLAGS_DATAVOL) {
5b4e29
+	if (p->flags & (PM_DISCOVER_FLAGS_DATAVOL | PM_DISCOVER_FLAGS_META)) {
5b4e29
 	    struct timeval	tvp;
5b4e29
 
5b4e29
 	    /* create the PMAPI context (once off) */
5b4e29
@@ -898,28 +1262,25 @@ pmDiscoverInvokeCallBacks(pmDiscover *p)
5b4e29
 		p->ctx = -1;
5b4e29
 		return;
5b4e29
 	    }
5b4e29
+	    /* seek to end of archive for logvol data - see TODO in process_logvol() */
5b4e29
 	    pmSetMode(PM_MODE_FORW, &tvp, 1);
5b4e29
-	    /* note: we do not scan pre-existing logvol data. */
5b4e29
-	}
5b4e29
-	else if (p->flags & PM_DISCOVER_FLAGS_META) {
5b4e29
-	    if ((sts = pmNewContext(p->context.type, p->context.name)) < 0) {
5b4e29
-		infofmt(msg, "pmNewContext failed for %s: %s\n",
5b4e29
-				p->context.name, pmErrStr(sts));
5b4e29
-		moduleinfo(p->module, PMLOG_ERROR, msg, p->data);
5b4e29
-		return;
5b4e29
-	    }
5b4e29
-	    pmDiscoverNewSource(p, sts);
5b4e29
 
5b4e29
-	    /* for archive meta files, p->fd is the direct file descriptor */
5b4e29
-	    if ((p->fd = open(p->context.name, O_RDONLY)) < 0) {
5b4e29
-		infofmt(msg, "open failed for %s: %s\n", p->context.name,
5b4e29
-				osstrerror());
5b4e29
+	    /*
5b4e29
+	     * For archive meta files, p->fd is the direct file descriptor
5b4e29
+	     * and we pre-scan existing metadata. Note: we do NOT scan
5b4e29
+	     * pre-existing logvol data (see pmSetMode above)
5b4e29
+	     */
5b4e29
+	    metaname = sdsnew(p->context.name);
5b4e29
+	    metaname = sdscat(metaname, ".meta");
5b4e29
+	    if ((p->fd = open(metaname, O_RDONLY)) < 0) {
5b4e29
+		infofmt(msg, "open failed for %s: %s\n", metaname, osstrerror());
5b4e29
 		moduleinfo(p->module, PMLOG_ERROR, msg, p->data);
5b4e29
+		sdsfree(metaname);
5b4e29
 		return;
5b4e29
 	    }
5b4e29
-
5b4e29
-	    /* process all existing metadata */
5b4e29
+	    /* pre-process all existing metadata */
5b4e29
 	    process_metadata(p);
5b4e29
+	    sdsfree(metaname);
5b4e29
 	}
5b4e29
     }
5b4e29
 
5b4e29
@@ -943,15 +1304,61 @@ pmDiscoverInvokeCallBacks(pmDiscover *p)
5b4e29
     }
5b4e29
 
5b4e29
     if (p->flags & PM_DISCOVER_FLAGS_META) {
5b4e29
-	/* process metadata */
5b4e29
+	/* process new metadata, if any */
5b4e29
 	process_metadata(p);
5b4e29
     }
5b4e29
 
5b4e29
-    /* process any unprocessed datavol callbacks */
5b4e29
-    pmDiscoverTraverse(PM_DISCOVER_FLAGS_DATAVOL_READY, process_logvol_callback);
5b4e29
+    if ((p->flags & PM_DISCOVER_FLAGS_META_IN_PROGRESS) == 0) {
5b4e29
+	/* no metdata read in progress, so process new datavol data, if any */
5b4e29
+	process_logvol(p);
5b4e29
+    }
5b4e29
+}
5b4e29
+
5b4e29
+static void
5b4e29
+print_callback(pmDiscover *p)
5b4e29
+{
5b4e29
+    if (p->flags & PM_DISCOVER_FLAGS_DIRECTORY) {
5b4e29
+	fprintf(stderr, "    DIRECTORY %s %s\n",
5b4e29
+	    p->context.name, pmDiscoverFlagsStr(p));
5b4e29
+    }
5b4e29
+    else {
5b4e29
+	__pmContext *ctxp;
5b4e29
+	__pmArchCtl *acp;
5b4e29
 
5b4e29
-    /* finally, purge deleted entries, if any */
5b4e29
-    pmDiscoverPurgeDeleted();
5b4e29
+	if (p->ctx >= 0 && (ctxp = __pmHandleToPtr(p->ctx)) != NULL) {
5b4e29
+	    acp = ctxp->c_archctl;
5b4e29
+	    fprintf(stderr, "    ARCHIVE %s fd=%d ctx=%d maxvol=%d ac_curvol=%d ac_offset=%ld %s\n",
5b4e29
+		p->context.name, p->fd, p->ctx, acp->ac_log->l_maxvol, acp->ac_curvol,
5b4e29
+		acp->ac_offset, pmDiscoverFlagsStr(p));
5b4e29
+	    PM_UNLOCK(ctxp->c_lock);
5b4e29
+	} else {
5b4e29
+	    /* no context yet - probably PM_DISCOVER_FLAGS_NEW */
5b4e29
+	    fprintf(stderr, "    ARCHIVE %s fd=%d ctx=%d %s\n",
5b4e29
+		p->context.name, p->fd, p->ctx, pmDiscoverFlagsStr(p));
5b4e29
+	}
5b4e29
+    }
5b4e29
+}
5b4e29
+
5b4e29
+/*
5b4e29
+ * p is a tracked archive and arg is a directory path.
5b4e29
+ * If p is in the directory, call it's callbacks to
5b4e29
+ * process metadata and logvol data. This allows better
5b4e29
+ * scalability because we only process archives in the
5b4e29
+ * directories that have changed.
5b4e29
+ */
5b4e29
+static void
5b4e29
+directory_changed_cb(pmDiscover *p, void *arg)
5b4e29
+{
5b4e29
+    char *dirpath = (char *)arg;
5b4e29
+    int dlen = strlen(dirpath);
5b4e29
+
5b4e29
+    if (strncmp(p->context.name, dirpath, dlen) == 0) {
5b4e29
+    	/* this archive is in this directory - process it's metadata and logvols */
5b4e29
+	if (pmDebugOptions.discovery)
5b4e29
+	    fprintf(stderr, "directory_changed_cb: archive %s is in dir %s\n",
5b4e29
+		p->context.name, dirpath);
5b4e29
+	pmDiscoverInvokeCallBacks(p);
5b4e29
+    }
5b4e29
 }
5b4e29
 
5b4e29
 static void
5b4e29
@@ -962,27 +1369,46 @@ changed_callback(pmDiscover *p)
5b4e29
 			pmDiscoverFlagsStr(p));
5b4e29
 
5b4e29
     if (p->flags & PM_DISCOVER_FLAGS_DELETED) {
5b4e29
-	/* path or directory has been deleted - remove from hash table */
5b4e29
-	deleted_callback(p);
5b4e29
-    }
5b4e29
-    else if (p->flags & PM_DISCOVER_FLAGS_DIRECTORY) {
5b4e29
 	/*
5b4e29
-	 * A changed directory path means a new archive or subdirectory
5b4e29
-	 * has been created - traverse and update the hash table.
5b4e29
+	 * Path has been deleted. Do nothing for now. Will be purged
5b4e29
+	 * in due course by pmDiscoverPurgeDeleted.
5b4e29
 	 */
5b4e29
-	pmDiscoverArchives(p->context.name, p->module, p->data);
5b4e29
-	pmDiscoverTraverse(PM_DISCOVER_FLAGS_NEW, created_callback);
5b4e29
+	return;
5b4e29
+	
5b4e29
     }
5b4e29
-    else if (p->flags & PM_DISCOVER_FLAGS_COMPRESSED) {
5b4e29
+
5b4e29
+    if (p->flags & PM_DISCOVER_FLAGS_COMPRESSED) {
5b4e29
     	/* we do not monitor compressed files - do nothing */
5b4e29
-	; /**/
5b4e29
+	return;
5b4e29
     }
5b4e29
-    else if (p->flags & (PM_DISCOVER_FLAGS_DATAVOL|PM_DISCOVER_FLAGS_META)) {
5b4e29
-    	/*
5b4e29
-	 * We only monitor uncompressed logvol and metadata paths. Fetch new data
5b4e29
-	 * (metadata or logvol) and call the registered callbacks.
5b4e29
+
5b4e29
+    if (p->flags & PM_DISCOVER_FLAGS_DIRECTORY) {
5b4e29
+	/*
5b4e29
+	 * A changed directory path means a new archive or subdirectory may have
5b4e29
+	 * been created or deleted - traverse and update the hash table.
5b4e29
 	 */
5b4e29
-	pmDiscoverInvokeCallBacks(p);
5b4e29
+	if (pmDebugOptions.discovery) {
5b4e29
+	    fprintf(stderr, "%s DIRECTORY CHANGED %s (%s)\n",
5b4e29
+	    	stamp(), p->context.name, pmDiscoverFlagsStr(p));
5b4e29
+	}
5b4e29
+	pmDiscoverArchives(p->context.name, p->module, p->data);
5b4e29
+	pmDiscoverTraverse(PM_DISCOVER_FLAGS_NEW, created_callback);
5b4e29
+
5b4e29
+	/*
5b4e29
+	 * Walk directory and invoke callbacks for tracked archives in this
5b4e29
+	 * directory that have changed
5b4e29
+	 */
5b4e29
+	pmDiscoverTraverseArg(PM_DISCOVER_FLAGS_DATAVOL|PM_DISCOVER_FLAGS_META,
5b4e29
+	    directory_changed_cb, (void *)p->context.name);
5b4e29
+
5b4e29
+	/* finally, purge deleted entries (globally), if any */
5b4e29
+	pmDiscoverPurgeDeleted();
5b4e29
+    }
5b4e29
+
5b4e29
+    if (pmDebugOptions.discovery) {
5b4e29
+	fprintf(stderr, "%s -- tracking status\n", stamp());
5b4e29
+	pmDiscoverTraverse(PM_DISCOVER_FLAGS_ALL, print_callback);
5b4e29
+	fprintf(stderr, "--\n");
5b4e29
     }
5b4e29
 }
5b4e29
 
5b4e29
@@ -995,18 +1421,9 @@ dir_callback(pmDiscover *p)
5b4e29
 static void
5b4e29
 archive_callback(pmDiscover *p)
5b4e29
 {
5b4e29
-    if (p->flags & PM_DISCOVER_FLAGS_COMPRESSED)
5b4e29
-    	return; /* compressed archives don't grow */
5b4e29
-
5b4e29
-    if (p->flags & PM_DISCOVER_FLAGS_DATAVOL) {
5b4e29
-	if (pmDebugOptions.discovery)
5b4e29
-	    fprintf(stderr, "DISCOVERED ARCHIVE LOGVOL %s\n", p->context.name);
5b4e29
-	pmDiscoverMonitor(p->context.name, changed_callback);
5b4e29
-    }
5b4e29
-
5b4e29
     if (p->flags & PM_DISCOVER_FLAGS_META) {
5b4e29
 	if (pmDebugOptions.discovery)
5b4e29
-	    fprintf(stderr, "DISCOVERED ARCHIVE METADATA %s\n", p->context.name);
5b4e29
+	    fprintf(stderr, "DISCOVERED ARCHIVE %s\n", p->context.name);
5b4e29
 	pmDiscoverMonitor(p->context.name, changed_callback);
5b4e29
     }
5b4e29
 }
5b4e29
@@ -1048,9 +1465,9 @@ pmDiscoverRegister(const char *dir, pmDi
5b4e29
     }
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery) {
5b4e29
-	fprintf(stderr, "Now managing %d directories and %d archive files\n",
5b4e29
+	fprintf(stderr, "Now tracking %d directories and %d archives\n",
5b4e29
 	    pmDiscoverTraverse(PM_DISCOVER_FLAGS_DIRECTORY, NULL),
5b4e29
-	    pmDiscoverTraverse(PM_DISCOVER_FLAGS_DATAVOL, NULL));
5b4e29
+	    pmDiscoverTraverse(PM_DISCOVER_FLAGS_DATAVOL|PM_DISCOVER_FLAGS_META, NULL));
5b4e29
     }
5b4e29
 
5b4e29
     /* monitor the directories */
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/discover.h pcp-5.0.2/src/libpcp_web/src/discover.h
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/discover.h	2019-12-10 17:04:20.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/discover.h	2020-02-03 13:36:09.904659047 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2018-2019 Red Hat.
5b4e29
+ * Copyright (c) 2018-2020 Red Hat.
5b4e29
  *
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
  * under the terms of the GNU Lesser General Public License as published
5b4e29
@@ -18,7 +18,9 @@
5b4e29
 #include "libpcp.h"
5b4e29
 #include "mmv_stats.h"
5b4e29
 #include "slots.h"
5b4e29
-
5b4e29
+#ifdef HAVE_REGEX_H
5b4e29
+#include <regex.h>
5b4e29
+#endif
5b4e29
 #ifdef HAVE_LIBUV
5b4e29
 #include <uv.h>
5b4e29
 #else
5b4e29
@@ -84,8 +86,8 @@ typedef struct pmDiscover {
5b4e29
     int				fd;		/* meta file descriptor */
5b4e29
 #ifdef HAVE_LIBUV
5b4e29
     uv_fs_event_t		*event_handle;	/* uv fs_notify event handle */ 
5b4e29
-    uv_stat_t			statbuf;	/* stat buffer from event CB */
5b4e29
 #endif
5b4e29
+    struct stat			statbuf;	/* stat buffer */
5b4e29
     void			*baton;		/* private internal lib data */
5b4e29
     void			*data;		/* opaque user data pointer */
5b4e29
 } pmDiscover;
5b4e29
@@ -115,6 +117,10 @@ typedef struct discoverModuleData {
5b4e29
     struct dict			*config;	/* configuration dict */
5b4e29
     uv_loop_t			*events;	/* event library loop */
5b4e29
     redisSlots			*slots;		/* server slots data */
5b4e29
+    regex_t			exclude_names;	/* metric names to exclude */
5b4e29
+    struct dict			*pmids;		/* dict of excluded PMIDs */
5b4e29
+    unsigned int		exclude_indoms;	/* exclude instance domains */
5b4e29
+    struct dict			*indoms;	/* dict of excluded InDoms */
5b4e29
     void			*data;		/* user-supplied pointer */
5b4e29
 } discoverModuleData;
5b4e29
 
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/exports pcp-5.0.2/src/libpcp_web/src/exports
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/exports	2019-11-26 16:29:58.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/exports	2020-02-03 13:23:15.264762900 +1100
5b4e29
@@ -178,3 +178,8 @@ PCP_WEB_1.11 {
5b4e29
   global:
5b4e29
     pmSeriesLabelValues;
5b4e29
 } PCP_WEB_1.10;
5b4e29
+
5b4e29
+PCP_WEB_1.12 {
5b4e29
+  global:
5b4e29
+    SDS_NOINIT;
5b4e29
+} PCP_WEB_1.11;
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/load.c pcp-5.0.2/src/libpcp_web/src/load.c
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/load.c	2019-12-11 14:01:53.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/load.c	2020-02-03 13:36:03.947721365 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2017-2019 Red Hat.
5b4e29
+ * Copyright (c) 2017-2020 Red Hat.
5b4e29
  *
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
  * under the terms of the GNU Lesser General Public License as published
5b4e29
@@ -112,22 +112,41 @@ load_prepare_metric(const char *name, vo
5b4e29
  * Iterate over an instance domain and extract names and labels
5b4e29
  * for each instance.
5b4e29
  */
5b4e29
-static unsigned int
5b4e29
-get_instance_metadata(seriesLoadBaton *baton, pmInDom indom)
5b4e29
+static void
5b4e29
+get_instance_metadata(seriesLoadBaton *baton, pmInDom indom, int force_refresh)
5b4e29
 {
5b4e29
     context_t		*cp = &baton->pmapi.context;
5b4e29
-    unsigned int	count = 0;
5b4e29
     domain_t		*dp;
5b4e29
     indom_t		*ip;
5b4e29
 
5b4e29
     if (indom != PM_INDOM_NULL) {
5b4e29
 	if ((dp = pmwebapi_add_domain(cp, pmInDom_domain(indom))))
5b4e29
 	    pmwebapi_add_domain_labels(cp, dp);
5b4e29
-	if ((ip = pmwebapi_add_indom(cp, dp, indom)) &&
5b4e29
-	    (count = pmwebapi_add_indom_instances(cp, ip)) > 0)
5b4e29
-	    pmwebapi_add_instances_labels(cp, ip);
5b4e29
+	if ((ip = pmwebapi_add_indom(cp, dp, indom)) != NULL) {
5b4e29
+	    if (force_refresh)
5b4e29
+		ip->updated = 1;
5b4e29
+	    if (ip->updated) {
5b4e29
+		pmwebapi_add_indom_instances(cp, ip);
5b4e29
+		pmwebapi_add_instances_labels(cp, ip);
5b4e29
+	    }
5b4e29
+	}
5b4e29
     }
5b4e29
-    return count;
5b4e29
+}
5b4e29
+
5b4e29
+static void
5b4e29
+get_metric_metadata(seriesLoadBaton *baton, metric_t *metric)
5b4e29
+{
5b4e29
+    context_t		*context = &baton->pmapi.context;
5b4e29
+
5b4e29
+    if (metric->cluster) {
5b4e29
+	if (metric->cluster->domain)
5b4e29
+	    pmwebapi_add_domain_labels(context, metric->cluster->domain);
5b4e29
+	pmwebapi_add_cluster_labels(context, metric->cluster);
5b4e29
+    }
5b4e29
+    if (metric->indom)
5b4e29
+	pmwebapi_add_instances_labels(context, metric->indom);
5b4e29
+    pmwebapi_add_item_labels(context, metric);
5b4e29
+    pmwebapi_metric_hash(metric);
5b4e29
 }
5b4e29
 
5b4e29
 static metric_t *
5b4e29
@@ -140,18 +159,25 @@ new_metric(seriesLoadBaton *baton, pmVal
5b4e29
     char		**nameall = NULL;
5b4e29
     int			count, sts, i;
5b4e29
 
5b4e29
-    if ((sts = pmLookupDesc(vsp->pmid, &desc)) < 0) {
5b4e29
+    if ((sts = pmUseContext(context->context)) < 0) {
5b4e29
+	fprintf(stderr, "%s: failed to use context for PMID %s: %s\n",
5b4e29
+		"new_metric",
5b4e29
+		pmIDStr_r(vsp->pmid, idbuf, sizeof(idbuf)),
5b4e29
+		pmErrStr_r(sts, errmsg, sizeof(errmsg)));
5b4e29
+    } else if ((sts = pmLookupDesc(vsp->pmid, &desc)) < 0) {
5b4e29
 	if (sts == PM_ERR_IPC)
5b4e29
 	    context->setup = 0;
5b4e29
 	if (pmDebugOptions.series)
5b4e29
-	    fprintf(stderr, "failed to lookup metric %s descriptor: %s",
5b4e29
+	    fprintf(stderr, "%s: failed to lookup metric %s descriptor: %s\n",
5b4e29
+		"new_metric",
5b4e29
 		pmIDStr_r(vsp->pmid, idbuf, sizeof(idbuf)),
5b4e29
 		pmErrStr_r(sts, errmsg, sizeof(errmsg)));
5b4e29
     } else if ((sts = count = pmNameAll(vsp->pmid, &nameall)) < 0) {
5b4e29
 	if (sts == PM_ERR_IPC)
5b4e29
 	    context->setup = 0;
5b4e29
 	if (pmDebugOptions.series)
5b4e29
-	    fprintf(stderr, "failed to lookup metric %s names: %s",
5b4e29
+	    fprintf(stderr, "%s: failed to lookup metric %s names: %s\n",
5b4e29
+		"new_metric",
5b4e29
 		pmIDStr_r(vsp->pmid, idbuf, sizeof(idbuf)),
5b4e29
 		pmErrStr_r(sts, errmsg, sizeof(errmsg)));
5b4e29
     }
5b4e29
@@ -160,18 +186,10 @@ new_metric(seriesLoadBaton *baton, pmVal
5b4e29
 
5b4e29
     if ((metric = pmwebapi_new_metric(context, NULL, &desc, count, nameall)) == NULL)
5b4e29
 	return NULL;
5b4e29
-    if (metric->cluster) {
5b4e29
-	if (metric->cluster->domain)
5b4e29
-	    pmwebapi_add_domain_labels(context, metric->cluster->domain);
5b4e29
-	pmwebapi_add_cluster_labels(context, metric->cluster);
5b4e29
-    }
5b4e29
-    if (metric->indom)
5b4e29
-	pmwebapi_add_instances_labels(context, metric->indom);
5b4e29
-    pmwebapi_add_item_labels(context, metric);
5b4e29
-    pmwebapi_metric_hash(metric);
5b4e29
+    get_metric_metadata(baton, metric);
5b4e29
 
5b4e29
     if (pmDebugOptions.series) {
5b4e29
-	fprintf(stderr, "new_metric [%s] names:",
5b4e29
+	fprintf(stderr, "%s [%s] names:\n", "new_metric",
5b4e29
 		pmIDStr_r(vsp->pmid, idbuf, sizeof(idbuf)));
5b4e29
 	for (i = 0; i < count; i++) {
5b4e29
 	    pmwebapi_hash_str(metric->names[i].hash, idbuf, sizeof(idbuf));
5b4e29
@@ -409,7 +427,7 @@ pmwebapi_add_valueset(metric_t *metric,
5b4e29
 }
5b4e29
 
5b4e29
 static void
5b4e29
-series_cache_update(seriesLoadBaton *baton)
5b4e29
+series_cache_update(seriesLoadBaton *baton, struct dict *exclude)
5b4e29
 {
5b4e29
     seriesGetContext	*context = &baton->pmapi;
5b4e29
     context_t		*cp = &context->context;
5b4e29
@@ -418,7 +436,7 @@ series_cache_update(seriesLoadBaton *bat
5b4e29
     metric_t		*metric = NULL;
5b4e29
     char		ts[64];
5b4e29
     sds			timestamp;
5b4e29
-    int			i, write_meta, write_data;
5b4e29
+    int			i, write_meta, write_inst, write_data;
5b4e29
 
5b4e29
     timestamp = sdsnew(timeval_stream_str(&result->timestamp, ts, sizeof(ts)));
5b4e29
     write_data = (!(baton->flags & PM_SERIES_FLAG_METADATA));
5b4e29
@@ -441,6 +459,12 @@ series_cache_update(seriesLoadBaton *bat
5b4e29
 	    dictFetchValue(baton->wanted, &vsp->pmid) == NULL)
5b4e29
 	    continue;
5b4e29
 
5b4e29
+	/* check if metric to be skipped (optional metric exclusion) */
5b4e29
+	if (exclude && (dictFind(exclude, &vsp->pmid)) != NULL)
5b4e29
+	    continue;
5b4e29
+
5b4e29
+	write_meta = write_inst = 0;
5b4e29
+
5b4e29
 	/* check if pmid already in hash list */
5b4e29
 	if ((metric = dictFetchValue(cp->pmids, &vsp->pmid)) == NULL) {
5b4e29
 	    /* create a new metric, and add it to load context */
5b4e29
@@ -448,21 +472,22 @@ series_cache_update(seriesLoadBaton *bat
5b4e29
 		continue;
5b4e29
 	    write_meta = 1;
5b4e29
 	} else {	/* pmid already observed */
5b4e29
-	    write_meta = 0;
5b4e29
+	    if ((write_meta = metric->cached) == 0)
5b4e29
+		get_metric_metadata(baton, metric);
5b4e29
 	}
5b4e29
 
5b4e29
 	/* iterate through result instances and ensure metric_t is complete */
5b4e29
 	if (metric->error == 0 && vsp->numval < 0)
5b4e29
 	    write_meta = 1;
5b4e29
 	if (pmwebapi_add_valueset(metric, vsp) != 0)
5b4e29
-	    write_meta = 1;
5b4e29
+	    write_meta = write_inst = 1;
5b4e29
 
5b4e29
 	/* record the error code in the cache */
5b4e29
 	metric->error = (vsp->numval < 0) ? vsp->numval : 0;
5b4e29
 
5b4e29
 	/* make PMAPI calls to cache metadata */
5b4e29
-	if (write_meta && get_instance_metadata(baton, metric->desc.indom) != 0)
5b4e29
-	    continue;
5b4e29
+	if (write_meta)
5b4e29
+	    get_instance_metadata(baton, metric->desc.indom, write_inst);
5b4e29
 
5b4e29
 	/* initiate writes to backend caching servers (Redis) */
5b4e29
 	server_cache_metric(baton, metric, timestamp, write_meta, write_data);
5b4e29
@@ -549,7 +574,7 @@ server_cache_window(void *arg)
5b4e29
 	    (finish->tv_sec == result->timestamp.tv_sec &&
5b4e29
 	     finish->tv_usec >= result->timestamp.tv_usec)) {
5b4e29
 	    context->done = server_cache_update_done;
5b4e29
-	    series_cache_update(baton);
5b4e29
+	    series_cache_update(baton, NULL);
5b4e29
 	}
5b4e29
 	else {
5b4e29
 	    if (pmDebugOptions.series)
5b4e29
@@ -1023,7 +1048,7 @@ pmSeriesDiscoverSource(pmDiscoverEvent *
5b4e29
     sds			msg;
5b4e29
     int			i;
5b4e29
 
5b4e29
-    if (data == NULL || data->slots == NULL)
5b4e29
+    if (data == NULL || data->slots == NULL || data->slots->setup == 0)
5b4e29
 	return;
5b4e29
 
5b4e29
     baton = (seriesLoadBaton *)calloc(1, sizeof(seriesLoadBaton));
5b4e29
@@ -1032,22 +1057,31 @@ pmSeriesDiscoverSource(pmDiscoverEvent *
5b4e29
 	moduleinfo(module, PMLOG_ERROR, msg, arg);
5b4e29
 	return;
5b4e29
     }
5b4e29
+    if ((set = pmwebapi_labelsetdup(p->context.labelset)) == NULL) {
5b4e29
+	infofmt(msg, "%s: out of memory for labels", "pmSeriesDiscoverSource");
5b4e29
+	moduleinfo(module, PMLOG_ERROR, msg, arg);
5b4e29
+	free(baton);
5b4e29
+	return;
5b4e29
+    }
5b4e29
+
5b4e29
     initSeriesLoadBaton(baton, module, 0 /*flags*/,
5b4e29
 			module->on_info, series_discover_done,
5b4e29
 			data->slots, arg);
5b4e29
     initSeriesGetContext(&baton->pmapi, baton);
5b4e29
     p->baton = baton;
5b4e29
 
5b4e29
+    cp = &baton->pmapi.context;
5b4e29
+
5b4e29
     if (pmDebugOptions.discovery)
5b4e29
-	fprintf(stderr, "%s: new source %s context=%d\n",
5b4e29
-			"pmSeriesDiscoverSource", p->context.name, p->ctx);
5b4e29
+	fprintf(stderr, "%s: new source %s context=%p ctxid=%d\n",
5b4e29
+			"pmSeriesDiscoverSource", p->context.name, cp, p->ctx);
5b4e29
 
5b4e29
-    cp = &baton->pmapi.context;
5b4e29
     cp->context = p->ctx;
5b4e29
     cp->type = p->context.type;
5b4e29
     cp->name.sds = sdsdup(p->context.name);
5b4e29
-    cp->host = p->context.hostname;
5b4e29
-    cp->labelset = set = p->context.labelset;
5b4e29
+    cp->host = sdsdup(p->context.hostname);
5b4e29
+    cp->labelset = set;
5b4e29
+
5b4e29
     pmwebapi_source_hash(cp->name.hash, set->json, set->jsonlen);
5b4e29
     pmwebapi_setup_context(cp);
5b4e29
     set_source_origin(cp);
5b4e29
@@ -1095,21 +1129,22 @@ pmSeriesDiscoverLabels(pmDiscoverEvent *
5b4e29
     sds			msg;
5b4e29
     int			i, id;
5b4e29
 
5b4e29
+    if (baton == NULL || baton->slots == NULL || baton->slots->setup == 0)
5b4e29
+	return;
5b4e29
+
5b4e29
     switch (type) {
5b4e29
     case PM_LABEL_CONTEXT:
5b4e29
 	if (pmDebugOptions.discovery)
5b4e29
 	    fprintf(stderr, "%s: context\n", "pmSeriesDiscoverLabels");
5b4e29
 
5b4e29
 	if ((labels = pmwebapi_labelsetdup(sets)) != NULL) {
5b4e29
-#if 0 /* PCP GH#800 do not free this labelset - it's owned by the discover code */
5b4e29
 	    if (cp->labelset)
5b4e29
 		pmFreeLabelSets(cp->labelset, 1);
5b4e29
-#endif
5b4e29
 	    cp->labelset = labels;
5b4e29
 	    pmwebapi_locate_context(cp);
5b4e29
 	    cp->updated = 1;
5b4e29
 	} else {
5b4e29
-	    infofmt(msg, "failed to duplicate label set");
5b4e29
+	    infofmt(msg, "failed to duplicate %s label set", "context");
5b4e29
 	    moduleinfo(event->module, PMLOG_ERROR, msg, arg);
5b4e29
 	}
5b4e29
 	break;
5b4e29
@@ -1125,8 +1160,8 @@ pmSeriesDiscoverLabels(pmDiscoverEvent *
5b4e29
 		pmFreeLabelSets(domain->labelset, 1);
5b4e29
 	    domain->labelset = labels;
5b4e29
 	    domain->updated = 1;
5b4e29
-	} else {
5b4e29
-	    infofmt(msg, "failed to duplicate label set");
5b4e29
+	} else if (domain) {
5b4e29
+	    infofmt(msg, "failed to duplicate %s label set", "domain");
5b4e29
 	    moduleinfo(event->module, PMLOG_ERROR, msg, arg);
5b4e29
 	}
5b4e29
 	break;
5b4e29
@@ -1142,8 +1177,8 @@ pmSeriesDiscoverLabels(pmDiscoverEvent *
5b4e29
 		pmFreeLabelSets(cluster->labelset, 1);
5b4e29
 	    cluster->labelset = labels;
5b4e29
 	    cluster->updated = 1;
5b4e29
-	} else {
5b4e29
-	    infofmt(msg, "failed to duplicate label set");
5b4e29
+	} else if (cluster) {
5b4e29
+	    infofmt(msg, "failed to duplicate %s label set", "cluster");
5b4e29
 	    moduleinfo(event->module, PMLOG_ERROR, msg, arg);
5b4e29
 	}
5b4e29
 	break;
5b4e29
@@ -1159,8 +1194,8 @@ pmSeriesDiscoverLabels(pmDiscoverEvent *
5b4e29
 		pmFreeLabelSets(metric->labelset, 1);
5b4e29
 	    metric->labelset = labels;
5b4e29
 	    metric->updated = 1;
5b4e29
-	} else {
5b4e29
-	    infofmt(msg, "failed to duplicate label set");
5b4e29
+	} else if (metric) {
5b4e29
+	    infofmt(msg, "failed to duplicate %s label set", "item");
5b4e29
 	    moduleinfo(event->module, PMLOG_ERROR, msg, arg);
5b4e29
 	}
5b4e29
 	break;
5b4e29
@@ -1177,8 +1212,8 @@ pmSeriesDiscoverLabels(pmDiscoverEvent *
5b4e29
 		pmFreeLabelSets(indom->labelset, 1);
5b4e29
 		    indom->labelset = labels;
5b4e29
 	    indom->updated = 1;
5b4e29
-	} else {
5b4e29
-	    infofmt(msg, "failed to duplicate label set");
5b4e29
+	} else if (indom) {
5b4e29
+	    infofmt(msg, "failed to duplicate %s label set", "indom");
5b4e29
 	    moduleinfo(event->module, PMLOG_ERROR, msg, arg);
5b4e29
 	}
5b4e29
 	break;
5b4e29
@@ -1196,7 +1231,7 @@ pmSeriesDiscoverLabels(pmDiscoverEvent *
5b4e29
 	    if ((instance = dictFetchValue(indom->insts, &id)) == NULL)
5b4e29
 		continue;
5b4e29
 	    if ((labels = pmwebapi_labelsetdup(&sets[i])) == NULL) {
5b4e29
-		infofmt(msg, "failed to dup %s instance labels: %s",
5b4e29
+		infofmt(msg, "failed to dup indom %s instance label set: %s",
5b4e29
 			pmInDomStr_r(indom->indom, idbuf, sizeof(idbuf)),
5b4e29
 			pmErrStr_r(-ENOMEM, errmsg, sizeof(errmsg)));
5b4e29
 		moduleinfo(event->module, PMLOG_ERROR, msg, arg);
5b4e29
@@ -1229,10 +1264,13 @@ pmSeriesDiscoverMetric(pmDiscoverEvent *
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery) {
5b4e29
 	for (i = 0; i < numnames; i++)
5b4e29
-	    fprintf(stderr, "pmSeriesDiscoverMetric: [%d/%d] %s - %s\n",
5b4e29
+	    fprintf(stderr, "%s: [%d/%d] %s - %s\n", "pmSeriesDiscoverMetric",
5b4e29
 			i + 1, numnames, pmIDStr(desc->pmid), names[i]);
5b4e29
     }
5b4e29
 
5b4e29
+    if (baton == NULL || baton->slots == NULL || baton->slots->setup == 0)
5b4e29
+	return;
5b4e29
+
5b4e29
     if ((metric = pmwebapi_add_metric(&baton->pmapi.context,
5b4e29
 				NULL, desc, numnames, names)) == NULL) {
5b4e29
 	infofmt(msg, "%s: failed metric discovery", "pmSeriesDiscoverMetric");
5b4e29
@@ -1244,18 +1282,23 @@ pmSeriesDiscoverMetric(pmDiscoverEvent *
5b4e29
 void
5b4e29
 pmSeriesDiscoverValues(pmDiscoverEvent *event, pmResult *result, void *arg)
5b4e29
 {
5b4e29
+    pmDiscoverModule	*module = event->module;
5b4e29
     pmDiscover		*p = (pmDiscover *)event->data;
5b4e29
     seriesLoadBaton	*baton = p->baton;
5b4e29
     seriesGetContext	*context = &baton->pmapi;
5b4e29
+    discoverModuleData	*data = getDiscoverModuleData(module);
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery)
5b4e29
 	fprintf(stderr, "%s: result numpmids=%d\n", "pmSeriesDiscoverValues", result->numpmid);
5b4e29
 
5b4e29
+    if (baton == NULL || baton->slots == NULL || baton->slots->setup == 0)
5b4e29
+	return;
5b4e29
+
5b4e29
     seriesBatonReference(context, "pmSeriesDiscoverValues");
5b4e29
     baton->arg = arg;
5b4e29
     context->result = result;
5b4e29
 
5b4e29
-    series_cache_update(baton);
5b4e29
+    series_cache_update(baton, data->pmids);
5b4e29
 }
5b4e29
 
5b4e29
 void
5b4e29
@@ -1271,7 +1314,10 @@ pmSeriesDiscoverInDom(pmDiscoverEvent *e
5b4e29
     int			i;
5b4e29
 
5b4e29
     if (pmDebugOptions.discovery)
5b4e29
-	fprintf(stderr, "pmSeriesDiscoverInDom: %s\n", pmInDomStr(id));
5b4e29
+	fprintf(stderr, "%s: %s\n", "pmSeriesDiscoverInDom", pmInDomStr(id));
5b4e29
+
5b4e29
+    if (baton == NULL || baton->slots == NULL || baton->slots->setup == 0)
5b4e29
+	return;
5b4e29
 
5b4e29
     if ((domain = pmwebapi_add_domain(context, pmInDom_domain(id))) == NULL) {
5b4e29
 	infofmt(msg, "%s: failed indom discovery (domain %u)",
5b4e29
@@ -1303,11 +1349,10 @@ pmSeriesDiscoverText(pmDiscoverEvent *ev
5b4e29
     pmDiscover		*p = (pmDiscover *)event->data;
5b4e29
     seriesLoadBaton	*baton = p->baton;
5b4e29
 
5b4e29
-    (void)baton;
5b4e29
-    (void)ident;
5b4e29
-    (void)type;
5b4e29
-    (void)text;
5b4e29
-    (void)arg;
5b4e29
+    if (pmDebugOptions.discovery)
5b4e29
+	fprintf(stderr, "%s: ident=%u type=%u arg=%p\n",
5b4e29
+			"pmSeriesDiscoverText", ident, type, arg);
5b4e29
 
5b4e29
-    /* for Redis, help text will need special handling (RediSearch) */
5b4e29
+    if (baton == NULL || baton->slots == NULL || baton->slots->setup == 0)
5b4e29
+	return;
5b4e29
 }
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/query.c pcp-5.0.2/src/libpcp_web/src/query.c
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/query.c	2019-12-05 17:29:43.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/query.c	2020-02-03 13:23:15.265762890 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2017-2019 Red Hat.
5b4e29
+ * Copyright (c) 2017-2020 Red Hat.
5b4e29
  *
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
  * under the terms of the GNU Lesser General Public License as published
5b4e29
@@ -1243,24 +1243,43 @@ series_prepare_time_reply(
5b4e29
     series_query_end_phase(baton);
5b4e29
 }
5b4e29
 
5b4e29
+unsigned int
5b4e29
+series_value_count_only(timing_t *tp)
5b4e29
+{
5b4e29
+    if (tp->window.range || tp->window.delta ||
5b4e29
+	tp->window.start || tp->window.end)
5b4e29
+	return 0;
5b4e29
+    return tp->count;
5b4e29
+}
5b4e29
+
5b4e29
 static void
5b4e29
 series_prepare_time(seriesQueryBaton *baton, series_set_t *result)
5b4e29
 {
5b4e29
     timing_t		*tp = &baton->u.query.timing;
5b4e29
     unsigned char	*series = result->series;
5b4e29
     seriesGetSID	*sid;
5b4e29
-    char		buffer[64];
5b4e29
+    char		buffer[64], revbuf[64];
5b4e29
     sds			start, end, key, cmd;
5b4e29
-    unsigned int	i;
5b4e29
+    unsigned int	i, revlen = 0, reverse = 0;
5b4e29
+
5b4e29
+    /* if only 'count' is requested, work back from most recent value */
5b4e29
+    if ((reverse = series_value_count_only(tp)) != 0) {
5b4e29
+	revlen = pmsprintf(revbuf, sizeof(revbuf), "%u", reverse);
5b4e29
+	start = sdsnew("+");
5b4e29
+    } else {
5b4e29
+	start = sdsnew(timeval_stream_str(&tp->start, buffer, sizeof(buffer)));
5b4e29
+    }
5b4e29
 
5b4e29
-    start = sdsnew(timeval_stream_str(&tp->start, buffer, sizeof(buffer)));
5b4e29
     if (pmDebugOptions.series)
5b4e29
 	fprintf(stderr, "START: %s\n", start);
5b4e29
 
5b4e29
-    if (tp->end.tv_sec)
5b4e29
+    if (reverse)
5b4e29
+	end = sdsnew("-");
5b4e29
+    else if (tp->end.tv_sec)
5b4e29
 	end = sdsnew(timeval_stream_str(&tp->end, buffer, sizeof(buffer)));
5b4e29
     else
5b4e29
 	end = sdsnew("+");	/* "+" means "no end" - to the most recent */
5b4e29
+
5b4e29
     if (pmDebugOptions.series)
5b4e29
 	fprintf(stderr, "END: %s\n", end);
5b4e29
 
5b4e29
@@ -1277,12 +1296,21 @@ series_prepare_time(seriesQueryBaton *ba
5b4e29
 
5b4e29
 	key = sdscatfmt(sdsempty(), "pcp:values:series:%S", sid->name);
5b4e29
 
5b4e29
-	/* XRANGE key t1 t2 */
5b4e29
-	cmd = redis_command(4);
5b4e29
-	cmd = redis_param_str(cmd, XRANGE, XRANGE_LEN);
5b4e29
+	/* X[REV]RANGE key t1 t2 [count N] */
5b4e29
+	if (reverse) {
5b4e29
+	    cmd = redis_command(6);
5b4e29
+	    cmd = redis_param_str(cmd, XREVRANGE, XREVRANGE_LEN);
5b4e29
+	} else {
5b4e29
+	    cmd = redis_command(4);
5b4e29
+	    cmd = redis_param_str(cmd, XRANGE, XRANGE_LEN);
5b4e29
+	}
5b4e29
 	cmd = redis_param_sds(cmd, key);
5b4e29
 	cmd = redis_param_sds(cmd, start);
5b4e29
 	cmd = redis_param_sds(cmd, end);
5b4e29
+	if (reverse) {
5b4e29
+	    cmd = redis_param_str(cmd, "COUNT", sizeof("COUNT")-1);
5b4e29
+	    cmd = redis_param_str(cmd, revbuf, revlen);
5b4e29
+	}
5b4e29
 	redisSlotsRequest(baton->slots, XRANGE, key, cmd,
5b4e29
 				series_prepare_time_reply, sid);
5b4e29
     }
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/schema.c pcp-5.0.2/src/libpcp_web/src/schema.c
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/schema.c	2019-11-18 19:35:11.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/schema.c	2020-02-03 13:36:03.948721355 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2017-2019 Red Hat.
5b4e29
+ * Copyright (c) 2017-2020 Red Hat.
5b4e29
  *
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
  * under the terms of the GNU Lesser General Public License as published
5b4e29
@@ -819,7 +819,7 @@ redis_series_metric(redisSlots *slots, m
5b4e29
      */
5b4e29
 
5b4e29
     /* ensure all metric name strings are mapped */
5b4e29
-    for (i = 0; i < metric->numnames; i++) {
5b4e29
+    for (i = 0; metric->cached == 0 && i < metric->numnames; i++) {
5b4e29
 	assert(metric->names[i].sds != NULL);
5b4e29
 	seriesBatonReference(baton, "redis_series_metric");
5b4e29
 	redisGetMap(slots,
5b4e29
@@ -830,7 +830,8 @@ redis_series_metric(redisSlots *slots, m
5b4e29
 
5b4e29
     /* ensure all metric or instance label strings are mapped */
5b4e29
     if (metric->desc.indom == PM_INDOM_NULL || metric->u.vlist == NULL) {
5b4e29
-	series_metric_label_mapping(metric, baton);
5b4e29
+	if (metric->cached == 0)
5b4e29
+	    series_metric_label_mapping(metric, baton);
5b4e29
     } else {
5b4e29
 	for (i = 0; i < metric->u.vlist->listcount; i++) {
5b4e29
 	    value = &metric->u.vlist->value[i];
5b4e29
@@ -847,7 +848,8 @@ redis_series_metric(redisSlots *slots, m
5b4e29
 			series_name_mapping_callback,
5b4e29
 			baton->info, baton->userdata, baton);
5b4e29
 
5b4e29
-	    series_instance_label_mapping(metric, instance, baton);
5b4e29
+	    if (instance->cached == 0)
5b4e29
+		series_instance_label_mapping(metric, instance, baton);
5b4e29
 	}
5b4e29
     }
5b4e29
 
5b4e29
@@ -941,6 +943,9 @@ redis_series_metadata(context_t *context
5b4e29
     sds				cmd, key;
5b4e29
     int				i;
5b4e29
 
5b4e29
+    if (metric->cached)
5b4e29
+	goto check_instances;
5b4e29
+
5b4e29
     indom = pmwebapi_indom_str(metric, ibuf, sizeof(ibuf));
5b4e29
     pmid = pmwebapi_pmid_str(metric, pbuf, sizeof(pbuf));
5b4e29
     sem = pmwebapi_semantics_str(metric, sbuf, sizeof(sbuf));
5b4e29
@@ -1000,16 +1005,24 @@ redis_series_metadata(context_t *context
5b4e29
 	cmd = redis_param_sha(cmd, metric->names[i].hash);
5b4e29
     redisSlotsRequest(slots, SADD, key, cmd, redis_series_source_callback, arg);
5b4e29
 
5b4e29
+check_instances:
5b4e29
     if (metric->desc.indom == PM_INDOM_NULL || metric->u.vlist == NULL) {
5b4e29
-	redis_series_labelset(slots, metric, NULL, baton);
5b4e29
+	if (metric->cached == 0) {
5b4e29
+	    redis_series_labelset(slots, metric, NULL, baton);
5b4e29
+	    metric->cached = 1;
5b4e29
+	}
5b4e29
     } else {
5b4e29
 	for (i = 0; i < metric->u.vlist->listcount; i++) {
5b4e29
 	    value = &metric->u.vlist->value[i];
5b4e29
 	    if ((instance = dictFetchValue(metric->indom->insts, &value->inst)) == NULL)
5b4e29
 		continue;
5b4e29
-	    redis_series_instance(slots, metric, instance, baton);
5b4e29
-	    redis_series_labelset(slots, metric, instance, baton);
5b4e29
+	    if (instance->cached == 0 || metric->cached == 0) {
5b4e29
+		redis_series_instance(slots, metric, instance, baton);
5b4e29
+		redis_series_labelset(slots, metric, instance, baton);
5b4e29
+	    }
5b4e29
+	    instance->cached = 1;
5b4e29
 	}
5b4e29
+	metric->cached = 1;
5b4e29
     }
5b4e29
 }
5b4e29
 
5b4e29
@@ -1210,7 +1223,6 @@ redis_series_stream(redisSlots *slots, s
5b4e29
 
5b4e29
     redisSlotsRequest(slots, XADD, key, cmd, redis_series_stream_callback, baton);
5b4e29
 
5b4e29
-
5b4e29
     key = sdscatfmt(sdsempty(), "pcp:values:series:%s", hash);
5b4e29
     cmd = redis_command(3);	/* EXPIRE key timer */
5b4e29
     cmd = redis_param_str(cmd, EXPIRE, EXPIRE_LEN);
5b4e29
@@ -1228,9 +1240,6 @@ redis_series_streamed(sds stamp, metric_
5b4e29
     char			hashbuf[42];
5b4e29
     int				i;
5b4e29
 
5b4e29
-    if (metric->updated == 0)
5b4e29
-	return;
5b4e29
-
5b4e29
     for (i = 0; i < metric->numnames; i++) {
5b4e29
 	pmwebapi_hash_str(metric->names[i].hash, hashbuf, sizeof(hashbuf));
5b4e29
 	redis_series_stream(slots, stamp, metric, hashbuf, arg);
5b4e29
@@ -1545,7 +1554,10 @@ redis_load_slots_callback(
5b4e29
     redisSlots		*slots = baton->slots;
5b4e29
 
5b4e29
     seriesBatonCheckMagic(baton, MAGIC_SLOTS, "redis_load_slots_callback");
5b4e29
+
5b4e29
+    slots->setup = 1;	/* we've received initial response from Redis */
5b4e29
     slots->refresh = 0;	/* we're processing CLUSTER SLOTS command now */
5b4e29
+
5b4e29
     /* no cluster redirection checking is needed for this callback */
5b4e29
     sdsfree(cmd);
5b4e29
 
5b4e29
@@ -1832,12 +1844,47 @@ pmDiscoverSetup(pmDiscoverModule *module
5b4e29
     const char		fallback[] = "/var/log/pcp";
5b4e29
     const char		*paths[] = { "pmlogger", "pmmgr" };
5b4e29
     const char		*logdir = pmGetOptionalConfig("PCP_LOG_DIR");
5b4e29
+    struct dict		*config;
5b4e29
+    unsigned int	domain, serial;
5b4e29
+    pmInDom		indom;
5b4e29
     char		path[MAXPATHLEN];
5b4e29
     char		sep = pmPathSeparator();
5b4e29
-    int			i, sts, count = 0;
5b4e29
+    sds			option, *ids;
5b4e29
+    int			i, sts, nids, count = 0;
5b4e29
 
5b4e29
     if (data == NULL)
5b4e29
 	return -ENOMEM;
5b4e29
+    config = data->config;
5b4e29
+
5b4e29
+    /* double-check that we are supposed to be in here */
5b4e29
+    if ((option = pmIniFileLookup(config, "discover", "enabled"))) {
5b4e29
+	if (strcasecmp(option, "false") == 0)
5b4e29
+	    return 0;
5b4e29
+    }
5b4e29
+
5b4e29
+    /* prepare for optional metric and indom exclusion */
5b4e29
+    if ((option = pmIniFileLookup(config, "discover", "exclude.metrics"))) {
5b4e29
+	if ((data->pmids = dictCreate(&intKeyDictCallBacks, NULL)) == NULL)
5b4e29
+	    return -ENOMEM;
5b4e29
+	/* parse regular expression string for matching on metric names */
5b4e29
+	regcomp(&data->exclude_names, option, REG_EXTENDED|REG_NOSUB);
5b4e29
+    }
5b4e29
+    if ((option = pmIniFileLookup(config, "discover", "exclude.indoms"))) {
5b4e29
+	if ((data->indoms = dictCreate(&intKeyDictCallBacks, NULL)) == NULL)
5b4e29
+	    return -ENOMEM;
5b4e29
+	/* parse comma-separated indoms in 'option', convert to pmInDom */
5b4e29
+	if ((ids = sdssplitlen(option, sdslen(option), ",", 1, &nids))) {
5b4e29
+	    data->exclude_indoms = nids;
5b4e29
+	    for (i = 0; i < nids; i++) {
5b4e29
+		if (sscanf(ids[i], "%u.%u", &domain, &serial) == 2) {
5b4e29
+		    indom = pmInDom_build(domain, serial);
5b4e29
+		    dictAdd(data->indoms, &indom, NULL);
5b4e29
+		}
5b4e29
+		sdsfree(ids[i]);
5b4e29
+	    }
5b4e29
+	    free(ids);
5b4e29
+	}
5b4e29
+    }
5b4e29
 
5b4e29
     /* create global EVAL hashes and string map caches */
5b4e29
     redisGlobalsInit(data->config);
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/schema.h pcp-5.0.2/src/libpcp_web/src/schema.h
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/schema.h	2019-10-11 17:16:29.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/schema.h	2020-02-03 13:23:15.266762879 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2017-2018 Red Hat.
5b4e29
+ * Copyright (c) 2017-2020 Red Hat.
5b4e29
  * 
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
  * under the terms of the GNU Lesser General Public License as published
5b4e29
@@ -51,6 +51,10 @@
5b4e29
 #define HSET_LEN	(sizeof(HSET)-1)
5b4e29
 #define HVALS		"HVALS"
5b4e29
 #define HVALS_LEN	(sizeof(HVALS)-1)
5b4e29
+#define INFO		"INFO"
5b4e29
+#define INFO_LEN	(sizeof(INFO)-1)
5b4e29
+#define PING		"PING"
5b4e29
+#define PING_LEN	(sizeof(PING)-1)
5b4e29
 #define PUBLISH		"PUBLISH"
5b4e29
 #define PUBLISH_LEN	(sizeof(PUBLISH)-1)
5b4e29
 #define SADD		"SADD"
5b4e29
@@ -63,6 +67,8 @@
5b4e29
 #define XADD_LEN	(sizeof(XADD)-1)
5b4e29
 #define XRANGE		"XRANGE"
5b4e29
 #define XRANGE_LEN	(sizeof(XRANGE)-1)
5b4e29
+#define XREVRANGE	"XREVRANGE"
5b4e29
+#define XREVRANGE_LEN	(sizeof(XREVRANGE)-1)
5b4e29
 
5b4e29
 /* create a Redis protocol command (e.g. XADD, SMEMBER) */
5b4e29
 static inline sds
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/slots.c pcp-5.0.2/src/libpcp_web/src/slots.c
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/slots.c	2019-10-11 17:16:29.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/slots.c	2020-02-03 13:23:15.266762879 +1100
5b4e29
@@ -356,6 +356,21 @@ redisSlotsRequest(redisSlots *slots, con
5b4e29
 
5b4e29
     if (UNLIKELY(pmDebugOptions.desperate))
5b4e29
 	fputs(cmd, stderr);
5b4e29
+    if (UNLIKELY(!key && !slots->setup)) {
5b4e29
+	/*
5b4e29
+	 * First request must be CLUSTER, PING, or similar - must
5b4e29
+	 * not allow regular requests until these have completed.
5b4e29
+	 * This is because the low layers accumulate async requests
5b4e29
+	 * until connection establishment, which might not happen.
5b4e29
+	 * Over time this becomes a memory leak - if we do not ever
5b4e29
+	 * establish an initial connection).
5b4e29
+	 */
5b4e29
+	if (strcmp(topic, CLUSTER) != 0 &&
5b4e29
+	    strcmp(topic, PING) != 0 && strcmp(topic, INFO) != 0) {
5b4e29
+	    sdsfree(cmd);
5b4e29
+	    return -ENOTCONN;
5b4e29
+	}
5b4e29
+    }
5b4e29
 
5b4e29
     sts = redisAsyncFormattedCommand(context, callback, cmd, arg);
5b4e29
     if (key)
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/slots.h pcp-5.0.2/src/libpcp_web/src/slots.h
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/slots.h	2019-04-08 09:11:00.000000000 +1000
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/slots.h	2020-02-03 13:23:15.266762879 +1100
5b4e29
@@ -44,10 +44,11 @@ typedef struct redisSlotRange {
5b4e29
 typedef struct redisSlots {
5b4e29
     unsigned int	counter;
5b4e29
     unsigned int	nslots;
5b4e29
+    unsigned int	setup;		/* slots info all successfully setup */
5b4e29
+    unsigned int	refresh;	/* do slot refresh whenever possible */
5b4e29
     redisSlotRange	*slots;		/* all instances; e.g. CLUSTER SLOTS */
5b4e29
     redisMap		*keymap;	/* map command names to key position */
5b4e29
     dict		*contexts;	/* async contexts access by hostspec */
5b4e29
-    unsigned int	refresh;	/* do slot refresh whenever possible */
5b4e29
     void		*events;
5b4e29
 } redisSlots;
5b4e29
 
5b4e29
diff -Naurp pcp-5.0.2.orig/src/libpcp_web/src/util.c pcp-5.0.2/src/libpcp_web/src/util.c
5b4e29
--- pcp-5.0.2.orig/src/libpcp_web/src/util.c	2019-12-10 17:39:49.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/libpcp_web/src/util.c	2020-02-03 13:23:15.266762879 +1100
5b4e29
@@ -1,5 +1,5 @@
5b4e29
 /*
5b4e29
- * Copyright (c) 2017-2019 Red Hat.
5b4e29
+ * Copyright (c) 2017-2020 Red Hat.
5b4e29
  *
5b4e29
  * This library is free software; you can redistribute it and/or modify it
5b4e29
  * under the terms of the GNU Lesser General Public License as published
5b4e29
@@ -535,6 +535,8 @@ pmwebapi_metric_hash(metric_t *metric)
5b4e29
 	sdsclear(identifier);
5b4e29
     }
5b4e29
     sdsfree(identifier);
5b4e29
+
5b4e29
+    metric->cached = 0;
5b4e29
 }
5b4e29
 
5b4e29
 void
5b4e29
@@ -574,6 +576,8 @@ pmwebapi_instance_hash(indom_t *ip, inst
5b4e29
     SHA1Update(&shactx, (unsigned char *)identifier, sdslen(identifier));
5b4e29
     SHA1Final(instance->name.hash, &shactx);
5b4e29
     sdsfree(identifier);
5b4e29
+
5b4e29
+    instance->cached = 0;
5b4e29
 }
5b4e29
 
5b4e29
 sds
5b4e29
@@ -1046,7 +1050,6 @@ pmwebapi_add_instance(struct indom *indo
5b4e29
 	    instance->name.sds = sdscatlen(instance->name.sds, name, length);
5b4e29
 	    pmwebapi_string_hash(instance->name.id, name, length);
5b4e29
 	    pmwebapi_instance_hash(indom, instance);
5b4e29
-	    instance->cached = 0;
5b4e29
 	}
5b4e29
 	return instance;
5b4e29
     }
5b4e29
@@ -1202,12 +1205,14 @@ struct metric *
5b4e29
 pmwebapi_add_metric(context_t *cp, const sds base, pmDesc *desc, int numnames, char **names)
5b4e29
 {
5b4e29
     struct metric	*metric;
5b4e29
-    sds			name = sdsempty();
5b4e29
+    sds			name;
5b4e29
     int			i;
5b4e29
 
5b4e29
     /* search for a match on any of the given names */
5b4e29
     if (base && (metric = dictFetchValue(cp->metrics, base)) != NULL)
5b4e29
 	return metric;
5b4e29
+
5b4e29
+    name = sdsempty();
5b4e29
     for (i = 0; i < numnames; i++) {
5b4e29
 	sdsclear(name);
5b4e29
 	name = sdscat(name, names[i]);
5b4e29
@@ -1217,6 +1222,7 @@ pmwebapi_add_metric(context_t *cp, const
5b4e29
 	}
5b4e29
     }
5b4e29
     sdsfree(name);
5b4e29
+
5b4e29
     return pmwebapi_new_metric(cp, base, desc, numnames, names);
5b4e29
 }
5b4e29
 
5b4e29
@@ -1230,21 +1236,24 @@ pmwebapi_new_pmid(context_t *cp, const s
5b4e29
     int			sts, numnames;
5b4e29
 
5b4e29
     if ((sts = pmUseContext(cp->context)) < 0) {
5b4e29
-	fprintf(stderr, "failed to use context for PMID %s: %s",
5b4e29
+	fprintf(stderr, "%s: failed to use context for PMID %s: %s\n",
5b4e29
+		"pmwebapi_new_pmid",
5b4e29
 		pmIDStr_r(pmid, buffer, sizeof(buffer)),
5b4e29
 		pmErrStr_r(sts, errmsg, sizeof(errmsg)));
5b4e29
     } else if ((sts = pmLookupDesc(pmid, &desc)) < 0) {
5b4e29
 	if (sts == PM_ERR_IPC)
5b4e29
 	    cp->setup = 0;
5b4e29
 	if (pmDebugOptions.series)
5b4e29
-	    fprintf(stderr, "failed to lookup metric %s descriptor: %s",
5b4e29
+	    fprintf(stderr, "%s: failed to lookup metric %s descriptor: %s\n",
5b4e29
+		    "pmwebapi_new_pmid",
5b4e29
 		    pmIDStr_r(pmid, buffer, sizeof(buffer)),
5b4e29
 		    pmErrStr_r(sts, errmsg, sizeof(errmsg)));
5b4e29
     } else if ((numnames = sts = pmNameAll(pmid, &names)) < 0) {
5b4e29
 	if (sts == PM_ERR_IPC)
5b4e29
 	    cp->setup = 0;
5b4e29
 	if (pmDebugOptions.series)
5b4e29
-	    fprintf(stderr, "failed to lookup metric %s names: %s",
5b4e29
+	    fprintf(stderr, "%s: failed to lookup metric %s names: %s\n",
5b4e29
+		    "pmwebapi_new_pmid",
5b4e29
 		    pmIDStr_r(pmid, buffer, sizeof(buffer)),
5b4e29
 		    pmErrStr_r(sts, errmsg, sizeof(errmsg)));
5b4e29
     } else {
5b4e29
diff -Naurp pcp-5.0.2.orig/src/pmproxy/pmproxy.conf pcp-5.0.2/src/pmproxy/pmproxy.conf
5b4e29
--- pcp-5.0.2.orig/src/pmproxy/pmproxy.conf	2019-08-09 15:50:17.000000000 +1000
5b4e29
+++ pcp-5.0.2/src/pmproxy/pmproxy.conf	2020-02-03 13:36:03.948721355 +1100
5b4e29
@@ -43,6 +43,11 @@ secure.enabled = true
5b4e29
 # propogate archives from pmlogger(1) into Redis querying
5b4e29
 enabled = true
5b4e29
 
5b4e29
+# metrics name regex to skip during discovery (eg due to high volume)
5b4e29
+exclude.metrics = proc.*
5b4e29
+
5b4e29
+# comma-separated list of instance domains to skip during discovery
5b4e29
+exclude.indoms = 3.9,79.7
5b4e29
 
5b4e29
 #####################################################################
5b4e29
 ## settings for fast, scalable time series quering via Redis
5b4e29
diff -Naurp pcp-5.0.2.orig/src/pmproxy/src/redis.c pcp-5.0.2/src/pmproxy/src/redis.c
5b4e29
--- pcp-5.0.2.orig/src/pmproxy/src/redis.c	2019-12-02 16:39:33.000000000 +1100
5b4e29
+++ pcp-5.0.2/src/pmproxy/src/redis.c	2020-02-03 13:36:13.585620539 +1100
5b4e29
@@ -145,11 +145,11 @@ setup_redis_module(struct proxy *proxy)
5b4e29
 	proxy->slots = redisSlotsConnect(proxy->config,
5b4e29
 			flags, proxylog, on_redis_connected,
5b4e29
 			proxy, proxy->events, proxy);
5b4e29
-	if (archive_discovery)
5b4e29
+	if (archive_discovery && series_queries)
5b4e29
 	    pmDiscoverSetSlots(&redis_discover.module, proxy->slots);
5b4e29
     }
5b4e29
 
5b4e29
-    if (archive_discovery) {
5b4e29
+    if (archive_discovery && series_queries) {
5b4e29
 	pmDiscoverSetEventLoop(&redis_discover.module, proxy->events);
5b4e29
 	pmDiscoverSetConfiguration(&redis_discover.module, proxy->config);
5b4e29
 	pmDiscoverSetMetricRegistry(&redis_discover.module, metric_registry);