Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ typesense-prometheus-exporter [OPTIONS] --typesense-host <TYPESENSE_HOST> --type
- `--typesense-protocol <TYPESENSE_PROTOCOL>`: Typesense protocol (env: TYPESENSE_PROTOCOL, default: http).
- `--typesense-api-key <TYPESENSE_API_KEY>`: Typesense API key (env: TYPESENSE_API_KEY).
- `--typesense-port <TYPESENSE_PORT>`: Typesense port number (env: TYPESENSE_PORT, default: 8108).
- `--typesense-timeout <TYPESENSE_TIMEOUT>`: Timeout for Typesense API requests in seconds (env: TYPESENSE_TIMEOUT, default: -1 to disable).
- `--exporter-bind-address <EXPORTER_BIND_ADDRESS>`: Internal server bind address (env: EXPORTER_BIND_ADDRESS, default: 0.0.0.0).
- `--exporter-bind-port <EXPORTER_BIND_PORT>`: Internal server bind port (env: EXPORTER_BIND_PORT, default: 8888).

Expand Down
4 changes: 4 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ pub struct CliArgs {
/// Bind port for internal server
#[arg(long, env, default_value_t = 8888)]
pub(crate) exporter_bind_port: u16,

/// Timeout for Typesense API requests in seconds (-1 to disable)
#[arg(long, env, default_value_t = -1)]
pub(crate) typesense_timeout: i64,
}
43 changes: 42 additions & 1 deletion src/prometheus_exp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use std::sync::Arc;
use crate::{
cli::CliArgs,
typesense::models::{
typesense_metrics_model::TypesenseMetrics, typesense_stats_model::TypesenseStats,
typesense_metrics_model::TypesenseMetrics,
typesense_stats_model::TypesenseStats,
typesense_health_model::TypesenseHealth,
typesense_debug_model::TypesenseDebug,
},
};
use prometheus::{register_gauge_vec_with_registry, Encoder, Registry, TextEncoder};
Expand All @@ -13,6 +16,8 @@ use regex::Regex;
pub(crate) async fn generate_metrics(
ts_metrics: TypesenseMetrics,
ts_stats: TypesenseStats,
ts_health: TypesenseHealth,
ts_debug: TypesenseDebug,
cli_args: Arc<CliArgs>,
) -> String {
let registry = Registry::new();
Expand Down Expand Up @@ -398,6 +403,42 @@ pub(crate) async fn generate_metrics(
}
}

let typesense_health = register_gauge_vec_with_registry!(
"typesense_health",
"Health status of Typesense instance (1 = healthy, 0 = unhealthy)",
&["host", "port"],
registry
)
.unwrap();

typesense_health
.with_label_values(&[
&cli_args.typesense_host,
&cli_args.typesense_port.to_string(),
])
.set(if ts_health.ok { 1.0 } else { 0.0 });

let typesense_raft_state = register_gauge_vec_with_registry!(
"typesense_raft_state",
"Raft state of Typesense node (1 = leader, 4 = follower, 0 = other)",
&["host", "port"],
registry
)
.unwrap();

let state_value = match ts_debug.state {
1 => 1.0,
4 => 4.0,
_ => 0.0,
};

typesense_raft_state
.with_label_values(&[
&cli_args.typesense_host,
&cli_args.typesense_port.to_string(),
])
.set(state_value);

let encoder = TextEncoder::new();
let metric_families = registry.gather();
let mut buffer = Vec::new();
Expand Down
16 changes: 12 additions & 4 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ use std::sync::Arc;
use crate::prometheus_exp;
use crate::{
cli::CliArgs,
typesense::{metrics::get_typesense_metrics, stats::get_typesense_stats},
typesense::{
stats::get_typesense_stats,
metrics::get_typesense_metrics,
health::get_typesense_health,
debug::get_typesense_debug,
},
};

use axum::extract::State;
Expand Down Expand Up @@ -59,15 +64,18 @@ async fn root() -> &'static str {
}

