Skip to content

feat(metrics): implement prometheus metrics endpoint#283

Open
ns212 wants to merge 1 commit intodevelopmentfrom
prom-metrics
Open

feat(metrics): implement prometheus metrics endpoint#283
ns212 wants to merge 1 commit intodevelopmentfrom
prom-metrics

Conversation

@ns212
Copy link
Contributor

@ns212 ns212 commented Mar 21, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a new metrics endpoint that delivers real-time performance data in a standardized format for enhanced monitoring and observability.
  • Chores

    • Updated internal monitoring integration to support the new endpoint.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 21, 2025

Walkthrough

This update removes Visual Studio Code theme configurations, adds the Prometheus dependency to the Rust project's dependency file, and introduces a new metrics endpoint. The metrics endpoint is implemented by adding a route in the HTTP server and corresponding asynchronous handler functions for collecting and encoding Prometheus metrics data.

Changes

File(s) Change Summary
.vscode/settings.json Deleted file that contained VS Code color theme settings
p2pool/Cargo.toml Added Prometheus dependency (prometheus = "0.13.4")
p2pool/src/server/http/server.rs
p2pool/src/server/http/stats/handlers.rs
Added a new /metrics route and implemented asynchronous handler functions (handle_metrics and get_metrics_data) for generating and encoding metrics in Prometheus format

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant S as HTTP Server
    participant H as Metrics Handler
    participant R as Prometheus Registry
    participant E as TextEncoder

    C->>S: GET /metrics
    S->>H: handle_metrics(request)
    H->>R: Create & register metrics
    H->>H: Call get_metrics_data()
    H->>E: Encode collected metrics
    E-->>H: Encoded metrics data
    H->>S: Return HTTP 200 with metrics payload
    S-->>C: HTTP 200 Response with Prometheus metrics
Loading

Poem

I'm a rabbit hopping through the code,
Metrics now bloom on every road.
Themes are gone for a cleaner view,
Prometheus joins our crew anew.
Hop along, let's cheer and play,
CodeRabbit smiles at a brighter day!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
p2pool/Cargo.toml (1)

72-72: Confirm optimal Prometheus version.
Adding prometheus = "0.13.4" is fine, but ensure it won't cause dependency resolution or security issues.

If you're unsure, consider verifying any open issues or advisories for this specific version.

p2pool/src/server/http/stats/handlers.rs (1)

367-406: Metrics handler flow looks good.
This async function properly collects, encodes, and returns metrics via a Prometheus-friendly format. Consider adding a brief doccomment explaining usage and ensure you have tests verifying proper response on success/failure.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 565f7b0 and 82a5588.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .vscode/settings.json (0 hunks)
  • p2pool/Cargo.toml (1 hunks)
  • p2pool/src/server/http/server.rs (1 hunks)
  • p2pool/src/server/http/stats/handlers.rs (3 hunks)
💤 Files with no reviewable changes (1)
  • .vscode/settings.json
🧰 Additional context used
🧬 Code Definitions (1)
p2pool/src/server/http/stats/handlers.rs (7)
p2pool/src/server/http/server.rs (1) (1)
  • new (53-65)
p2pool/src/sharechain/in_memory.rs (1) (1)
  • new (74-181)
p2pool/src/server/server.rs (1) (1)
  • new (52-139)
p2pool/src/server/http/stats_collector.rs (2) (2)
  • new (53-87)
  • new (390-392)
p2pool/src/sharechain/mod.rs (1) (1)
  • new (63-73)
p2pool/src/sharechain/p2chain_level.rs (1) (1)
  • height (127-129)
p2pool/src/server/p2p/peer_store.rs (4) (4)
  • whitelist_peers (192-194)
  • whitelist_peers (211-211)
  • whitelist_peers (226-226)
  • greylist_peers (196-198)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: test (esmeralda)
  • GitHub Check: ci
  • GitHub Check: Cucumber tests / Base Layer
  • GitHub Check: cargo check with stable
  • GitHub Check: security_audit
🔇 Additional comments (4)
p2pool/src/server/http/server.rs (1)

77-77: Route addition is correct.
Adding /metrics endpoint aligns well with standard convention for Prometheus scraping. Great job.

p2pool/src/server/http/stats/handlers.rs (3)

9-9: No issues with the new import path.
The response::Response import is needed for returning the new type from the handler.


13-13: Prometheus imports are valid.
These imports appropriately cover encoding, gauge, and registry usage.


26-26: New log target definition is fine.
Defining METRICS_LOG_TARGET ensures logs for metrics are clearly separated.

