use clap::{Parser, Subcommand};
use pg_extras::diagnose::report::render_diagnose_report;
use pg_extras::{
all_locks, bloat, blocking, buffercache_stats, buffercache_usage, cache_hit, calls,
connections, db_settings, diagnose, duplicate_indexes, extensions, index_cache_hit,
index_scans, index_size, index_usage, indexes, locks, long_running_queries, mandelbrot,
null_indexes, outliers, pg_pool, records_rank, render_table, seq_scans, ssl_used,
table_cache_hit, table_index_scans, table_indexes_size, table_size, tables, total_index_size,
total_table_size, unused_indexes, vacuum_stats, AllLocks, Bloat, Blocking, BuffercacheStats,
BuffercacheUsage, CacheHit, Calls, Connections, DbSettings, DuplicateIndexes, Extensions,
IndexCacheHit, IndexScans, IndexSize, IndexUsage, Indexes, Locks, LongRunningQueries,
Mandelbrot, NullIndexes, Outliers, PgExtrasError, Query, RecordsRank, SeqScans, SslUsed,
TableCacheHit, TableIndexScans, TableIndexesSize, TableSize, Tables, TotalIndexSize,
TotalTableSize, UnusedIndexes, VacuumStats,
};
#[derive(Parser, Debug)]
#[command(
version,
about,
long_about = "pg-extras: PostgreSQL database performance insights. Locks, index usage, buffer cache hit ratios, vacuum stats and more.
https://github.com/pawurb/pg-extras-rs"
)]
pub struct PgExtrasArgs {
#[command(subcommand)]
pub cmd: PgSubcommand,
}
#[cfg(feature = "web")]
use sqlx::PgPool;
#[derive(Subcommand, Debug)]
pub enum PgSubcommand {
#[command(about = "Diagnose common database problems")]
Diagnose(EmptyArgs),
#[cfg(feature = "web")]
#[command(about = "Start dashboard web server")]
Web(EmptyArgs),
#[command(about = &AllLocks::description())]
AllLocks(EmptyArgs),
#[command(about = &Bloat::description())]
Bloat(EmptyArgs),
#[command(about = &Blocking::description())]
Blocking(EmptyArgs),
#[command(about = &BuffercacheStats::description())]
BuffercacheStats(EmptyArgs),
#[command(about = &BuffercacheUsage::description())]
BuffercacheUsage(EmptyArgs),
#[command(about = &CacheHit::description())]
CacheHit(EmptyArgs),
#[command(about = &Calls::description())]
Calls(EmptyArgs),
#[command(about = &Connections::description())]
Connections(EmptyArgs),
#[command(about = &DbSettings::description())]
DbSettings(EmptyArgs),
#[command(about = &DuplicateIndexes::description())]
DuplicateIndexes(EmptyArgs),
#[command(about = &Extensions::description())]
Extensions(EmptyArgs),
#[command(about = &IndexCacheHit::description())]
IndexCacheHit(EmptyArgs),
#[command(about = &IndexScans::description())]
IndexScans(EmptyArgs),
#[command(about = &IndexSize::description())]
IndexSize(EmptyArgs),
#[command(about = &IndexUsage::description())]
IndexUsage(EmptyArgs),
#[command(about = &Indexes::description())]
Indexes(EmptyArgs),
#[command(about = &Locks::description())]
Locks(EmptyArgs),
#[command(about = &LongRunningQueries::description())]
LongRunningQueries(EmptyArgs),
#[command(about = &NullIndexes::description())]
NullIndexes(EmptyArgs),
#[command(about = &Outliers::description())]
Outliers(EmptyArgs),
#[command(about = &Mandelbrot::description())]
Mandelbrot(EmptyArgs),
#[command(about = &RecordsRank::description())]
RecordsRank(EmptyArgs),
#[command(about = &SeqScans::description())]
SeqScans(EmptyArgs),
#[command(about = &SslUsed::description())]
SslUsed(EmptyArgs),
#[command(about = &TableCacheHit::description())]
TableCacheHit(EmptyArgs),
#[command(about = &TableIndexScans::description())]
TableIndexScans(EmptyArgs),
#[command(about = &TableIndexesSize::description())]
TableIndexesSize(EmptyArgs),
#[command(about = &TableSize::description())]
TableSize(EmptyArgs),
#[command(about = &Tables::description())]
Tables(EmptyArgs),
#[command(about = &TotalIndexSize::description())]
TotalIndexSize(EmptyArgs),
#[command(about = &TotalTableSize::description())]
TotalTableSize(EmptyArgs),
#[command(about = &UnusedIndexes::description())]
UnusedIndexes(EmptyArgs),
#[command(about = &VacuumStats::description())]
VacuumStats(EmptyArgs),
}
#[derive(Parser, Debug)]
pub struct EmptyArgs {}
#[tokio::main]
async fn main() {
match execute().await {
Ok(_) => {}
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
}
type PG = PgSubcommand;
async fn execute() -> Result<(), PgExtrasError> {
let args = PgExtrasArgs::parse();
let pool = pg_pool().await?;
match args.cmd {
PG::AllLocks(_args) => {
render_table(all_locks(&pool).await?);
}
PG::Bloat(_args) => {
render_table(bloat(&pool).await?);
}
PG::Blocking(_args) => {
render_table(blocking(None, &pool).await?);
}
PG::BuffercacheStats(_args) => {
render_table(buffercache_stats(&pool).await?);
}
PG::BuffercacheUsage(_args) => {
render_table(buffercache_usage(&pool).await?);
}
PG::CacheHit(_args) => {
render_table(cache_hit(None, &pool).await?);
}
PG::Calls(_args) => {
render_table(calls(None, &pool).await?);
}
PG::Connections(_args) => {
render_table(connections(&pool).await?);
}
PG::DbSettings(_args) => {
render_table(db_settings(&pool).await?);
}
PG::Diagnose(_args) => {
render_diagnose_report(diagnose(&pool).await?);
}
PG::DuplicateIndexes(_args) => {
render_table(duplicate_indexes(&pool).await?);
}
PG::Extensions(_args) => {
render_table(extensions(&pool).await?);
}
PG::IndexCacheHit(_args) => {
render_table(index_cache_hit(None, &pool).await?);
}
PG::IndexScans(_args) => {
render_table(index_scans(None, &pool).await?);
}
PG::IndexSize(_args) => {
render_table(index_size(&pool).await?);
}
PG::IndexUsage(_args) => {
render_table(index_usage(None, &pool).await?);
}
PG::Indexes(_args) => {
render_table(indexes(&pool).await?);
}
PG::Locks(_args) => {
render_table(locks(&pool).await?);
}
PG::LongRunningQueries(_args) => {
render_table(long_running_queries(&pool).await?);
}
PG::NullIndexes(_args) => {
render_table(null_indexes(None, &pool).await?);
}
PG::Outliers(_args) => {
render_table(outliers(&pool).await?);
}
PG::Mandelbrot(_args) => {
render_table(mandelbrot(&pool).await?);
}
PG::RecordsRank(_args) => {
render_table(records_rank(None, &pool).await?);
}
PG::SeqScans(_args) => {
render_table(seq_scans(None, &pool).await?);
}
PG::SslUsed(_args) => {
render_table(ssl_used(&pool).await?);
}
PG::TableCacheHit(_args) => {
render_table(table_cache_hit(&pool).await?);
}
PG::TableIndexScans(_args) => {
render_table(table_index_scans(None, &pool).await?);
}
PG::TableIndexesSize(_args) => {
render_table(table_indexes_size(None, &pool).await?);
}
PG::TableSize(_args) => {
render_table(table_size(&pool).await?);
}
PG::Tables(_args) => {
render_table(tables(None, &pool).await?);
}
PG::TotalIndexSize(_args) => {
render_table(total_index_size(&pool).await?);
}
PG::TotalTableSize(_args) => {
render_table(total_table_size(&pool).await?);
}
PG::UnusedIndexes(_args) => {
render_table(unused_indexes(None, &pool).await?);
}
PG::VacuumStats(_args) => {
render_table(vacuum_stats(&pool).await?);
}
#[cfg(feature = "web")]
PG::Web(_args) => {
start_web_server(pool).await?;
}
}
Ok(())
}
#[cfg(feature = "web")]
async fn start_web_server(pool: PgPool) -> Result<(), PgExtrasError> {
let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
if tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
.await
.is_err()
{
return Err(PgExtrasError::Other(format!(
"Port {} is already in use",
port
)));
}
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
.await
.unwrap();
let app = pg_extras::web::routes::app(pool).await;
println!(
"Available on http://{}/pg_extras",
listener.local_addr().unwrap()
);
axum::serve(listener, app).await.unwrap();
Ok(())
}