blob: 2f9c2bd827ba4d934a99b328fb1ac3120a0b2732 [file]
// Copyright 2023 Google LLC
//
// 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
//
// https://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.
#ifndef MPACT_SIM_GENERIC_COUNTERS_H_
#define MPACT_SIM_GENERIC_COUNTERS_H_
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/types/variant.h"
#include "mpact/sim/generic/counters_base.h"
#include "mpact/sim/generic/variant_helper.h"
#include "mpact/sim/proto/component_data.pb.h"
// This file defines the user facing classes for the simulator statistics
// instrumentation infrastructure. Base classes are located in counters_base.h.
// It is intended that instances of the counter classes are used to accumulate
// results locally in software components, whereas the CounterBaseInterface
// class (defined in counters_base.h) can be used to access the results in a
// type independent way, suitable for access through a registry to collect and
// process the results at the end of a simulation.
//
// While CounterBaseInterface is type agnostic, the derived classes are
// templated on the input and output data type. The output data type of a
// counter is restricted to the set of types in the absl::variant type aliased
// as CounterValue.
//
// This file defines three classes. The first is CounterValueOutputBase<T>
// which is templated, but only valid for the types in the CounterValue
// variant (see below and in counters_base.h). This class inherits from
// CounterBaseInterface and implements some of the common functionality.
//
// The second class is SimpleCounter<T>, where T is in the variant but also an
// arithmetic type (in the case a non-arithmetic type gets added to the variant
// in the future). It supports setting, incrementing and decrementing of the
// counter value.
//
// The third type is FunctionCounter<In, Out> where In and Out are the types in
// the SetValue interface (no Increment or Decrement methods are supported) and
// the CounterValueOutputBase<> class respectively. The FunctionCounter applies
// a user supplied function or functor to the input value to optionally produce
// an output value. The function/functor is of type
// std::function<bool(const In&, Out *)>, and returns true if the output value
// was updated, false otherwise.
namespace mpact {
namespace sim {
namespace generic {
// The generic value of a counter is an absl::variant containing a set of
// anticipated frequently used types that aliased to CounterValue. It is
// defined in counters_base.h as:
// using CounterValue = absl::variant<uint64, int64, double>;
//
// Templated output base class. The template argument must be one of the types
// in the CounterValue variant, if not, the static_assert will generate an
// error. The CounterValueOutputBase<> class implements all the pure virtual
// methods from CounterBaseInterface except for ToString(). The implementations
// are declared final to allow the compiler to de-virtualize calls to these
// methods made in instances of derived classes.
template <typename T>
class CounterValueOutputBase : public CounterBaseInterface {
// Static check to signal error when an illegal type is used.
static_assert(mpact::sim::generic::IsMemberOfVariant<CounterValue, T>::value,
"Template argument type is not in CounterValue variant");
public:
// Constructors and destructor.
// The default constructor does not initialize the counter. A separate call
// to Initialize(..) is required before it can be added to a component.
CounterValueOutputBase() : is_enabled_(false), is_initialized_(false) {}
// Constructs and initializes the counter.
CounterValueOutputBase(std::string name, const T initial)
: name_(std::move(name)),
about_(),
is_enabled_(true),
is_initialized_(true),
value_(std::move(initial)) {}
~CounterValueOutputBase() override = default;
// Method to add an object that implements the CounterInputSetInterface<T>
// to be added a a listener, which means that its SetValue() method will be
// called whenever value of the counter is updated. The caller retains
// ownership of the listener.
void AddListener(CounterValueSetInterface<T> *listener) {
listeners_.push_back(listener);
}
// Implementation of the pure virtual method from CounterBaseInterface to
// return the value as a variant. Notice, this method is declared as final to
// enable the compiler to "de-virtualize" the method and improve its
// performance when called in certain circumstances.
CounterValue GetCounterValue() const final { return CounterValue(value_); }
// Typed getter for the counter value.
T GetValue() const { return value_; }
// Method for exporting the counter to proto message. Calls ExportValue
// for the type specific export, as each different type needs to set
// a different field in the proto message.
absl::Status Export(
mpact::sim::proto::ComponentValueEntry *entry) const override {
if (entry == nullptr) return absl::InvalidArgumentError("Entry is null");
entry->set_name(GetName());
if (!GetAbout().empty()) {
entry->set_about(GetAbout());
}
ExportValue(entry);
return absl::OkStatus();
}
// Must be called before being added to a component if the counter was
// created using the default constructor.
void Initialize(std::string name, const T initial) {
name_ = std::move(name);
value_ = std::move(initial);
is_initialized_ = true;
is_enabled_ = true;
}
// Implementation of pure virtual methods inherited from CounterBaseInterface
// that act as accessors. Keeping the names in PascalCase since they implement
// an inherited interface.
std::string GetName() const final { return name_; }
std::string GetAbout() const final { return about_; }
void SetAbout(std::string about) final { about_ = std::move(about); }
bool IsEnabled() const final { return is_enabled_; }
void SetIsEnabled(bool is_enabled) final { is_enabled_ = is_enabled; }
bool IsInitialized() const final { return is_initialized_; }
protected:
// The setter for the counter value used by derived classes. Calls the set
// of registered listener objects with the new value.
void UpdateValue(T value) {
value_ = std::move(value);
for (auto *listener : listeners_) {
listener->SetValue(value_);
}
}
private:
// This method exports the typed value of the counter to the appropriate
// field in the proto message. Note, this method is defined at the bottom of
// this file.
void ExportValue(mpact::sim::proto::ComponentValueEntry *entry) const;
// Objects pointed to are owned elsewhere. Their lifetimes must exceed the
// lifetime of the counter, or at least beyond the last call to UpdateValue.
std::vector<CounterValueSetInterface<T> *> listeners_;
std::string name_;
std::string about_;
bool is_enabled_;
bool is_initialized_;
T value_;
};
// A simple arithmetic counter class that supports increment and decrement. Only
// enabled for arithmetic types. This class has multiple inheritance from both
// the input interface and the output base class. The input interface is pure
// virtual, so there is no multiple implementation inheritance. There is also
// no possibility of a diamond pattern. The input interface inheritance is also
// necessary should instances of this class be used as a listener to another
// counter.
template <typename T>
class SimpleCounter : public CounterValueOutputBase<T>,
public CounterValueIncrementInterface<T> {
// Static checks on the template argument type. The IsMemberOfVariant<T> check
// is repeated to simplify error messages. If it is omitted and T is not in
// the counter type, additional error messages may be generated for each of
// the methods inherited from CounterBaseInterface and
// CounterValueOutputInterface.
static_assert(mpact::sim::generic::IsMemberOfVariant<CounterValue, T>::value,
"Template argument type T is not in CounterValue variant.");
static_assert(std::is_arithmetic<T>::value,
"Template argument type T must be arithmetic.");
public:
// Since this class derives from templated classes, calls to the base class
// methods must be qualified or use this->. Electing to do the former with the
// following using declarations.
using CounterValueOutputBase<T>::IsEnabled;
using CounterValueOutputBase<T>::UpdateValue;
using CounterValueOutputBase<T>::GetValue;
// Constructor and destructor.
SimpleCounter() : CounterValueOutputBase<T>() {}
SimpleCounter(std::string name, const T &initial)
: CounterValueOutputBase<T>(std::move(name), initial) {}
explicit SimpleCounter(std::string name)
: SimpleCounter(std::move(name), T()) {}
SimpleCounter &operator=(const SimpleCounter &) = delete;
~SimpleCounter() override = default;
// Implementing the methods from the CounterValueIncrementInterface<T>. Note
// that the methods are declared final to enable de-virtualization
// optimizations in the compiler.
void Increment(const T &val) final {
if (IsEnabled()) UpdateValue(GetValue() + val);
}
void Decrement(const T &val) final {
if (IsEnabled()) UpdateValue(GetValue() - val);
}
void SetValue(const T &val) final {
if (IsEnabled()) UpdateValue(val);
}
// From CounterValueOutputInterface<T>. This method is not declared final
// to make it possible to customize formatting of the string in derived
// classes.
std::string ToString() const override { return absl::StrCat(GetValue()); }
};
// A complex counter that calls a function on its input value. If the
// function returns true, then the functions' computed output value is used to
// update the counter. The function can be any function object that can be cast
// to the ProcessingFunction type, including free functions, class methods,
// or call operators on class instances. This allows the function object to
// maintain state which enables more advanced computations, such as max/min
// etc. The fact that the counter is only updated when the function returns
// true allows for input samples to be filtered. This class has multiple
// inheritance from both the input interface and the output base class. The
// input interface is pure virtual, so there is no multiple implementation
// inheritance. There is also no possibility of a diamond pattern. The input
// interface inheritance is also necessary should instances of this class be
// used as a listener to another counter.
template <typename In, typename Out = In>
class FunctionCounter : public CounterValueOutputBase<Out>,
public CounterValueSetInterface<In> {
// Static checks on the template argument type. The IsMemberOfVariant<T> check
// is repeated to simplify error messages. If it is omitted and T is not in
// the counter type, additional error messages may be generated for each of
// the methods inherited from CounterBaseInterface and CounterValueOutputBase.
static_assert(
mpact::sim::generic::IsMemberOfVariant<CounterValue, Out>::value,
"Template argument type Out is not in CounterValue variant.");
public:
using ProcessingFunction = std::function<bool(const In &, Out *)>;
// Since this class derives from templated classes, calls to the base class
// methods must be qualified or use this->. Electing to do the former with the
// following using declarations.
using CounterValueOutputBase<Out>::IsEnabled;
using CounterValueOutputBase<Out>::UpdateValue;
using CounterValueOutputBase<Out>::GetValue;
// Constructors and destructor. The name is mandatory for the constructor. The
// initial value is optional and defaults to the default constructed value.
// The processing function may be given in the constructor or assigned later.
// If not passed in to the constructor it defaults to a function that always
// returns false and thus never updates the counter value.
template <typename F>
FunctionCounter(std::string name, const Out &initial, F processing_function)
: CounterValueOutputBase<Out>(std::move(name), initial),
processing_function_(
std::move(ProcessingFunction(processing_function))) {}
template <typename F>
FunctionCounter(std::string name, F processing_function)
: FunctionCounter(std::move(name), Out(),
std::move(processing_function)) {}
FunctionCounter(std::string name, const Out &initial)
: FunctionCounter(std::move(name), initial,
[](const In &, Out *) -> bool { return false; }) {}
explicit FunctionCounter(std::string name)
: FunctionCounter(std::move(name), Out()) {}
FunctionCounter &operator=(const FunctionCounter &) = delete;
~FunctionCounter() override = default;
// Set the value processing function.
template <typename F>
void SetFunction(F fcn) {
processing_function_ = std::move(ProcessingFunction(fcn));
}
// The following method is defined final to enable devirtualization
// optimization in the compiler.
// Process the input value and update the counter if indicated.
void SetValue(const In &in_value) final {
if (IsEnabled()) {
Out out_value;
if (processing_function_(in_value, &out_value)) UpdateValue(out_value);
}
}
// From CounterValueOutputBase<T>. This method is not declared final
// to make it possible to customize formatting of the string in derived
// classes.
std::string ToString() const override { return absl::StrCat(GetValue()); }
private:
ProcessingFunction processing_function_;
};
// Definition of ExportValue methods, one for each type in the CounterValue
// variant.
// NOTE: add a specialization for every new type added to the variant.
template <>
inline void CounterValueOutputBase<uint64_t>::ExportValue(
mpact::sim::proto::ComponentValueEntry *entry) const {
entry->set_uint64_value(GetValue());
}
template <>
inline void CounterValueOutputBase<int64_t>::ExportValue(
mpact::sim::proto::ComponentValueEntry *entry) const {
entry->set_sint64_value(GetValue());
}
template <>
inline void CounterValueOutputBase<double>::ExportValue(
mpact::sim::proto::ComponentValueEntry *entry) const {
entry->set_double_value(GetValue());
}
} // namespace generic
} // namespace sim
} // namespace mpact
#endif // MPACT_SIM_GENERIC_COUNTERS_H_