diff --git a/example/0-single/README.md b/example/0-single/README.md index 7d7b529..f5ef5ee 100644 --- a/example/0-single/README.md +++ b/example/0-single/README.md @@ -17,7 +17,7 @@ Before starting the node, update the GENESIS_TIME in the genesis config file to ```bash future_time=$(( $(date +%s) + 20 )) -sed -i "s/GENESIS_TIME: .*/GENESIS_TIME: $future_time/" example/0-single/genesis/config.yaml +sed -i '' "s/GENESIS_TIME: .*/GENESIS_TIME: $future_time/" example/0-single/genesis/config.yaml ``` Example CLI command: diff --git a/example/1-network/README.md b/example/1-network/README.md index 15c3300..78eebef 100644 --- a/example/1-network/README.md +++ b/example/1-network/README.md @@ -17,7 +17,7 @@ Before starting nodes, set `GENESIS_TIME` in `example/1-network/genesis/config.y ```bash future_time=$(( $(date +%s) + 20 )) -sed -i "s/GENESIS_TIME: .*/GENESIS_TIME: $future_time/" example/1-network/genesis/config.yaml +sed -i '' "s/GENESIS_TIME: .*/GENESIS_TIME: $future_time/" example/1-network/genesis/config.yaml ``` ## Start the 4 validators @@ -33,7 +33,7 @@ Node 0: --genesis example/1-network/genesis/config.yaml \ --validator-registry-path example/1-network/genesis/validators.yaml \ --node-id node_0 \ - --node-key cb920fbda3b96e18f03e22825f4a5a61343ec43c7be1c8c4a717fffee2f4c4ce \ + --node-key 0000000000000000010000000000000002000000000000000300000000000000 \ --listen-addr /ip4/0.0.0.0/udp/9000/quic-v1 \ --prometheus-port 9100 ``` @@ -47,7 +47,7 @@ Node 1: --genesis example/1-network/genesis/config.yaml \ --validator-registry-path example/1-network/genesis/validators.yaml \ --node-id node_1 \ - --node-key a87e7d23bb1de4613b67002b700bce41e031f4ab1529a3436bd73c893ea039b3 \ + --node-key 0100000000000000020000000000000003000000000000000400000000000000 \ --listen-addr /ip4/0.0.0.0/udp/9001/quic-v1 \ --prometheus-port 9101 ``` @@ -61,7 +61,7 @@ Node 2: --genesis example/1-network/genesis/config.yaml \ --validator-registry-path example/1-network/genesis/validators.yaml \ --node-id node_2 \ - --node-key f2f53f6acf312c5e92c2a611bbca7a1932b4db0b9e0c43bec413badca9b76760 \ + --node-key 0200000000000000030000000000000004000000000000000500000000000000 \ --listen-addr /ip4/0.0.0.0/udp/9002/quic-v1 \ --prometheus-port 9102 ``` @@ -75,7 +75,7 @@ Node 3: --genesis example/1-network/genesis/config.yaml \ --validator-registry-path example/1-network/genesis/validators.yaml \ --node-id node_3 \ - --node-key fa5ddbec80f964d17d28221c2c5bac0f4a3f9cfcf4b86674e605f459e195a1c4 \ + --node-key 0300000000000000040000000000000005000000000000000600000000000000 \ --listen-addr /ip4/0.0.0.0/udp/9003/quic-v1 \ --prometheus-port 9103 ``` diff --git a/example/1-network/genesis/nodes.yaml b/example/1-network/genesis/nodes.yaml index 1439119..807a631 100644 --- a/example/1-network/genesis/nodes.yaml +++ b/example/1-network/genesis/nodes.yaml @@ -1,5 +1,4 @@ -- enr:-IW4QIh9cSo0CPOcsTq5T6SAYr0HrGFMYekZjrgC7ZTgdsMhBKjIQgzCfgqsxulCO4O1TXyjRLZ3BYc4GgqVRvl3d1sBgmlkgnY0gmlwhAoAAAqEcXVpY4IjKIlzZWNwMjU2azGhAmVoFLEuozqtJ7uxbf6RlL8ow-UlDKSYLzxKcbpZ13Zg -- enr:-IW4QG_-KOB94fI18bEqxW8B_lKgG3cGVdvKIIKdZJBLftoaZE5y3Vg4PFQQIPmIRpeD1QawVKrd_6HDd1D2K7WLsLQBgmlkgnY0gmlwhAoAAAuEcXVpY4IjKYlzZWNwMjU2azGhA8PFJzjZs3Nmzn34yVzbnN5Mo5RhzwiWDxLnmoW1U7AV -- enr:-IW4QDEHjhkVEcEX5GS4qAjAHbiqCevwjwFd6ce1SYxLEIgYXDmozUjm8ao4Nl1YoFVhNBs1cn8zW2kwb6yaJpgVDLkBgmlkgnY0gmlwhAoAAAyEcXVpY4IjKolzZWNwMjU2azGhAsQeX5os8a2pG4v2cGuMMXZYY2B-yzYLcZM3yEHa3_kW -- enr:-IW4QEgn7uYKIhbom8qWeFGWBTOh_WZGKjLCfoJay5PHND9yAG359yxsK84DBxfOWm86U5zvVF_UbCO5n1Uz6P2tG28BgmlkgnY0gmlwhAoAAA2EcXVpY4IjK4lzZWNwMjU2azGhAhXhXn0CrP2llJ7PNcWcimcUJ31GeGVMfk0MF2lnH4Ri - +- enr:-IW4QF83vMklTZR8wrXxsNiA14P7xNmk79zsy1OsqblVCiG5ZEzT2TMVbtkARM5q0QfHbYU2jaVfk5xCNXEv01LkZ1QBgmlkgnY0gmlwhH8AAAGEcXVpY4InEIlzZWNwMjU2azGhAyUAddLEC14kKi9ziAKt47wyvzX_jvJqFbbD-HkxKQak +- enr:-IW4QBj8XATzGHrMHeYuQG1PLXkH7CujKi0knmbeo8AAGNBqVXM24zrEElpLqYOaPdMiZzjeZ-KKOrZfBy24rvh9QaIBgmlkgnY0gmlwhH8AAAGEcXVpY4InEYlzZWNwMjU2azGhA3x5MQ79P8UyJ8JrEcHbbJp-CbX4AgyV6cZHlYb6uJrl +- enr:-IW4QPpY9L4EHFiGk3FXbGIe_pKSaORsxN-Kij2avIpg4V_jdHCnHYhEc_jyi6CLpwRnbHJvIxduC7AUU3d10P1ZoKoBgmlkgnY0gmlwhH8AAAGEcXVpY4InEolzZWNwMjU2azGhA9GSOWDRYKpxNbaRn_Nctl2E3GBReM6XsPumdACuwJvR +- enr:-IW4QANPPIHjaXKMePVjOIUvqtOUg89t6pWuey9hBcQhywX_dg8QekjUpigHkUvvBO8O7j3-4iRradZCf5puJ4hVDuMBgmlkgnY0gmlwhH8AAAGEcXVpY4InE4lzZWNwMjU2azGhAhbcwnDGuwqTSa-lfSuqVS2F6m3SLNP24pLUPHv6QT8E diff --git a/src/app/application_metrics.def b/src/app/application_metrics.def index 94f5868..fb01c6a 100644 --- a/src/app/application_metrics.def +++ b/src/app/application_metrics.def @@ -5,7 +5,6 @@ */ /** - * @file application_metrics.def * @brief Application-level metrics definitions * * Format: @@ -13,30 +12,37 @@ * "prometheus_metric_name", * "help_text") * - * METRIC_GAUGE(field_name, - * "prometheus_metric_name", - * "help_text", - * "label1", - * "label2", - * ...) + * METRIC_GAUGE_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {"label1", "label2", ...}) + * + * METRIC_COUNTER(field_name, + * "prometheus_metric_name", + * "help_text") + * + * METRIC_COUNTER_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {"label1", "label2", ...}) * - * Parameters: - * - field_name: Required - C++ member variable name - * - prometheus_metric_name: Required - Prometheus metric name - * - help_text: Required - Metric description - * - labels: Optional - Any number of label names (must be string literals) + * METRIC_HISTOGRAM(field_name, + * "prometheus_metric_name", + * "help_text", + * {bucket1, bucket2, ...}) * - * Notes: - * - All labels MUST be strings (enforced by static_assert at compile time) - * - If you pass a non-string type, you'll get a compile error - * - Usage: metrics_->metric_name({{"label1", value1}, {"label2", value2}})->set(...) + * METRIC_HISTOGRAM_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {bucket1, bucket2, ...}, + * {"label1", "label2", ...}) */ -METRIC_GAUGE_LABELS(app_build_info, - "lean_build_info", - "A metric with a constant '1' value labeled by name, version", - "name", - "version") +METRIC_GAUGE_LABELS( + app_build_info, + "lean_build_info", + "A metric with a constant '1' value labeled by name, version", + ("name", "version")) METRIC_GAUGE(app_process_start_time, "lean_process_start_time_seconds", diff --git a/src/blockchain/fork_choice.cpp b/src/blockchain/fork_choice.cpp index 808021c..8473eeb 100644 --- a/src/blockchain/fork_choice.cpp +++ b/src/blockchain/fork_choice.cpp @@ -183,6 +183,7 @@ namespace lean { "Validating attestation for target {}, source {}", signed_vote.data.target, signed_vote.data.source); + auto timer = metrics_->fc_attestation_validation_time_seconds()->timer(); auto &vote = signed_vote.data; // Validate vote targets exist in store @@ -223,7 +224,12 @@ namespace lean { outcome::result ForkChoiceStore::processAttestation( const SignedVote &signed_vote, bool is_from_block) { // Validate attestation structure and constraints - BOOST_OUTCOME_TRY(validateAttestation(signed_vote)); + if (auto res = validateAttestation(signed_vote); res.has_value()) { + metrics_->fc_attestations_valid_total()->inc(); + } else { + metrics_->fc_attestations_invalid_total()->inc(); + return res; + } auto &validator_id = signed_vote.validator_id; auto &vote = signed_vote.data; @@ -261,6 +267,7 @@ namespace lean { } outcome::result ForkChoiceStore::onBlock(Block block) { + auto timer = metrics_->fc_block_processing_time_seconds()->timer(); block.setHash(); auto block_hash = block.hash(); // If the block is already known, ignore it @@ -464,7 +471,8 @@ namespace lean { qtils::SharedRef logging_system, qtils::SharedRef metrics, qtils::SharedRef validator_registry) - : validator_registry_(validator_registry), + : stf_(metrics), + validator_registry_(validator_registry), logger_( logging_system->getLogger("ForkChoiceStore", "fork_choice_store")), metrics_(std::move(metrics)) { @@ -507,7 +515,8 @@ namespace lean { Votes latest_new_votes, ValidatorIndex validator_index, qtils::SharedRef validator_registry) - : time_(now_sec / SECONDS_PER_INTERVAL), + : stf_(metrics), + time_(now_sec / SECONDS_PER_INTERVAL), logger_( logging_system->getLogger("ForkChoiceStore", "fork_choice_store")), config_(config), diff --git a/src/blockchain/fork_choice_metrics.def b/src/blockchain/fork_choice_metrics.def index 8d696e5..b46555d 100644 --- a/src/blockchain/fork_choice_metrics.def +++ b/src/blockchain/fork_choice_metrics.def @@ -5,7 +5,6 @@ */ /** - * @file fork_choice_metrics.def * @brief ForkChoiceStore metrics definitions * * Format: @@ -13,21 +12,48 @@ * "prometheus_metric_name", * "help_text") * - * METRIC_GAUGE(field_name, - * "prometheus_metric_name", - * "help_text", - * "label1", - * "label2", - * ...) + * METRIC_GAUGE_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {"label1", "label2", ...}) + * + * METRIC_COUNTER(field_name, + * "prometheus_metric_name", + * "help_text") * - * Parameters: - * - field_name: Required - C++ member variable name - * - prometheus_metric_name: Required - Prometheus metric name - * - help_text: Required - Metric description - * - labels: Optional - Any number of label names + * METRIC_COUNTER_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {"label1", "label2", ...}) + * + * METRIC_HISTOGRAM(field_name, + * "prometheus_metric_name", + * "help_text", + * {bucket1, bucket2, ...}) + * + * METRIC_HISTOGRAM_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {bucket1, bucket2, ...}, + * {"label1", "label2", ...}) */ -METRIC_GAUGE(fc_head_slot, - "lean_head_slot", - "Latest slot of the beacon chain") +METRIC_GAUGE(fc_head_slot, "lean_head_slot", "Latest slot of the beacon chain") + +// Attestation validation +METRIC_COUNTER(fc_attestations_valid_total, + "lean_attestations_valid_total", + "Total number of valid attestations") +METRIC_COUNTER(fc_attestations_invalid_total, + "lean_attestations_invalid_total", + "Total number of invalid attestations") +METRIC_HISTOGRAM(fc_attestation_validation_time_seconds, + "lean_attestation_validation_time_seconds", + "Time taken to validate attestation", + (0.005, 0.01, 0.025, 0.05, 0.1, 1)) +// Block processing +METRIC_HISTOGRAM(fc_block_processing_time_seconds, + "lean_fork_choice_block_processing_time_seconds", + "Time taken to process block in fork choice", + (0.005, 0.01, 0.025, 0.05, 0.1, 1)) diff --git a/src/blockchain/impl/validator_metrics.def b/src/blockchain/impl/validator_metrics.def new file mode 100644 index 0000000..7942691 --- /dev/null +++ b/src/blockchain/impl/validator_metrics.def @@ -0,0 +1,43 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief Validator metrics definitions + * + * Format: + * METRIC_GAUGE(field_name, + * "prometheus_metric_name", + * "help_text") + * + * METRIC_GAUGE_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {"label1", "label2", ...}) + * + * METRIC_COUNTER(field_name, + * "prometheus_metric_name", + * "help_text") + * + * METRIC_COUNTER_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {"label1", "label2", ...}) + * + * METRIC_HISTOGRAM(field_name, + * "prometheus_metric_name", + * "help_text", + * {bucket1, bucket2, ...}) + * + * METRIC_HISTOGRAM_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {bucket1, bucket2, ...}, + * {"label1", "label2", ...}) + */ + +METRIC_GAUGE(val_validators_count, + "lean_validators_count", + "Number of connected validators") diff --git a/src/blockchain/impl/validator_registry_impl.cpp b/src/blockchain/impl/validator_registry_impl.cpp index 63aa7a7..cbd8117 100644 --- a/src/blockchain/impl/validator_registry_impl.cpp +++ b/src/blockchain/impl/validator_registry_impl.cpp @@ -14,6 +14,7 @@ #include #include "app/configuration.hpp" +#include "metrics/metrics.hpp" namespace lean { enum class ValidatorRegistryError { @@ -42,16 +43,20 @@ namespace lean { ValidatorRegistryImpl::ValidatorRegistryImpl( qtils::SharedRef logging_system, + qtils::SharedRef metrics, const app::Configuration &config) : ValidatorRegistryImpl{std::move(logging_system), + std::move(metrics), YAML::LoadFile(config.validatorRegistryPath()), config.nodeId()} {} ValidatorRegistryImpl::ValidatorRegistryImpl( qtils::SharedRef logging_system, + qtils::SharedRef metrics, std::string yaml, std::string current_node_id) : ValidatorRegistryImpl{std::move(logging_system), + std::move(metrics), YAML::Load(yaml), std::move(current_node_id)} {} @@ -83,10 +88,12 @@ namespace lean { ValidatorRegistryImpl::ValidatorRegistryImpl( qtils::SharedRef logging_system, + qtils::SharedRef metrics, YAML::Node root, std::string current_node_id) : logger_{logging_system->getLogger("ValidatorRegistry", "validator_registry")}, + metrics_{std::move(metrics)}, current_node_id_{std::move(current_node_id)} { if (not root.IsDefined() or root.IsNull()) { SL_WARN(logger_, "Validator registry YAML is empty"); @@ -161,5 +168,7 @@ namespace lean { "Validator indices for node '{}' not found in registry YAML", current_node_id_); } + + metrics_->val_validators_count()->set(current_validator_indices_.size()); } } // namespace lean diff --git a/src/blockchain/impl/validator_registry_impl.hpp b/src/blockchain/impl/validator_registry_impl.hpp index 71b9a2a..87e5b1a 100644 --- a/src/blockchain/impl/validator_registry_impl.hpp +++ b/src/blockchain/impl/validator_registry_impl.hpp @@ -10,6 +10,7 @@ #include #include +#include #include "blockchain/validator_registry.hpp" @@ -21,6 +22,10 @@ namespace lean::app { class Configuration; } // namespace lean::app +namespace lean::metrics { + class Metrics; +} // namespace lean::metrics + namespace lean { /** * ValidatorRegistry provides access to mapping between validator indices and @@ -36,10 +41,13 @@ namespace lean { class ValidatorRegistryImpl : public ValidatorRegistry { public: ValidatorRegistryImpl(qtils::SharedRef logging_system, + qtils::SharedRef metrics, const app::Configuration &config); BOOST_DI_INJECT_TRAITS(qtils::SharedRef, + qtils::SharedRef, const lean::app::Configuration &); ValidatorRegistryImpl(qtils::SharedRef logging_system, + qtils::SharedRef metrics, std::string yaml, std::string current_node_id); @@ -55,10 +63,12 @@ namespace lean { private: ValidatorRegistryImpl(qtils::SharedRef logging_system, + qtils::SharedRef metrics, YAML::Node root, std::string current_node_id); log::Logger logger_; + qtils::SharedRef metrics_; std::string current_node_id_; std::unordered_map index_to_node_; std::unordered_map> diff --git a/src/blockchain/state_transition_function.cpp b/src/blockchain/state_transition_function.cpp index 80856aa..dbd6a9e 100644 --- a/src/blockchain/state_transition_function.cpp +++ b/src/blockchain/state_transition_function.cpp @@ -10,12 +10,16 @@ #include #include "blockchain/is_justifiable_slot.hpp" +#include "metrics/metrics.hpp" #include "types/signed_block.hpp" #include "types/state.hpp" namespace lean { constexpr BlockHash kZeroHash; + STF::STF(qtils::SharedRef metrics) + : metrics_(std::move(metrics)) {} + inline bool getBit(const std::vector &bits, size_t i) { return i < bits.size() and bits.at(i); } @@ -106,6 +110,7 @@ namespace lean { outcome::result STF::stateTransition(const Block &block, const State &parent_state, bool check_state_root) const { + auto timer = metrics_->stf_state_transition_time_seconds()->timer(); auto state = parent_state; // Process slots (including those with no blocks) since block OUTCOME_TRY(processSlots(state, block.slot)); @@ -122,12 +127,14 @@ namespace lean { } outcome::result STF::processSlots(State &state, Slot slot) const { + auto timer = metrics_->stf_slots_processing_time_seconds()->timer(); if (state.slot >= slot) { return Error::INVALID_SLOT; } while (state.slot < slot) { processSlot(state); ++state.slot; + metrics_->stf_slots_processed_total()->inc(); } return outcome::success(); } @@ -141,6 +148,7 @@ namespace lean { outcome::result STF::processBlock(State &state, const Block &block) const { + auto timer = metrics_->stf_block_processing_time_seconds()->timer(); OUTCOME_TRY(processBlockHeader(state, block)); OUTCOME_TRY(processOperations(state, block.body)); return outcome::success(); @@ -210,6 +218,7 @@ namespace lean { State &state, const std::vector &attestations) const { // get justifications, justified slots and historical block hashes are // already upto date as per the processing in process_block_header + auto timer = metrics_->stf_attestations_processing_time_seconds()->timer(); auto justifications = getJustifications(state); // From 3sf-mini/consensus.py - apply votes @@ -263,6 +272,7 @@ namespace lean { // scenarios if (3 * count >= 2 * state.config.num_validators) { state.latest_justified = vote.target; + metrics_->stf_latest_justified_slot()->set(state.latest_justified.slot); setBit(state.justified_slots.data(), vote.target.slot); justifications.erase(vote.target.root); @@ -278,8 +288,11 @@ namespace lean { } if (not any) { state.latest_finalized = vote.source; + metrics_->stf_latest_finalized_slot()->set( + state.latest_finalized.slot); } } + metrics_->stf_attestations_processed_total()->inc(); } // flatten and set updated justifications back to the state diff --git a/src/blockchain/state_transition_function.hpp b/src/blockchain/state_transition_function.hpp index 8111467..66011c2 100644 --- a/src/blockchain/state_transition_function.hpp +++ b/src/blockchain/state_transition_function.hpp @@ -8,6 +8,7 @@ #include #include +#include #include "app/impl/chain_spec_impl.hpp" #include "types/block.hpp" @@ -20,7 +21,13 @@ namespace lean { struct Config; struct SignedVote; struct State; +} // namespace lean + +namespace lean::metrics { + class Metrics; +} // namespace lean::metrics +namespace lean { class STF { public: enum class Error { @@ -53,6 +60,8 @@ namespace lean { abort(); } + explicit STF(qtils::SharedRef metrics); + static AnchorState generateGenesisState(const Config &config); static AnchorBlock genesisBlock(const State &state); @@ -76,5 +85,8 @@ namespace lean { outcome::result processAttestations( State &state, const std::vector &attestations) const; bool validateProposerIndex(const State &state, const Block &block) const; + + private: + qtils::SharedRef metrics_; }; } // namespace lean diff --git a/src/blockchain/state_transition_metrics.def b/src/blockchain/state_transition_metrics.def new file mode 100644 index 0000000..df6c113 --- /dev/null +++ b/src/blockchain/state_transition_metrics.def @@ -0,0 +1,77 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief State Transition Function metrics definitions + * + * Format: + * METRIC_GAUGE(field_name, + * "prometheus_metric_name", + * "help_text") + * + * METRIC_GAUGE_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {"label1", "label2", ...}) + * + * METRIC_COUNTER(field_name, + * "prometheus_metric_name", + * "help_text") + * + * METRIC_COUNTER_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {"label1", "label2", ...}) + * + * METRIC_HISTOGRAM(field_name, + * "prometheus_metric_name", + * "help_text", + * {bucket1, bucket2, ...}) + * + * METRIC_HISTOGRAM_LABELS(field_name, + * "prometheus_metric_name", + * "help_text", + * {bucket1, bucket2, ...}, + * {"label1", "label2", ...}) + */ + +// Justification and finalization +METRIC_GAUGE(stf_latest_justified_slot, + "lean_latest_justified_slot", + "Latest justified slot") +METRIC_GAUGE(stf_latest_finalized_slot, + "lean_latest_finalized_slot", + "Latest finalized slot") + +// State transition timing +METRIC_HISTOGRAM(stf_state_transition_time_seconds, + "lean_state_transition_time_seconds", + "Time to process state transition", + (0.005, 0.01, 0.025, 0.05, 0.1, 1)) + +// Slot processing +METRIC_COUNTER(stf_slots_processed_total, + "lean_state_transition_slots_processed_total", + "Total number of processed slots") +METRIC_HISTOGRAM(stf_slots_processing_time_seconds, + "lean_state_transition_slots_processing_time_seconds", + "Time taken to process slots", + (0.005, 0.01, 0.025, 0.05, 0.1, 1)) + +// Block processing +METRIC_HISTOGRAM(stf_block_processing_time_seconds, + "lean_state_transition_block_processing_time_seconds", + "Time taken to process block", + (0.005, 0.01, 0.025, 0.05, 0.1, 1)) + +// Attestation processing +METRIC_COUNTER(stf_attestations_processed_total, + "lean_state_transition_attestations_processed_total", + "Total number of processed attestations") +METRIC_HISTOGRAM(stf_attestations_processing_time_seconds, + "lean_state_transition_attestations_processing_time_seconds", + "Time taken to process attestations", + (0.005, 0.01, 0.025, 0.05, 0.1, 1)) diff --git a/src/metrics/all_metrics.def b/src/metrics/all_metrics.def index c902f4e..2e1f585 100644 --- a/src/metrics/all_metrics.def +++ b/src/metrics/all_metrics.def @@ -1,2 +1,4 @@ #include "app/application_metrics.def" #include "blockchain/fork_choice_metrics.def" +#include "blockchain/impl/validator_metrics.def" +#include "blockchain/state_transition_metrics.def" diff --git a/src/metrics/impl/metrics_impl.cpp b/src/metrics/impl/metrics_impl.cpp index 918c980..01c7cc8 100644 --- a/src/metrics/impl/metrics_impl.cpp +++ b/src/metrics/impl/metrics_impl.cpp @@ -10,31 +10,70 @@ namespace lean::metrics { +#define UNWRAP(...) __VA_ARGS__ + MetricsImpl::MetricsImpl(std::shared_ptr registry) : registry_{std::move(registry)} { #define METRIC_GAUGE(field, name, help) \ registry_->registerGaugeFamily(name, help); \ metric_##field##_ = registry_->registerGaugeMetric(name); -#define METRIC_GAUGE_LABELS(field, name, help, ...) \ +#define METRIC_GAUGE_LABELS(field, name, help, label_names) \ registry_->registerGaugeFamily(name, help); +#define METRIC_COUNTER(field, name, help) \ + registry_->registerCounterFamily(name, help); \ + metric_##field##_ = registry_->registerCounterMetric(name); +#define METRIC_COUNTER_LABELS(field, name, help, label_names) \ + registry_->registerCounterFamily(name, help); +#define METRIC_HISTOGRAM(field, name, help, buckets) \ + registry_->registerHistogramFamily(name, help); \ + metric_##field##_ = \ + registry_->registerHistogramMetric(name, {UNWRAP buckets}); +#define METRIC_HISTOGRAM_LABELS(field, name, help, buckets, label_names) \ + registry_->registerHistogramFamily(name, help); #include "metrics/all_metrics.def" #undef METRIC_GAUGE #undef METRIC_GAUGE_LABELS +#undef METRIC_COUNTER +#undef METRIC_COUNTER_LABELS +#undef METRIC_HISTOGRAM +#undef METRIC_HISTOGRAM_LABELS } #define METRIC_GAUGE(field, name, help) \ Gauge *MetricsImpl::field() { \ return metric_##field##_; \ } -#define METRIC_GAUGE_LABELS(field, name, help, ...) \ - Gauge *MetricsImpl::field(const Labels &labels) { \ - return registry_->registerGaugeMetric(name, labels); \ +#define METRIC_GAUGE_LABELS(field, name, help, label_names) \ + Gauge *MetricsImpl::field(const Labels &labels) { \ + return registry_->registerGaugeMetric(name, labels); \ + } +#define METRIC_COUNTER(field, name, help) \ + Counter *MetricsImpl::field() { \ + return metric_##field##_; \ + } +#define METRIC_COUNTER_LABELS(field, name, help, label_names) \ + Counter *MetricsImpl::field(const Labels &labels) { \ + return registry_->registerCounterMetric(name, labels); \ + } +#define METRIC_HISTOGRAM(field, name, help, buckets) \ + Histogram *MetricsImpl::field() { \ + return metric_##field##_; \ + } +#define METRIC_HISTOGRAM_LABELS(field, name, help, buckets, label_names) \ + Histogram *MetricsImpl::field(const Labels &labels) { \ + static const std::vector bucket_boundaries = {UNWRAP buckets}; \ + return registry_->registerHistogramMetric( \ + name, bucket_boundaries, labels); \ } #include "metrics/all_metrics.def" #undef METRIC_GAUGE #undef METRIC_GAUGE_LABELS +#undef METRIC_COUNTER +#undef METRIC_COUNTER_LABELS +#undef METRIC_HISTOGRAM +#undef METRIC_HISTOGRAM_LABELS } // namespace lean::metrics diff --git a/src/metrics/impl/metrics_impl.hpp b/src/metrics/impl/metrics_impl.hpp index 7527d12..d4518bb 100644 --- a/src/metrics/impl/metrics_impl.hpp +++ b/src/metrics/impl/metrics_impl.hpp @@ -37,11 +37,33 @@ namespace lean::metrics { #define METRIC_GAUGE_LABELS(field, name, help, ...) \ public: \ Gauge *field(const Labels &labels) override; +#define METRIC_COUNTER(field, name, help) \ + private: \ + Counter *metric_##field##_; \ + \ + public: \ + Counter *field() override; +#define METRIC_COUNTER_LABELS(field, name, help, ...) \ + public: \ + Counter *field(const Labels &labels) override; +#define METRIC_HISTOGRAM(field, name, help, ...) \ + private: \ + Histogram *metric_##field##_; \ + \ + public: \ + Histogram *field() override; +#define METRIC_HISTOGRAM_LABELS(field, name, help, ...) \ + public: \ + Histogram *field(const Labels &labels) override; #include "metrics/all_metrics.def" #undef METRIC_GAUGE #undef METRIC_GAUGE_LABELS +#undef METRIC_COUNTER +#undef METRIC_COUNTER_LABELS +#undef METRIC_HISTOGRAM +#undef METRIC_HISTOGRAM_LABELS }; } // namespace lean::metrics diff --git a/src/metrics/metrics.hpp b/src/metrics/metrics.hpp index d89bf3c..7dc90a3 100644 --- a/src/metrics/metrics.hpp +++ b/src/metrics/metrics.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include @@ -92,6 +93,8 @@ namespace lean::metrics { virtual void observe(const double value) = 0; }; + class HistogramTimer; + /** * @brief A histogram metric to represent aggregatable distributions of * events. @@ -107,6 +110,12 @@ namespace lean::metrics { * @brief Observe the given amount. */ virtual void observe(const double value) = 0; + + /** + * @brief Create a timer that automatically observes elapsed time. + * @return HistogramTimer that records duration when it goes out of scope + */ + HistogramTimer timer(); }; /** @@ -121,10 +130,125 @@ namespace lean::metrics { #define METRIC_GAUGE(field, name, help) virtual Gauge *field() = 0; #define METRIC_GAUGE_LABELS(field, name, help, ...) \ virtual Gauge *field(const Labels &labels) = 0; +#define METRIC_COUNTER(field, name, help) virtual Counter *field() = 0; +#define METRIC_COUNTER_LABELS(field, name, help, ...) \ + virtual Counter *field(const Labels &labels) = 0; +#define METRIC_HISTOGRAM(field, name, help, ...) virtual Histogram *field() = 0; +#define METRIC_HISTOGRAM_LABELS(field, name, help, ...) \ + virtual Histogram *field(const Labels &labels) = 0; #include "metrics/all_metrics.def" #undef METRIC_GAUGE #undef METRIC_GAUGE_LABELS +#undef METRIC_COUNTER +#undef METRIC_COUNTER_LABELS +#undef METRIC_HISTOGRAM +#undef METRIC_HISTOGRAM_LABELS }; + + /** + * @brief timer for histogram metrics + * + * Automatically records the elapsed time to a histogram when the timer + * goes out of scope or when manually stopped. + */ + class HistogramTimer { + public: + using Clock = std::chrono::steady_clock; + using TimePoint = std::chrono::time_point; + using Duration = std::chrono::duration; + + /** + * @brief Construct and start a timer for the given histogram + * @param histogram Histogram to record elapsed time to + */ + explicit HistogramTimer(Histogram *histogram) + : histogram_(histogram), start_time_(Clock::now()), running_(true) {} + + /** + * @brief Destructor - automatically stops timer and records elapsed time + */ + ~HistogramTimer() { + if (running_) { + const auto elapsed_time = stop(); + if (histogram_) { + histogram_->observe(elapsed_time); + } + } + } + + // Disable copying + HistogramTimer(const HistogramTimer &) = delete; + HistogramTimer &operator=(const HistogramTimer &) = delete; + + // Enable moving + HistogramTimer(HistogramTimer &&other) noexcept + : histogram_(other.histogram_), + start_time_(other.start_time_), + running_(other.running_) { + other.running_ = false; + } + + HistogramTimer &operator=(HistogramTimer &&other) noexcept { + if (this != &other) { + if (running_) { + const auto elapsed_time = stop(); + if (histogram_) { + histogram_->observe(elapsed_time); + } + } + histogram_ = other.histogram_; + start_time_ = other.start_time_; + running_ = other.running_; + other.running_ = false; + } + return *this; + } + + /** + * @brief Manually stop the timer + * @return Elapsed time in seconds + * + * After calling stop(), the timer is considered stopped and the destructor + * will not record the time to the histogram. + */ + double stop() { + if (!running_) { + return 0.0; + } + + const auto elapsed_time = elapsed(); + running_ = false; + return elapsed_time; + } + + /** + * @brief Check if the timer is currently running + * @return true if running, false if stopped + */ + bool isRunning() const { + return running_; + } + + /** + * @brief Get elapsed time without stopping the timer + * @return Elapsed time in seconds + */ + double elapsed() const { + const auto now = Clock::now(); + const Duration duration = now - start_time_; + return duration.count(); + } + + private: + Histogram *histogram_; + TimePoint start_time_; + bool running_; + }; + + inline HistogramTimer Histogram::timer() { + return HistogramTimer(this); + } + } // namespace lean::metrics diff --git a/tests/mock/blockchain/metrics_mock.hpp b/tests/mock/blockchain/metrics_mock.hpp index 348bb71..39dc86b 100644 --- a/tests/mock/blockchain/metrics_mock.hpp +++ b/tests/mock/blockchain/metrics_mock.hpp @@ -11,7 +11,6 @@ namespace lean::metrics { class GaugeMock : public Gauge { public: - // Gauge void inc() override {} void inc(double val) override {} void dec() override {} @@ -19,6 +18,17 @@ namespace lean::metrics { void set(double val) override {} }; + class CounterMock : public Counter { + public: + void inc() override {} + void inc(double val) override {} + }; + + class HistogramMock : public Histogram { + public: + void observe(const double value) override {} + }; + class MetricsMock : public Metrics { public: #define METRIC_GAUGE(field, name, help) \ @@ -29,13 +39,35 @@ namespace lean::metrics { Gauge *field(const Labels &labels) override { \ return &gauge_; \ } +#define METRIC_COUNTER(field, name, help) \ + Counter *field() override { \ + return &counter_; \ + } +#define METRIC_COUNTER_LABELS(field, name, help, ...) \ + Counter *field(const Labels &labels) override { \ + return &counter_; \ + } +#define METRIC_HISTOGRAM(field, name, help, ...) \ + Histogram *field() override { \ + return &histogram_; \ + } +#define METRIC_HISTOGRAM_LABELS(field, name, help, ...) \ + Histogram *field(const Labels &labels) override { \ + return &histogram_; \ + } #include "metrics/all_metrics.def" #undef METRIC_GAUGE #undef METRIC_GAUGE_LABELS +#undef METRIC_COUNTER +#undef METRIC_COUNTER_LABELS +#undef METRIC_HISTOGRAM +#undef METRIC_HISTOGRAM_LABELS private: GaugeMock gauge_; + CounterMock counter_; + HistogramMock histogram_; }; } // namespace lean::metrics diff --git a/tests/unit/blockchain/state_transition_function_test.cpp b/tests/unit/blockchain/state_transition_function_test.cpp index 3d644c2..8d40f25 100644 --- a/tests/unit/blockchain/state_transition_function_test.cpp +++ b/tests/unit/blockchain/state_transition_function_test.cpp @@ -8,12 +8,14 @@ #include +#include "mock/blockchain/metrics_mock.hpp" #include "types/config.hpp" #include "types/signed_block.hpp" #include "types/state.hpp" TEST(STF, Test) { - lean::STF stf; + auto metrics = std::make_shared(); + lean::STF stf(metrics); lean::Config config{ .num_validators = 2, diff --git a/tests/unit/utils/validator_registry_test.cpp b/tests/unit/utils/validator_registry_test.cpp index c87e583..3531b02 100644 --- a/tests/unit/utils/validator_registry_test.cpp +++ b/tests/unit/utils/validator_registry_test.cpp @@ -7,13 +7,16 @@ #include #include "blockchain/impl/validator_registry_impl.hpp" +#include "mock/blockchain/metrics_mock.hpp" #include "testutil/prepare_loggers.hpp" TEST(ValidatorRegistryTest, LoadsRegistryAndResolvesIndices) { auto logger = testutil::prepareLoggers(); + auto metrics = std::make_shared(); lean::ValidatorRegistryImpl registry{ logger, + metrics, R"( node_0: - 0 @@ -31,9 +34,11 @@ TEST(ValidatorRegistryTest, LoadsRegistryAndResolvesIndices) { TEST(ValidatorRegistryTest, MissingNodeDefaultsToZero) { auto logger = testutil::prepareLoggers(); + auto metrics = std::make_shared(); lean::ValidatorRegistryImpl registry{ logger, + metrics, R"( node_0: - 0 @@ -47,9 +52,11 @@ TEST(ValidatorRegistryTest, MissingNodeDefaultsToZero) { TEST(ValidatorRegistryTest, ThrowsOnDuplicateIndex) { auto logger = testutil::prepareLoggers(); + auto metrics = std::make_shared(); EXPECT_THROW((lean::ValidatorRegistryImpl{ logger, + metrics, R"( node_0: - 0