json stats

This commit is contained in:
Toshit Chawda 2024-09-26 18:06:18 -07:00
parent 14b5bd796b
commit d0ef7b476c
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
4 changed files with 104 additions and 116 deletions

View file

@ -27,7 +27,7 @@ pty-process = { version = "0.4.0", features = ["async", "tokio"], optional = tru
regex = "1.10.6" regex = "1.10.6"
rustls-pemfile = "2.1.3" rustls-pemfile = "2.1.3"
serde = { version = "1.0.208", features = ["derive"] } serde = { version = "1.0.208", features = ["derive"] }
serde_json = { version = "1.0.125", optional = true } serde_json = "1.0.125"
serde_yaml = { version = "0.9.34", optional = true } serde_yaml = { version = "0.9.34", optional = true }
sha2 = "0.10.8" sha2 = "0.10.8"
shell-words = { version = "1.1.0", optional = true } shell-words = { version = "1.1.0", optional = true }
@ -43,7 +43,6 @@ wisp-mux = { version = "5.0.0", path = "../wisp", features = ["fastwebsockets",
[features] [features]
default = ["toml"] default = ["toml"]
json = ["dep:serde_json"]
yaml = ["dep:serde_yaml"] yaml = ["dep:serde_yaml"]
toml = ["dep:toml"] toml = ["dep:toml"]

3
server/e.toml Normal file
View file

@ -0,0 +1,3 @@
[server]
enable_stats_endpoint = true
stats_endpoint = ["tcp", "127.0.0.1:5000"]

View file

@ -67,7 +67,7 @@ pub enum StatsEndpoint {
/// Stats on the same listener as the Wisp server. /// Stats on the same listener as the Wisp server.
SameServer(String), SameServer(String),
/// Stats on this address and socket type. /// Stats on this address and socket type.
SeparateServer((SocketType, String)), SeparateServer(BindAddr),
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -467,44 +467,32 @@ impl StreamConfig {
impl Config { impl Config {
pub fn ser(&self) -> anyhow::Result<String> { pub fn ser(&self) -> anyhow::Result<String> {
Ok(match CLI.format { Ok(match CLI.format {
ConfigFormat::Json => serde_json::to_string_pretty(self)?,
#[cfg(feature = "toml")] #[cfg(feature = "toml")]
ConfigFormat::Toml => toml::to_string_pretty(self)?, ConfigFormat::Toml => toml::to_string_pretty(self)?,
#[cfg(feature = "json")]
ConfigFormat::Json => serde_json::to_string_pretty(self)?,
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
ConfigFormat::Yaml => serde_yaml::to_string(self)?, ConfigFormat::Yaml => serde_yaml::to_string(self)?,
#[cfg(not(any(feature = "toml", feature = "json", feature = "yaml")))]
ConfigFormat::None => String::new(),
}) })
} }
pub fn de(string: String) -> anyhow::Result<Self> { pub fn de(string: String) -> anyhow::Result<Self> {
Ok(match CLI.format { Ok(match CLI.format {
ConfigFormat::Json => serde_json::from_str(&string)?,
#[cfg(feature = "toml")] #[cfg(feature = "toml")]
ConfigFormat::Toml => toml::from_str(&string)?, ConfigFormat::Toml => toml::from_str(&string)?,
#[cfg(feature = "json")]
ConfigFormat::Json => serde_json::from_str(&string)?,
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
ConfigFormat::Yaml => serde_yaml::from_str(&string)?, ConfigFormat::Yaml => serde_yaml::from_str(&string)?,
#[cfg(not(any(feature = "toml", feature = "json", feature = "yaml")))]
ConfigFormat::None => {
let _ = string;
Self::default()
}
}) })
} }
} }
#[derive(Clone, Copy, Eq, PartialEq, ValueEnum)] #[derive(Clone, Copy, Eq, PartialEq, ValueEnum)]
pub enum ConfigFormat { pub enum ConfigFormat {
Json,
#[cfg(feature = "toml")] #[cfg(feature = "toml")]
Toml, Toml,
#[cfg(feature = "json")]
Json,
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
Yaml, Yaml,
#[cfg(not(any(feature = "toml", feature = "json", feature = "yaml")))]
None,
} }
impl Default for ConfigFormat { impl Default for ConfigFormat {
@ -512,13 +500,10 @@ impl Default for ConfigFormat {
cfg_if! { cfg_if! {
if #[cfg(feature = "toml")] { if #[cfg(feature = "toml")] {
Self::Toml Self::Toml
} else if #[cfg(feature = "json")] {
Self::Json
} else if #[cfg(feature = "yaml")] { } else if #[cfg(feature = "yaml")] {
Self::Yaml Self::Yaml
} else { } else {
compile_error!("no config format feature enabled!"); Self::Json
Self::None
} }
} }
} }

View file

@ -1,7 +1,7 @@
#![feature(ip)] #![feature(ip)]
#![deny(clippy::todo)] #![deny(clippy::todo)]
use std::{fmt::Write, fs::read_to_string, net::IpAddr}; use std::{collections::HashMap, fs::read_to_string, net::IpAddr};
use anyhow::Context; use anyhow::Context;
use clap::Parser; use clap::Parser;
@ -16,6 +16,7 @@ use lazy_static::lazy_static;
use listener::ServerListener; use listener::ServerListener;
use log::{error, info}; use log::{error, info};
use route::{route_stats, ServerRouteResult}; use route::{route_stats, ServerRouteResult};
use serde::Serialize;
use tokio::signal::unix::{signal, SignalKind}; use tokio::signal::unix::{signal, SignalKind};
use uuid::Uuid; use uuid::Uuid;
use wisp_mux::{ConnectPacket, StreamType}; use wisp_mux::{ConnectPacket, StreamType};
@ -93,92 +94,90 @@ fn format_stream_type(stream_type: StreamType) -> &'static str {
} }
} }
fn generate_stats() -> anyhow::Result<String> { #[derive(Serialize)]
let mut out = String::new(); struct MemoryStats {
let len = CLIENTS.len(); active: f64,
allocated: f64,
mapped: f64,
metadata: f64,
resident: f64,
retained: f64,
}
#[derive(Serialize)]
struct StreamStats {
stream_type: String,
requested: String,
resolved: String,
}
impl From<(ConnectPacket, ConnectPacket)> for StreamStats {
fn from(value: (ConnectPacket, ConnectPacket)) -> Self {
Self {
stream_type: format_stream_type(value.0.stream_type).to_string(),
requested: format!(
"{}:{}",
value.0.destination_hostname, value.0.destination_port
),
resolved: format!(
"{}:{}",
value.1.destination_hostname, value.1.destination_port
),
}
}
}
#[derive(Serialize)]
struct ClientStats {
wsproxy: bool,
streams: HashMap<String, StreamStats>,
}
#[derive(Serialize)]
struct ServerStats {
config: String,
clients: HashMap<String, ClientStats>,
memory: MemoryStats,
}
fn generate_stats() -> anyhow::Result<String> {
use tikv_jemalloc_ctl::stats::{active, allocated, mapped, metadata, resident, retained}; use tikv_jemalloc_ctl::stats::{active, allocated, mapped, metadata, resident, retained};
tikv_jemalloc_ctl::epoch::advance()?; tikv_jemalloc_ctl::epoch::advance()?;
writeln!(&mut out, "Memory usage:")?; let memory = MemoryStats {
writeln!( active: active::read()? as f64 / (1024 * 1024) as f64,
&mut out, allocated: allocated::read()? as f64 / (1024 * 1024) as f64,
"\tActive: {:?} MiB", mapped: mapped::read()? as f64 / (1024 * 1024) as f64,
active::read()? as f64 / (1024 * 1024) as f64 metadata: metadata::read()? as f64 / (1024 * 1024) as f64,
)?; resident: resident::read()? as f64 / (1024 * 1024) as f64,
writeln!( retained: retained::read()? as f64 / (1024 * 1024) as f64,
&mut out, };
"\tAllocated: {:?} MiB",
allocated::read()? as f64 / (1024 * 1024) as f64
)?;
writeln!(
&mut out,
"\tMapped: {:?} MiB",
mapped::read()? as f64 / (1024 * 1024) as f64
)?;
writeln!(
&mut out,
"\tMetadata: {:?} MiB",
metadata::read()? as f64 / (1024 * 1024) as f64
)?;
writeln!(
&mut out,
"\tResident: {:?} MiB",
resident::read()? as f64 / (1024 * 1024) as f64
)?;
writeln!(
&mut out,
"\tRetained: {:?} MiB",
retained::read()? as f64 / (1024 * 1024) as f64
)?;
writeln!( let clients = CLIENTS
&mut out, .iter()
"{} clients connected{}", .map(|x| {
len, (
if len != 0 { ":" } else { "" } x.key().to_string(),
)?; ClientStats {
wsproxy: x.value().1,
streams: x
.value()
.0
.iter()
.map(|x| (x.key().to_string(), StreamStats::from(x.value().clone())))
.collect(),
},
)
})
.collect();
for client in CLIENTS.iter() { let stats = ServerStats {
let len = client.value().0.len(); config: CONFIG.ser()?,
clients,
memory,
};
writeln!( Ok(serde_json::to_string_pretty(&stats)?)
&mut out,
"\tClient \"{}\"{}: {} streams connected{}",
client.key(),
if client.value().1 { " (wsproxy)" } else { "" },
len,
if len != 0 && CONFIG.server.verbose_stats {
":"
} else {
""
}
)?;
if CONFIG.server.verbose_stats {
for stream in client.value().0.iter() {
writeln!(
&mut out,
"\t\tStream \"{}\": {}",
stream.key(),
format_stream_type(stream.value().0.stream_type)
)?;
writeln!(
&mut out,
"\t\t\tRequested: {}:{}",
stream.value().0.destination_hostname,
stream.value().0.destination_port
)?;
writeln!(
&mut out,
"\t\t\tResolved: {}:{}",
stream.value().1.destination_hostname,
stream.value().1.destination_port
)?;
}
}
}
Ok(out)
} }
fn handle_stream(stream: ServerRouteResult, id: String) { fn handle_stream(stream: ServerRouteResult, id: String) {
@ -230,24 +229,26 @@ async fn main() -> anyhow::Result<()> {
.await .await
.with_context(|| format!("failed to bind to address {}", CONFIG.server.bind.1))?; .with_context(|| format!("failed to bind to address {}", CONFIG.server.bind.1))?;
if let Some(bind_addr) = CONFIG.server.stats_endpoint.get_bindaddr() { if CONFIG.server.enable_stats_endpoint {
info!("stats server listening on {:?}", bind_addr); if let Some(bind_addr) = CONFIG.server.stats_endpoint.get_bindaddr() {
let mut stats_listener = ServerListener::new(&bind_addr).await.with_context(|| { info!("stats server listening on {:?}", bind_addr);
format!("failed to bind to address {} for stats server", bind_addr.1) let mut stats_listener = ServerListener::new(&bind_addr).await.with_context(|| {
})?; format!("failed to bind to address {} for stats server", bind_addr.1)
})?;
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {
match stats_listener.accept().await { match stats_listener.accept().await {
Ok((stream, _)) => { Ok((stream, _)) => {
if let Err(e) = route_stats(stream).await { if let Err(e) = route_stats(stream).await {
error!("error while routing stats client: {:?}", e); error!("error while routing stats client: {:?}", e);
}
} }
Err(e) => error!("error while accepting stats client: {:?}", e),
} }
Err(e) => error!("error while accepting stats client: {:?}", e),
} }
} });
}); }
} }
let stats_endpoint = CONFIG.server.stats_endpoint.get_endpoint(); let stats_endpoint = CONFIG.server.stats_endpoint.get_endpoint();