Преглед изворни кода

HPCC-26022 Add metrics unit tests

Added unit tests for metrics framework

Signed-off-by: Ken Rowland kenneth.rowland@lexisnexisrisk.com
Ken Rowland пре 3 година
родитељ
комит
13ca30aea7

+ 17 - 8
system/jlib/jmetrics.cpp

@@ -28,19 +28,11 @@ MODULE_EXIT()
 }
 
 
-struct hpccMetrics::SinkInfo
-{
-    explicit SinkInfo(MetricSink *_pSink) : pSink{_pSink} {}
-    MetricSink *pSink = nullptr;             // ptr to the sink
-    std::vector<std::string> reportMetrics;   // vector of metrics to report (empty for none)
-};
-
 MetricsReporter &hpccMetrics::queryMetricsReporter()
 {
     return *metricsReporter.query([] { return new MetricsReporter; });
 }
 
-
 MetricsReporter::~MetricsReporter()
 {
     for (auto const &sinkIt : sinks)
@@ -202,6 +194,23 @@ MetricSink *MetricsReporter::getSinkFromLib(const char *type, const char *sinkNa
     return pSink;
 }
 
+// Method for use when testing
+void MetricsReporter::addSink(MetricSink *pSink, const char *name)
+{
+    //
+    // Add the sink if it does not already exist, otherwise delete the sink because
+    // we are taking ownership.
+    auto sinkIt = sinks.find(name);
+    if (sinkIt == sinks.end())
+    {
+        sinks.insert({std::string(name), std::unique_ptr<SinkInfo>(new SinkInfo(pSink))});
+    }
+    else
+    {
+        delete pSink;
+    }
+}
+
 
 PeriodicMetricSink::PeriodicMetricSink(const char *name, const char *type, const IPropertyTree *pSettingsTree) :
     MetricSink(name, type),

+ 8 - 2
system/jlib/jmetrics.hpp

@@ -120,7 +120,7 @@ class jlib_decl CounterMetric : public MetricVal
 public:
     CounterMetric(const char *name, const char *description) :
         MetricVal{name, description, MetricType::METRICS_COUNTER}  { }
-    void inc(uint64_t val)
+    void inc(uint64_t val = 1)
     {
         value.fetch_add(val);
     }
@@ -218,7 +218,12 @@ protected:
 
 extern "C" { typedef hpccMetrics::MetricSink* (*getSinkInstance)(const char *, const IPropertyTree *pSettingsTree); }
 
-struct SinkInfo;
+struct SinkInfo
+{
+    explicit SinkInfo(MetricSink *_pSink) : pSink{_pSink} {}
+    MetricSink *pSink = nullptr;             // ptr to the sink
+    std::vector<std::string> reportMetrics;   // vector of metrics to report (empty for none)
+};
 
 class jlib_decl MetricsReporter
 {
@@ -226,6 +231,7 @@ public:
     MetricsReporter() = default;
     ~MetricsReporter();
     void init(IPropertyTree *pMetricsTree);
+    void addSink(MetricSink *pSink, const char *name);  // for use by unit tests
     void addMetric(const std::shared_ptr<IMetric> &pMetric);
     void startCollecting();
     void stopCollecting();

+ 2 - 0
testing/unittests/CMakeLists.txt

@@ -47,6 +47,8 @@ set (    SRCS
          configmgr/ConfigMgrUnitTests.cpp
          configmgr/ConfigMgrTemplateTests.cpp
          configmgr/ConfigMgrHPCCTests.cpp
+         metrics/MetricFrameworkTests.cpp
+         metrics/PeriodicSinkTests.cpp
     )
 endif ()
 

+ 189 - 0
testing/unittests/metrics/MetricFrameworkTests.cpp

@@ -0,0 +1,189 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2021 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifdef _USE_CPPUNIT
+
+#include <cppunit/TestFixture.h>
+#include "unittests.hpp"
+#include <algorithm>
+
+#include "jmetrics.hpp"
+
+using namespace hpccMetrics;
+
+class MetricFrameworkTestSink : public MetricSink
+{
+public:
+    explicit MetricFrameworkTestSink(const char *name) :
+        MetricSink(name, "test") { }
+
+    ~MetricFrameworkTestSink() override = default;
+
+    void startCollection(MetricsReporter *_pReporter) override
+    {
+        pReporter = _pReporter;
+        isCollecting = true;
+    }
+
+    void stopCollection() override
+    {
+        isCollecting = false;
+    }
+
+    std::vector<std::shared_ptr<IMetric>> getReportMetrics() const
+    {
+        return pReporter->queryMetricsForReport(name);
+    }
+
+public:
+    bool isCollecting = false;
+};
+
+
+class MetricFrameworkTests : public CppUnit::TestFixture
+{
+public:
+    MetricFrameworkTests()
+    {
+        pTestSink = new MetricFrameworkTestSink("testsink");
+        frameworkTestReporter.addSink(pTestSink, "testsink");
+    }
+
+    ~MetricFrameworkTests() = default;
+
+    CPPUNIT_TEST_SUITE(MetricFrameworkTests);
+        CPPUNIT_TEST(Test_counter_metric_increments_properly);
+        CPPUNIT_TEST(Test_gauge_metric_updates_properly);
+        CPPUNIT_TEST(Test_custom_metric);
+        CPPUNIT_TEST(Test_reporter_calls_sink_to_start_and_stop_collection);
+        CPPUNIT_TEST(Test_reporter_manages_metrics_properly);
+    CPPUNIT_TEST_SUITE_END();
+
+protected:
+
+
+    void Test_counter_metric_increments_properly()
+    {
+        std::shared_ptr<CounterMetric> pCounter = std::make_shared<CounterMetric>("test-counter", "description");
+        CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(pCounter->queryValue()));
+
+        //
+        // Test default increment (by 1)
+        pCounter->inc();
+        int counterValue = pCounter->queryValue();
+        CPPUNIT_ASSERT_EQUAL(1, counterValue);
+
+        //
+        // Test increment by > 1
+        pCounter->inc(2);
+        counterValue = pCounter->queryValue();
+        CPPUNIT_ASSERT_EQUAL(3, counterValue);
+    }
+
+
+    void Test_gauge_metric_updates_properly()
+    {
+        std::shared_ptr<GaugeMetric> pGauge = std::make_shared<GaugeMetric>("test-gauge", "description");
+        int gaugeValue = pGauge->queryValue();
+        CPPUNIT_ASSERT_EQUAL(0, gaugeValue);
+
+        //
+        // Test initial setting of gauge
+        pGauge->set(25);
+        gaugeValue = pGauge->queryValue();
+        CPPUNIT_ASSERT_EQUAL(25, gaugeValue);
+
+        //
+        // Test updating gauge
+        pGauge->add(10);
+        gaugeValue = pGauge->queryValue();
+        CPPUNIT_ASSERT_EQUAL(35, gaugeValue);
+
+        pGauge->add(-5);
+        gaugeValue = pGauge->queryValue();
+        CPPUNIT_ASSERT_EQUAL(30, gaugeValue);
+    }
+
+
+    void Test_custom_metric()
+    {
+        int customCounter = 0;
+        std::shared_ptr<CustomMetric<int>> pCustomCounter = std::make_shared<CustomMetric<int>>("custom-counter", "description", METRICS_COUNTER, customCounter);
+        int customCounterValue = pCustomCounter->queryValue();
+        CPPUNIT_ASSERT_EQUAL(0, customCounterValue);
+
+        customCounter++;
+        customCounterValue = pCustomCounter->queryValue();
+        CPPUNIT_ASSERT_EQUAL(1, customCounterValue);
+    }
+
+
+    void Test_reporter_calls_sink_to_start_and_stop_collection()
+    {
+        frameworkTestReporter.startCollecting();
+        CPPUNIT_ASSERT_EQUAL(true, pTestSink->isCollecting);
+        frameworkTestReporter.stopCollecting();
+        CPPUNIT_ASSERT_EQUAL(false, pTestSink->isCollecting);
+    }
+
+
+    void Test_reporter_manages_metrics_properly()
+    {
+        int numAdded;
+        std::shared_ptr<CounterMetric> pCounter = std::make_shared<CounterMetric>("test-counter", "description");
+        std::shared_ptr<GaugeMetric> pGauge = std::make_shared<GaugeMetric>("test-gauge", "description");
+        frameworkTestReporter.addMetric(pCounter);
+        frameworkTestReporter.addMetric(pGauge);
+        numAdded = 2;
+
+        frameworkTestReporter.startCollecting();
+
+        //
+        // Make sure the initial list is correct
+        int numMetrics = frameworkTestReporter.queryMetricsForReport("testsink").size();
+        CPPUNIT_ASSERT_EQUAL(numAdded, numMetrics);
+
+        //
+        // Add a metric while reporting is enabled and make sure it is returned
+        std::shared_ptr<CounterMetric> pNewCounter = std::make_shared<CounterMetric>("test-newcounter", "description");
+        frameworkTestReporter.addMetric(pNewCounter);
+        numAdded++;
+
+        numMetrics = frameworkTestReporter.queryMetricsForReport("testsink").size();
+        CPPUNIT_ASSERT_EQUAL(numAdded, numMetrics);
+
+        //
+        // Destroy a metric and ensure it is no longer in the list of report metrics
+        pNewCounter = nullptr;
+        numAdded--;
+
+        numMetrics = frameworkTestReporter.queryMetricsForReport("testsink").size();
+        CPPUNIT_ASSERT_EQUAL(numAdded, numMetrics);
+
+        frameworkTestReporter.stopCollecting();
+    }
+
+protected:
+    MetricsReporter frameworkTestReporter;
+    MetricFrameworkTestSink *pTestSink;
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION( MetricFrameworkTests );
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( MetricFrameworkTests, "MetricFrameworkTests" );
+
+#endif

+ 123 - 0
testing/unittests/metrics/PeriodicSinkTests.cpp

@@ -0,0 +1,123 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2021 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifdef _USE_CPPUNIT
+
+#include <cppunit/TestFixture.h>
+#include "unittests.hpp"
+#include <algorithm>
+
+#include "jptree.hpp"
+#include "jmetrics.hpp"
+
+using namespace hpccMetrics;
+
+class PeriodicTestSink : public PeriodicMetricSink
+{
+public:
+    explicit PeriodicTestSink(const char *name, const IPropertyTree *pSettingsTree) :
+        PeriodicMetricSink(name, "test", pSettingsTree) { }
+
+    ~PeriodicTestSink() = default;
+
+protected:
+    virtual void prepareToStartCollecting() override
+    {
+        prepareCalled = true;
+        numCollections = 0;
+    }
+
+    virtual void collectingHasStopped() override
+    {
+        stopCollectionNotificationCalled = true;
+    }
+
+    void doCollection() override
+    {
+        numCollections++;
+    }
+
+public:
+    bool prepareCalled = false;
+    bool stopCollectionNotificationCalled = false;
+    unsigned numCollections = 0;
+};
+
+
+const char *periodicSinkSettingsTestYml = R"!!(period: 2
+)!!";
+
+static const unsigned period = 2;
+
+
+class PeriodicSinkTests : public CppUnit::TestFixture
+{
+public:
+    PeriodicSinkTests()
+    {
+        //
+        // Load the settings then set the period using the global var
+        Owned<IPropertyTree> pSettings = createPTreeFromYAMLString(periodicSinkSettingsTestYml, ipt_none, ptr_ignoreWhiteSpace, nullptr);
+        pSettings->setPropInt("@period", period);
+        pPeriodicTestSink = new PeriodicTestSink("periodic_test_sink", pSettings);
+        periodicSinkTestReporter.addSink(pPeriodicTestSink, "periodic_test_sink");
+    }
+
+    ~PeriodicSinkTests() = default;
+
+    CPPUNIT_TEST_SUITE(PeriodicSinkTests);
+        CPPUNIT_TEST(Test_sets_period_correctly);
+    CPPUNIT_TEST_SUITE_END();
+
+protected:
+
+    void Test_sets_period_correctly()
+    {
+        //
+        // To test setting the period correctly, start collection and delay a multiple of that period.
+        // Stop collection and ask the test sink how many collections were done. If the count is +/- 1
+        // from the wait period multiple used, then we are close enough
+        unsigned multiple = 3;
+        periodicSinkTestReporter.startCollecting();
+
+        //
+        // Check that the sink called to prepare for collection
+        CPPUNIT_ASSERT(pPeriodicTestSink->prepareCalled);
+
+        // wait... then stop collecting
+        sleep(multiple * period);
+        periodicSinkTestReporter.stopCollecting();
+
+        unsigned numCollections = pPeriodicTestSink->numCollections;
+
+        bool numReportsCorrect = (numCollections >= multiple-1) && (numCollections <= multiple+1);
+        CPPUNIT_ASSERT_EQUAL(true, numReportsCorrect);
+
+        //
+        // Verify collection was stopped
+        CPPUNIT_ASSERT(pPeriodicTestSink->stopCollectionNotificationCalled);
+    }
+
+protected:
+    MetricsReporter periodicSinkTestReporter;
+    PeriodicTestSink *pPeriodicTestSink = nullptr;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION( PeriodicSinkTests );
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( PeriodicSinkTests, "PeriodicSinkTests" );
+
+#endif