Skip to content

Conversation

@zensgit
Copy link
Owner

@zensgit zensgit commented Sep 26, 2025

Adds /metrics endpoint with Prometheus text output:\n- jive_password_rehash_total counter\n- jive_password_hash_users{algo} gauges (2a,2b,2y,argon2id)\n\nNon-breaking; queries best-effort. All existing tests pass.

@Copilot Copilot AI review requested due to automatic review settings September 26, 2025 00:47
@gemini-code-assist
Copy link

Summary of Changes

Hello @zensgit, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the jive-api by integrating a dedicated /metrics endpoint. This endpoint exposes crucial operational data in a Prometheus-friendly format, specifically focusing on password security metrics. It provides insights into the ongoing transition of password hashing algorithms and the current distribution of users across different hashing schemes, facilitating better monitoring and operational awareness without impacting existing functionality.

Highlights

  • New Metrics Endpoint: Introduces a new /metrics endpoint to the API, providing Prometheus-compatible output for monitoring.
  • Password Rehash Counter: Adds a jive_password_rehash_total counter to track the total number of successful bcrypt to argon2id password rehashes.
  • Password Hash Distribution Gauge: Includes a jive_password_hash_users gauge that reports the count of users for each password hash algorithm variant (bcrypt 2a, 2b, 2y, and argon2id).
  • Non-Breaking Changes: The changes are non-breaking, and all existing tests continue to pass, ensuring backward compatibility.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@zensgit zensgit enabled auto-merge (squash) September 26, 2025 00:47
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds a Prometheus-compatible metrics endpoint to the Jive API service, exposing password hash distribution data and rehash counters for monitoring purposes.

  • Adds /metrics endpoint that returns Prometheus text format metrics
  • Tracks password hash algorithm distribution (bcrypt 2a/2b/2y, argon2id) and rehash counters
  • Implements best-effort querying with graceful fallback to zero values on database errors

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
jive-api/src/metrics.rs New metrics handler implementing Prometheus text format output with password hash distribution and rehash counter metrics
jive-api/src/main.rs Adds metrics module and routes /metrics endpoint to the metrics handler

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

row.try_get::<i64,_>("b2y").unwrap_or(0),
row.try_get::<i64,_>("a2id").unwrap_or(0),
)
} else { (0,0,0,0) };
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

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

[nitpick] The tuple lacks spacing between elements. Consider formatting as (0, 0, 0, 0) for better readability and consistency with Rust formatting conventions.

Suggested change
} else { (0,0,0,0) };
} else { (0, 0, 0, 0) };

Copilot uses AI. Check for mistakes.
Comment on lines 9 to 24
let (b2a, b2b, b2y, a2id) = if let Ok(row) = sqlx::query(
"SELECT \
COUNT(*) FILTER (WHERE password_hash LIKE '$2a$%') AS b2a,\
COUNT(*) FILTER (WHERE password_hash LIKE '$2b$%') AS b2b,\
COUNT(*) FILTER (WHERE password_hash LIKE '$2y$%') AS b2y,\
COUNT(*) FILTER (WHERE password_hash LIKE '$argon2id$%') AS a2id\
FROM users"
).fetch_one(pool).await {
use sqlx::Row;
(
row.try_get::<i64,_>("b2a").unwrap_or(0),
row.try_get::<i64,_>("b2b").unwrap_or(0),
row.try_get::<i64,_>("b2y").unwrap_or(0),
row.try_get::<i64,_>("a2id").unwrap_or(0),
)
} else { (0,0,0,0) };
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

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

This query performs multiple LIKE operations on the same column. Consider using a single query with CASE statements or regular expressions for better performance: SELECT algo, COUNT(*) FROM (SELECT CASE WHEN password_hash LIKE '$2a$%' THEN 'b2a' WHEN password_hash LIKE '$2b$%' THEN 'b2b' ... END as algo FROM users) GROUP BY algo.

Suggested change
let (b2a, b2b, b2y, a2id) = if let Ok(row) = sqlx::query(
"SELECT \
COUNT(*) FILTER (WHERE password_hash LIKE '$2a$%') AS b2a,\
COUNT(*) FILTER (WHERE password_hash LIKE '$2b$%') AS b2b,\
COUNT(*) FILTER (WHERE password_hash LIKE '$2y$%') AS b2y,\
COUNT(*) FILTER (WHERE password_hash LIKE '$argon2id$%') AS a2id\
FROM users"
).fetch_one(pool).await {
use sqlx::Row;
(
row.try_get::<i64,_>("b2a").unwrap_or(0),
row.try_get::<i64,_>("b2b").unwrap_or(0),
row.try_get::<i64,_>("b2y").unwrap_or(0),
row.try_get::<i64,_>("a2id").unwrap_or(0),
)
} else { (0,0,0,0) };
let mut b2a = 0;
let mut b2b = 0;
let mut b2y = 0;
let mut a2id = 0;
if let Ok(rows) = sqlx::query(
"SELECT
CASE
WHEN password_hash LIKE '$2a$%' THEN 'b2a'
WHEN password_hash LIKE '$2b$%' THEN 'b2b'
WHEN password_hash LIKE '$2y$%' THEN 'b2y'
WHEN password_hash LIKE '$argon2id$%' THEN 'a2id'
ELSE 'other'
END AS algo,
COUNT(*) as count
FROM users
GROUP BY algo"
).fetch_all(pool).await {
use sqlx::Row;
for row in rows {
let algo: String = row.try_get("algo").unwrap_or_default();
let count: i64 = row.try_get("count").unwrap_or(0);
match algo.as_str() {
"b2a" => b2a = count,
"b2b" => b2b = count,
"b2y" => b2y = count,
"a2id" => a2id = count,
_ => {},
}
}
}

Copilot uses AI. Check for mistakes.
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a /metrics endpoint for Prometheus, exposing password hash distribution and rehash counts. The implementation is a good start for improving observability. My review includes a critical fix for potential panics in error handling, along with suggestions to improve maintainability by reducing code duplication and adding logging for silent failures. Addressing these points will make the new endpoint more robust and easier to maintain.

Comment on lines 19 to 22
row.try_get::<i64,_>("b2a").unwrap_or(0),
row.try_get::<i64,_>("b2b").unwrap_or(0),
row.try_get::<i64,_>("b2y").unwrap_or(0),
row.try_get::<i64,_>("a2id").unwrap_or(0),

Choose a reason for hiding this comment

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

high

unwrap_or() on a Result panics if the value is an Err, which would crash the request handler. To safely provide a default value, you should convert the Result to an Option with .ok() first, or use unwrap_or_else. This same issue exists in the duplicated code in main.rs's health_check function.

Suggested change
row.try_get::<i64,_>("b2a").unwrap_or(0),
row.try_get::<i64,_>("b2b").unwrap_or(0),
row.try_get::<i64,_>("b2y").unwrap_or(0),
row.try_get::<i64,_>("a2id").unwrap_or(0),
row.try_get::<i64,_>("b2a").ok().unwrap_or(0),
row.try_get::<i64,_>("b2b").ok().unwrap_or(0),
row.try_get::<i64,_>("b2y").ok().unwrap_or(0),
row.try_get::<i64,_>("a2id").ok().unwrap_or(0),

pub async fn metrics_handler(axum::extract::State(state): axum::extract::State<AppState>) -> impl IntoResponse {
let pool: &PgPool = &state.pool;
// Query hash distribution (best-effort)
let (b2a, b2b, b2y, a2id) = if let Ok(row) = sqlx::query(

Choose a reason for hiding this comment

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

medium

This block of code for querying the password hash distribution (lines 9-24) is identical to the one in the health_check function in main.rs. Duplicating code makes maintenance harder and increases the chance of bugs (like an issue I've pointed out separately). Consider extracting this logic into a shared function, for example as a new method on AppState or in a database utility module, to improve maintainability.

row.try_get::<i64,_>("b2y").unwrap_or(0),
row.try_get::<i64,_>("a2id").unwrap_or(0),
)
} else { (0,0,0,0) };

Choose a reason for hiding this comment

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

medium

When the database query fails, the error is silently ignored, and the metrics default to 0. This can hide underlying database problems. It would be better to log a warning when the query fails.

Suggested change
} else { (0,0,0,0) };
} else { tracing::warn!("Failed to query password hash distribution for metrics"); (0,0,0,0) };

@zensgit zensgit merged commit e6d55d6 into main Sep 26, 2025
10 checks passed
@zensgit zensgit deleted the chore/metrics-endpoint branch September 26, 2025 01:10
@zensgit zensgit mentioned this pull request Oct 15, 2025
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