mirror of
https://github.com/neondatabase/neon.git
synced 2026-05-24 00:20:37 +00:00
## Problem My benchmarks show that prometheus is not very good. https://github.com/conradludgate/measured We're already using it in storage_controller and it seems to be working well. ## Summary of changes Replace prometheus with my new measured crate in proxy only. Apologies for the large diff. I tried to keep it as minimal as I could. The label types add a bit of boiler plate (but reduce the chance we mistype the labels), and some of our custom metrics like CounterPair and HLL needed to be rewritten.
244 lines
6.9 KiB
Rust
244 lines
6.9 KiB
Rust
use measured::FixedCardinalityLabel;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fmt;
|
|
|
|
use crate::auth::IpPattern;
|
|
|
|
use crate::intern::{BranchIdInt, EndpointIdInt, ProjectIdInt};
|
|
|
|
/// Generic error response with human-readable description.
|
|
/// Note that we can't always present it to user as is.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct ConsoleError {
|
|
pub error: Box<str>,
|
|
}
|
|
|
|
/// Response which holds client's auth secret, e.g. [`crate::scram::ServerSecret`].
|
|
/// Returned by the `/proxy_get_role_secret` API method.
|
|
#[derive(Deserialize)]
|
|
pub struct GetRoleSecret {
|
|
pub role_secret: Box<str>,
|
|
pub allowed_ips: Option<Vec<IpPattern>>,
|
|
pub project_id: Option<ProjectIdInt>,
|
|
}
|
|
|
|
// Manually implement debug to omit sensitive info.
|
|
impl fmt::Debug for GetRoleSecret {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("GetRoleSecret").finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
/// Response which holds compute node's `host:port` pair.
|
|
/// Returned by the `/proxy_wake_compute` API method.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct WakeCompute {
|
|
pub address: Box<str>,
|
|
pub aux: MetricsAuxInfo,
|
|
}
|
|
|
|
/// Async response which concludes the link auth flow.
|
|
/// Also known as `kickResponse` in the console.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct KickSession<'a> {
|
|
/// Session ID is assigned by the proxy.
|
|
pub session_id: &'a str,
|
|
|
|
/// Compute node connection params.
|
|
#[serde(deserialize_with = "KickSession::parse_db_info")]
|
|
pub result: DatabaseInfo,
|
|
}
|
|
|
|
impl KickSession<'_> {
|
|
fn parse_db_info<'de, D>(des: D) -> Result<DatabaseInfo, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
#[derive(Deserialize)]
|
|
enum Wrapper {
|
|
// Currently, console only reports `Success`.
|
|
// `Failure(String)` used to be here... RIP.
|
|
Success(DatabaseInfo),
|
|
}
|
|
|
|
Wrapper::deserialize(des).map(|x| match x {
|
|
Wrapper::Success(info) => info,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Compute node connection params.
|
|
#[derive(Deserialize)]
|
|
pub struct DatabaseInfo {
|
|
pub host: Box<str>,
|
|
pub port: u16,
|
|
pub dbname: Box<str>,
|
|
pub user: Box<str>,
|
|
/// Console always provides a password, but it might
|
|
/// be inconvenient for debug with local PG instance.
|
|
pub password: Option<Box<str>>,
|
|
pub aux: MetricsAuxInfo,
|
|
}
|
|
|
|
// Manually implement debug to omit sensitive info.
|
|
impl fmt::Debug for DatabaseInfo {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.debug_struct("DatabaseInfo")
|
|
.field("host", &self.host)
|
|
.field("port", &self.port)
|
|
.field("dbname", &self.dbname)
|
|
.field("user", &self.user)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
/// Various labels for prometheus metrics.
|
|
/// Also known as `ProxyMetricsAuxInfo` in the console.
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct MetricsAuxInfo {
|
|
pub endpoint_id: EndpointIdInt,
|
|
pub project_id: ProjectIdInt,
|
|
pub branch_id: BranchIdInt,
|
|
#[serde(default)]
|
|
pub cold_start_info: ColdStartInfo,
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, FixedCardinalityLabel)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ColdStartInfo {
|
|
#[default]
|
|
Unknown,
|
|
/// Compute was already running
|
|
Warm,
|
|
#[serde(rename = "pool_hit")]
|
|
#[label(rename = "pool_hit")]
|
|
/// Compute was not running but there was an available VM
|
|
VmPoolHit,
|
|
#[serde(rename = "pool_miss")]
|
|
#[label(rename = "pool_miss")]
|
|
/// Compute was not running and there were no VMs available
|
|
VmPoolMiss,
|
|
|
|
// not provided by control plane
|
|
/// Connection available from HTTP pool
|
|
HttpPoolHit,
|
|
/// Cached connection info
|
|
WarmCached,
|
|
}
|
|
|
|
impl ColdStartInfo {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
ColdStartInfo::Unknown => "unknown",
|
|
ColdStartInfo::Warm => "warm",
|
|
ColdStartInfo::VmPoolHit => "pool_hit",
|
|
ColdStartInfo::VmPoolMiss => "pool_miss",
|
|
ColdStartInfo::HttpPoolHit => "http_pool_hit",
|
|
ColdStartInfo::WarmCached => "warm_cached",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use serde_json::json;
|
|
|
|
fn dummy_aux() -> serde_json::Value {
|
|
json!({
|
|
"endpoint_id": "endpoint",
|
|
"project_id": "project",
|
|
"branch_id": "branch",
|
|
"cold_start_info": "unknown",
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn parse_kick_session() -> anyhow::Result<()> {
|
|
// This is what the console's kickResponse looks like.
|
|
let json = json!({
|
|
"session_id": "deadbeef",
|
|
"result": {
|
|
"Success": {
|
|
"host": "localhost",
|
|
"port": 5432,
|
|
"dbname": "postgres",
|
|
"user": "john_doe",
|
|
"password": "password",
|
|
"aux": dummy_aux(),
|
|
}
|
|
}
|
|
});
|
|
let _: KickSession = serde_json::from_str(&json.to_string())?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn parse_db_info() -> anyhow::Result<()> {
|
|
// with password
|
|
let _: DatabaseInfo = serde_json::from_value(json!({
|
|
"host": "localhost",
|
|
"port": 5432,
|
|
"dbname": "postgres",
|
|
"user": "john_doe",
|
|
"password": "password",
|
|
"aux": dummy_aux(),
|
|
}))?;
|
|
|
|
// without password
|
|
let _: DatabaseInfo = serde_json::from_value(json!({
|
|
"host": "localhost",
|
|
"port": 5432,
|
|
"dbname": "postgres",
|
|
"user": "john_doe",
|
|
"aux": dummy_aux(),
|
|
}))?;
|
|
|
|
// new field (forward compatibility)
|
|
let _: DatabaseInfo = serde_json::from_value(json!({
|
|
"host": "localhost",
|
|
"port": 5432,
|
|
"dbname": "postgres",
|
|
"user": "john_doe",
|
|
"project": "hello_world",
|
|
"N.E.W": "forward compatibility check",
|
|
"aux": dummy_aux(),
|
|
}))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn parse_wake_compute() -> anyhow::Result<()> {
|
|
let json = json!({
|
|
"address": "0.0.0.0",
|
|
"aux": dummy_aux(),
|
|
});
|
|
let _: WakeCompute = serde_json::from_str(&json.to_string())?;
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn parse_get_role_secret() -> anyhow::Result<()> {
|
|
// Empty `allowed_ips` field.
|
|
let json = json!({
|
|
"role_secret": "secret",
|
|
});
|
|
let _: GetRoleSecret = serde_json::from_str(&json.to_string())?;
|
|
let json = json!({
|
|
"role_secret": "secret",
|
|
"allowed_ips": ["8.8.8.8"],
|
|
});
|
|
let _: GetRoleSecret = serde_json::from_str(&json.to_string())?;
|
|
let json = json!({
|
|
"role_secret": "secret",
|
|
"allowed_ips": ["8.8.8.8"],
|
|
"project_id": "project",
|
|
});
|
|
let _: GetRoleSecret = serde_json::from_str(&json.to_string())?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|