async fn metrics_route_handler(State(args): State<Arc<CliArgs>>) -> String {
let (metrics_data, stats_data) = future::join(
let (metrics_data, stats_data, health_data, debug_data) = tokio::join!(
get_typesense_metrics(args.clone()),
get_typesense_stats(args.clone()),
)
.await;
get_typesense_health(args.clone()),
get_typesense_debug(args.clone()),
);

let promdata = prometheus_exp::generate_metrics(
metrics_data.unwrap().clone(),
stats_data.unwrap().clone(),
health_data.unwrap().clone(),
debug_data.unwrap().clone(),
args.clone(),
)
.await;
Expand Down
56 changes: 56 additions & 0 deletions src/typesense/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::sync::Arc;
use std::time::Duration;

use crate::{cli::CliArgs, typesense::models::typesense_debug_model::TypesenseDebug};
use axum::Error;

pub async fn get_typesense_debug(args: Arc<CliArgs>) -> Result<TypesenseDebug, Error> {
let mut debug_data: TypesenseDebug = TypesenseDebug::default();

let mut client_builder = reqwest::Client::builder();
if args.typesense_timeout >= 0 {
client_builder = client_builder.timeout(Duration::from_secs(args.typesense_timeout as u64));
}
let client = client_builder.build().unwrap();

let url = format!(
"{}://{}:{}/debug",
args.typesense_protocol, args.typesense_host, args.typesense_port
);

let res = match client
.get(url)
.header("X-TYPESENSE-API-KEY", format!("{}", args.typesense_api_key))
.send()
.await
{
Ok(res) => res,
Err(e) => {
println!("Debug endpoint: request failed (timeout/connection error): {:?}", e);
return Ok(debug_data);
}
};

match res.status() {
reqwest::StatusCode::OK => {
match res.json::<TypesenseDebug>().await {
Ok(parsed) => {
debug_data = parsed;
}
Err(er) => println!(
"Hm, the response didn't match the shape we expected. {:?}",
er
),
};
}
reqwest::StatusCode::UNAUTHORIZED => {
println!("Debug endpoint: Need to grab a new token");
}
_ => {
println!("Uh oh! Something unexpected happened.");
}
};

Ok(debug_data)
}

52 changes: 52 additions & 0 deletions src/typesense/health.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::sync::Arc;
use std::time::Duration;

use crate::{cli::CliArgs, typesense::models::typesense_health_model::TypesenseHealth};
use axum::Error;

pub async fn get_typesense_health(args: Arc<CliArgs>) -> Result<TypesenseHealth, Error> {
let mut health_data: TypesenseHealth = TypesenseHealth::default();

let mut client_builder = reqwest::Client::builder();
if args.typesense_timeout >= 0 {
client_builder = client_builder.timeout(Duration::from_secs(args.typesense_timeout as u64));
}
let client = client_builder.build().unwrap();

let url = format!(
"{}://{}:{}/health",
args.typesense_protocol, args.typesense_host, args.typesense_port
);

let res = match client
.get(url)
.send()
.await
{
Ok(res) => res,
Err(e) => {
println!("Health endpoint: request failed (timeout/connection error): {:?}", e);
return Ok(health_data);
}
};

match res.status() {
reqwest::StatusCode::OK => {
match res.json::<TypesenseHealth>().await {
Ok(parsed) => {
health_data = parsed;
}
Err(er) => println!(
"Hm, the response didn't match the shape we expected. {:?}",
er
),
};
}
_ => {
println!("Uh oh! Something unexpected happened.");
}
};

Ok(health_data)
}

19 changes: 15 additions & 4 deletions src/typesense/metrics.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
use std::sync::Arc;
use std::time::Duration;

use crate::{cli::CliArgs, typesense::models::typesense_metrics_model::TypesenseMetrics};
use axum::Error;

pub async fn get_typesense_metrics(args: Arc<CliArgs>) -> Result<TypesenseMetrics, Error> {
let mut stats_data: TypesenseMetrics = TypesenseMetrics::default();

let client = reqwest::Client::new();
let mut client_builder = reqwest::Client::builder();
if args.typesense_timeout >= 0 {
client_builder = client_builder.timeout(Duration::from_secs(args.typesense_timeout as u64));
}
let client = client_builder.build().unwrap();

let url: String = format!(
"{}://{}:{}/metrics.json",
args.typesense_protocol, args.typesense_host, args.typesense_port
);

let res = client
let res = match client
.get(url)
.header("X-TYPESENSE-API-KEY", format!("{}", args.typesense_api_key))
.send()
.await
.unwrap();
{
Ok(res) => res,
Err(e) => {
println!("Metrics endpoint: request failed (timeout/connection error): {:?}", e);
return Ok(stats_data);
}
};

match res.status() {
reqwest::StatusCode::OK => {
Expand All @@ -36,7 +47,7 @@ pub async fn get_typesense_metrics(args: Arc<CliArgs>) -> Result<TypesenseMetric
println!("Need to grab a new token");
}
_ => {
panic!("Uh oh! Something unexpected happened.");
println!("Uh oh! Something unexpected happened.");
}
};

Expand Down
2 changes: 2 additions & 0 deletions src/typesense/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod metrics;
pub mod stats;
pub mod health;
pub mod debug;
pub mod models;
4 changes: 3 additions & 1 deletion src/typesense/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pub mod typesense_metrics_model;
pub mod typesense_stats_model;
pub mod typesense_stats_model;
pub mod typesense_health_model;
pub mod typesense_debug_model;
17 changes: 17 additions & 0 deletions src/typesense/models/typesense_debug_model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TypesenseDebug {
pub version: String,
pub state: i32,
}

impl Default for TypesenseDebug {
fn default() -> TypesenseDebug {
TypesenseDebug {
version: String::new(),
state: 0,
}
}
}

18 changes: 18 additions & 0 deletions src/typesense/models/typesense_health_model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TypesenseHealth {
pub ok: bool,
#[serde(default)]
pub resource_error: Option<String>,
}

impl Default for TypesenseHealth {
fn default() -> TypesenseHealth {
TypesenseHealth {
ok: false,
resource_error: None,
}
}
}

19 changes: 15 additions & 4 deletions src/typesense/stats.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
use std::sync::Arc;
use std::time::Duration;

use crate::{cli::CliArgs, typesense::models::typesense_stats_model::TypesenseStats};
use axum::Error;

pub async fn get_typesense_stats(args: Arc<CliArgs>) -> Result<TypesenseStats, Error> {
let mut stats_data: TypesenseStats = TypesenseStats::default();

let client = reqwest::Client::new();
let mut client_builder = reqwest::Client::builder();
if args.typesense_timeout >= 0 {
client_builder = client_builder.timeout(Duration::from_secs(args.typesense_timeout as u64));
}
let client = client_builder.build().unwrap();

let url = format!(
"{}://{}:{}/stats.json",
args.typesense_protocol, args.typesense_host, args.typesense_port
);

let res = client
let res = match client
.get(url)
.header("X-TYPESENSE-API-KEY", format!("{}", args.typesense_api_key))
.send()
.await
.unwrap();
{
Ok(res) => res,
Err(e) => {
println!("Stats endpoint: request failed (timeout/connection error): {:?}", e);
return Ok(stats_data);
}
};

match res.status() {
reqwest::StatusCode::OK => {
Expand All @@ -33,7 +44,7 @@ pub async fn get_typesense_stats(args: Arc<CliArgs>) -> Result<TypesenseStats, E
println!("Need to grab a new token");
}
_ => {
panic!("Uh oh! Something unexpected happened.");
println!("Uh oh! Something unexpected happened.");
}
};

Expand Down