Comment on lines +408 to +525
async fn get_metrics_data(state: AppState, registry: &Registry) -> Result<(), StatusCode> {
// Get full stats which includes chain stats and connection info
let stats = handle_get_stats(State(state.clone())).await?.0;

// Get peer info
let (tx, rx) = oneshot::channel();
state
.p2p_service_client
.send(P2pServiceQuery::GetPeers(tx))
.await
.map_err(|error| {
error!(target: METRICS_LOG_TARGET, "Failed to get peers info: {error:?}");
StatusCode::INTERNAL_SERVER_ERROR
})?;

let peers_info = rx.await.map_err(|error| {
error!(target: METRICS_LOG_TARGET, "Failed to receive peers info: {error:?}");
StatusCode::INTERNAL_SERVER_ERROR
})?;

// Register and set metrics

// We'll use serde_json to convert the stats to a value we can extract fields from
let rx_stats_json = serde_json::to_value(&stats.randomx_stats).unwrap_or_default();
let sha3x_stats_json = serde_json::to_value(&stats.sha3x_stats).unwrap_or_default();

// Chain height metrics
let chain_height = GaugeVec::new(Opts::new("p2pool_chain_height", "Current chain height"), &["algorithm"]).unwrap();
registry.register(Box::new(chain_height.clone())).unwrap();

if let Some(height) = rx_stats_json.get("height").and_then(|v| v.as_u64()) {
chain_height.with_label_values(&["randomx"]).set(height as f64);
}

if let Some(height) = sha3x_stats_json.get("height").and_then(|v| v.as_u64()) {
chain_height.with_label_values(&["sha3x"]).set(height as f64);
}

// Shares metrics
let total_shares = GaugeVec::new(Opts::new("p2pool_total_shares", "Total number of shares"), &[
"algorithm",
])
.unwrap();
registry.register(Box::new(total_shares.clone())).unwrap();

if let Some(shares) = rx_stats_json.get("total_shares").and_then(|v| v.as_u64()) {
total_shares.with_label_values(&["randomx"]).set(shares as f64);
}

if let Some(shares) = sha3x_stats_json.get("total_shares").and_then(|v| v.as_u64()) {
total_shares.with_label_values(&["sha3x"]).set(shares as f64);
}

// My shares metrics
let my_shares = GaugeVec::new(Opts::new("p2pool_my_shares", "Number of my shares"), &["algorithm"]).unwrap();
registry.register(Box::new(my_shares.clone())).unwrap();

if let Some(shares) = rx_stats_json.get("num_my_shares").and_then(|v| v.as_u64()) {
my_shares.with_label_values(&["randomx"]).set(shares as f64);
}

if let Some(shares) = sha3x_stats_json.get("num_my_shares").and_then(|v| v.as_u64()) {
my_shares.with_label_values(&["sha3x"]).set(shares as f64);
}

// Connection metrics
let connected_peers = Gauge::new("p2pool_connected_peers", "Number of connected peers").unwrap();
registry.register(Box::new(connected_peers.clone())).unwrap();
connected_peers.set(stats.connection_info.connected_peers as f64);

// Connection counters
let pending_incoming = Gauge::new("p2pool_pending_incoming", "Number of pending incoming connections").unwrap();
registry.register(Box::new(pending_incoming.clone())).unwrap();
pending_incoming.set(stats.connection_info.network_info.connection_counters.pending_incoming as f64);

let pending_outgoing = Gauge::new("p2pool_pending_outgoing", "Number of pending outgoing connections").unwrap();
registry.register(Box::new(pending_outgoing.clone())).unwrap();
pending_outgoing.set(stats.connection_info.network_info.connection_counters.pending_outgoing as f64);

let established_incoming = Gauge::new(
"p2pool_established_incoming",
"Number of established incoming connections",
)
.unwrap();
registry.register(Box::new(established_incoming.clone())).unwrap();
established_incoming.set(
stats
.connection_info
.network_info
.connection_counters
.established_incoming as f64,
);

let established_outgoing = Gauge::new(
"p2pool_established_outgoing",
"Number of established outgoing connections",
)
.unwrap();
registry.register(Box::new(established_outgoing.clone())).unwrap();
established_outgoing.set(
stats
.connection_info
.network_info
.connection_counters
.established_outgoing as f64,
);

// Peer list metrics
let whitelist_peers = IntGauge::new("p2pool_whitelist_peers", "Number of whitelisted peers").unwrap();
registry.register(Box::new(whitelist_peers.clone())).unwrap();
whitelist_peers.set(peers_info.0.len() as i64);

let greylist_peers = IntGauge::new("p2pool_greylist_peers", "Number of greylisted peers").unwrap();
registry.register(Box::new(greylist_peers.clone())).unwrap();
greylist_peers.set(peers_info.1.len() as i64);

Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid duplicate metric registrations on every request call.
Each time handle_metrics is invoked, get_metrics_data re-registers multiple metrics. Typically, we'd define and register shared metrics once at startup, then just update them within each request. Repeated registration can lead to duplicates or memory overhead. A recommended approach is to keep the Gauges in a static or higher scope, updating them instead of re-registering.

-async fn get_metrics_data(state: AppState, registry: &Registry) -> Result<(), StatusCode> {
-    // Register and set metrics
-    let chain_height = GaugeVec::new(Opts::new("p2pool_chain_height", "Current chain height"), &["algorithm"]).unwrap();
-    registry.register(Box::new(chain_height.clone())).unwrap();
-    // ...
+async fn get_metrics_data(state: AppState) -> Result<(), StatusCode> {
+    // Instead of registering metrics here, update previously registered metrics:
+    // chain_height.with_label_values(&["randomx"]).set(..);
+    // ...
}

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant