Skip to content

Commit

Permalink
HPCC-33338 Convert statically allocated metrics to dynamic allocation
Browse files Browse the repository at this point in the history
Removed statically allocated metrics in favor of using MODULE_INIT

Signed-Off-By: Kenneth Rowland [email protected]
  • Loading branch information
kenrowland committed Mar 4, 2025
1 parent 5b9ce6b commit a5b71de
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 38 deletions.
7 changes: 6 additions & 1 deletion dali/base/danqs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
#pragma warning (disable : 4355)
#endif

static auto pNqRequestsCount = hpccMetrics::registerCounterMetric("dali.nq.requests.received", "The total number of Dali NQ requests received", SMeasureCount);
static std::shared_ptr<hpccMetrics::CounterMetric> pNqRequestsCount;
MODULE_INIT(INIT_PRIORITY_STANDARD)
{
pNqRequestsCount = hpccMetrics::registerCounterMetric("dali.nq.requests.received", "The total number of Dali NQ requests received", SMeasureCount);
return true;
}

enum MQueueRequestKind {
MQR_ADD_QUEUE,
Expand Down
18 changes: 14 additions & 4 deletions dali/base/dasds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,20 @@ static unsigned readWriteTimeout = 60000;
#define DEFAULT_LCIDLE_RATE 1 // 1 write transactions per idle period. <= this rate is deemed idle (suitable for save)
#define STORENOTSAVE_WARNING_PERIOD 72 // hours

static auto pSdsRequestsReceived = hpccMetrics::registerCounterMetric("dali.sds.requests.received", "The total number of Dali SDS requests received", SMeasureCount);
static auto pSdsRequestsStarted = hpccMetrics::registerCounterMetric("dali.sds.requests.started", "The total number of Dali SDS requests started", SMeasureCount);
static auto pSdsRequestsCompleted = hpccMetrics::registerCounterMetric("dali.sds.requests.completed", "The total number of Dali SDS requests completed", SMeasureCount);
static auto pSdsRequestsPending = hpccMetrics::registerGaugeFromCountersMetric("dali.sds.requests.pending", "Current number of pending SDS requests", SMeasureCount, pSdsRequestsReceived, pSdsRequestsStarted);
static std::shared_ptr<hpccMetrics::CounterMetric> pSdsRequestsReceived;
static std::shared_ptr<hpccMetrics::CounterMetric> pSdsRequestsStarted;
static std::shared_ptr<hpccMetrics::CounterMetric> pSdsRequestsCompleted;
static std::shared_ptr<hpccMetrics::GaugeMetricFromCounters> pSdsRequestsPending;

MODULE_INIT(INIT_PRIORITY_STANDARD)
{
pSdsRequestsReceived = hpccMetrics::registerCounterMetric("dali.sds.requests.received", "The total number of Dali SDS requests received", SMeasureCount);
pSdsRequestsStarted = hpccMetrics::registerCounterMetric("dali.sds.requests.started", "The total number of Dali SDS requests started", SMeasureCount);
pSdsRequestsCompleted = hpccMetrics::registerCounterMetric("dali.sds.requests.completed", "The total number of Dali SDS requests completed", SMeasureCount);
pSdsRequestsPending = hpccMetrics::registerGaugeFromCountersMetric("dali.sds.requests.pending", "Current number of pending SDS requests", SMeasureCount, pSdsRequestsReceived, pSdsRequestsStarted);
return true;
}


// #define TEST_NOTIFY_HANDLER

Expand Down
104 changes: 78 additions & 26 deletions devdoc/Metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ add jlib as a dependent lib (if not already doing so).
The general steps for instrumentation are
1. Determine what component state is to be measured. This requires deep knowledge of the
1. Determine what component state to measure. This requires deep knowledge of the
component.
2. Create metric objects for each measured state and add each to the manager.
3. Add updates for each metric throughout the component to track state
Expand All @@ -490,8 +490,8 @@ The general steps for instrumentation are
The component may retrieve the metrics manager singleton object and manually create
and add metrics. To simplify the process of metric creation (2 above), the framework
provides a set of register functions for each metric. Each creates a metric, registers
it, and returns a shared pointer to the metric (see below).Use of the register functions
is the recommended approach and alleviates the component from needing to get a reference
it, and returns a shared pointer to the metric (see below). Use of the register functions
is the recommended approach and alleviates the component from needing a reference
to the manager.
If the component desires to get a reference to the metrics manager singleton, the following
Expand All @@ -515,50 +515,102 @@ The following is an example of creating and registering a counter metric.
std::shared_ptr<CounterMetric> pCounter = registerCounterMetric("metric.name", "description");
```

Metrics can be created in one of two ways: statically or centrally. Static creation should
ONLY be reserved for cases where the component does not have some central place where all metrics
can be created. The primary reason to avoid static creation is that it if there is a problem
during creation, any log messages emitted are lost. Debugging is more difficult in this case.
### Metric Creation
The method by which a component creates a metric depends on component implementation. The sections
that follow provide guidance on different methods of metric creation. Choose the method that
best matches component implementation.

Central creation is the recommended approach. This is where all metrics are created in a single
place, such as the component's constructor. This allows for all metrics to be created at once. If
there is a problem during creation, any emitted log messages are not lost.
Metrics are guaranteed to always be created. Problems only arise when registering. If there is a
failure during registration, the metric can still be used within the component to update state.
However, the metric will not be reported during collection.

In some cases, a component may be created and destroyed multiple times. An example is a protocol
handler. For these cases a blend of static and central creation is recommended. The code below
details how:
#### During Module Initialization
If the component is a shared library whose code is not also shared with a main application,
then the component should create metrics during module initialization. The following code shows how
to register a metric during module initialization:

```cpp
static std::once_flag metricsInitialized;
static std::shared_ptr<hpccMetrics::CounterMetric> pCount1;
... others as needed
static std::shared_ptr<hpccMetrics::CounterMetric> pCount;

MODULE_INIT(INIT_PRIORITY_STANDARD)
{
pCount = hpccMetrics::registerCounterMetric("name", "description", SMeasureCount);
return true;
}

MODULE_EXIT()
{
pCount = nullptr; // note this is optional
}
```
Note that the metric is not required to be a static variable. Other options include:
* A global variable
* A static class member variable
#### During Construction
If the metric is based on measuring state in a class that is created and destroyed multiple times,
then the metric should be a static variable in the class. The metric is created during the first
construction and then reused for subsequent constructions. The metric is destroyed when the class
static variables are destroyed. The following code shows how to register a metric once:
```cpp
//
// Allocate the class static variables
static std::once_flag ClassX::metricsInitialized;
static std::shared_ptr<hpccMetrics::CounterMetric> ClassX::pCount1;
class ClassX
{
public:
ClassX();
void method1();
private:
static std::shared_ptr<hpccMetrics::CounterMetric> pCount1;
static std::once_flag metricsInitialized;
}
ClassX::ClassX()
{
std::call_once(metricsInitialized, []()
{
pCount1 = registerCounterMetric("metric1", "description");
... others as needed
});
pCount1 = hpccMetrics::registerCounterMetric("metric1", "description");
});
}
void ClassX::method1()
{
pCount1->inc(1); // example of (3 above)
pCount1->inc(1); // Update state
}
```

The code above uses a static _once_flag_ and a _call_once_ block to ensure that the metrics are
The static _once_flag_ and _call_once_ block ensure that the metric is
only created once. The first time the constructor is called, the lambda is executed. Subsequent
calls to the constructor do not execute the lambda. The static shared\_ptr for each metric maintain
references to each metric making them available throughout the class.

Invalid metrics are always created, but not registered. This allows component code to continue
taking measurements, update state, and count events with a valid metric. However, no results
will be reported. Essentially, the metric is a black hole.
#### Static Creation
Static creation should be a last resort. If the above methods do not solve the problem of metric
allocation and registering, then static creation is the next best choice. The primary reason to
avoid static creation is that if there is a problem during creation, any log messages
or exceptions are lost. Debugging is more difficult in this case.

An example of static creation is shown below:

```cpp

//
// Allocate the class static variables
static std::shared_ptr<hpccMetrics::CounterMetric> pCount1 = hpccMetrics::registerCounterMetric("metric1", "description");

```

### Metric State Updates

Note in the example above, only a single line of code is needed to update state. This is true for
Note in the examples above, only a single line of code is needed to update state. This is true for
all metric types.

That's it! There are no component requirements related to collection or
Expand All @@ -569,7 +621,7 @@ sinks.
If your component is already tracking a metric, you may not need to convert it to
a defined framework metric type. Instead, you can create a custom metric and
pass a reference to the existing metric value. The metric value, however, must be
a scalar value that can be converted to a 64bit unsigned integer (\_\_uint64).
a scalar value that can cast to a 64bit unsigned integer (\_\_uint64).

The following is an example of creating a custom metric (you must provide the metric type):

Expand Down
15 changes: 9 additions & 6 deletions esp/platform/espprotocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,29 @@
#endif
#include "jmetrics.hpp"

static RelaxedAtomic<unsigned> gActiveRequests;
static std::shared_ptr<hpccMetrics::GaugeMetric> pActiveRequests;

static auto pActiveRequests = hpccMetrics::registerCustomMetric("esp.requests.active", "Number of active requests",
hpccMetrics::METRICS_GAUGE, gActiveRequests, SMeasureCount);
MODULE_INIT(INIT_PRIORITY_STANDARD)
{
pActiveRequests = hpccMetrics::registerGaugeMetric("esp.requests.active", "Number of active requests", SMeasureCount);
return true;
}

typedef IXslProcessor * (*getXslProcessor_func)();

unsigned ActiveRequests::getCount()
{
return gActiveRequests;
return pActiveRequests->queryValue();
}

ActiveRequests::ActiveRequests()
{
gActiveRequests++;
pActiveRequests->adjust(1);
}

ActiveRequests::~ActiveRequests()
{
gActiveRequests--;
pActiveRequests->adjust(-1);
}

CEspApplicationPort::CEspApplicationPort(bool viewcfg, CEspProtocol* prot) : viewConfig(viewcfg), rootAuth(false), navResize(false), navScroll(false), navWidth(165), protocol(prot)
Expand Down
7 changes: 6 additions & 1 deletion esp/platform/txsummary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@

#define MATCH_ENTRY [&](const EntryValue& e) {return strieq(e.get()->name, pathPart);}

static auto pRequestCount = hpccMetrics::registerCounterMetric("esp.requests.received", "Number of requests received", SMeasureCount);
static std::shared_ptr<hpccMetrics::CounterMetric> pRequestCount;
MODULE_INIT(INIT_PRIORITY_STANDARD)
{
pRequestCount = hpccMetrics::registerCounterMetric("esp.requests.received", "Number of requests received", SMeasureCount);
return true;
}

inline bool validate(const char* k)
{
Expand Down

0 comments on commit a5b71de

Please sign in to comment.