Skip to content

Commit

Permalink
libsql-server,admin: add top-k queries to stats
Browse files Browse the repository at this point in the history
Top 10 most expensive queries are now stored and available
in the admin api:

```
$ curl -s http://localhost:8081/v1/namespaces/default/stats | jq '.top_queries'
[
  {
    "weight": 1,
    "rows_written": 0,
    "rows_read": 1,
    "query": "SELECT 1;"
  },
  {
    "weight": 1,
    "rows_written": 0,
    "rows_read": 1,
    "query": "SELECT 42;"
  },
  {
    "weight": 3,
    "rows_written": 0,
    "rows_read": 3,
    "query": "SELECT * FROM sqlite_master;"
  }
]
```
  • Loading branch information
psarna committed Oct 20, 2023
1 parent 056be63 commit cc6b3be
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 3 deletions.
6 changes: 6 additions & 0 deletions libsql-server/src/connection/libsql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,12 @@ impl<W: WalHook> Connection<W> {
};
self.stats.inc_rows_read(rows_read as u64);
self.stats.inc_rows_written(rows_written as u64);
let weight = (rows_read + rows_written) as i64;
if self.stats.qualifies_as_top_query(weight) {
let query = stmt.expanded_sql().unwrap_or("<unknown>".into());
self.stats
.add_top_query(crate::stats::TopQuery::new(query, rows_read, rows_written));
}
}

fn describe(&self, sql: &str) -> DescribeResult {
Expand Down
10 changes: 9 additions & 1 deletion libsql-server/src/http/admin/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use axum::Json;

use crate::namespace::{MakeNamespace, NamespaceName};
use crate::replication::FrameNo;
use crate::stats::Stats;
use crate::stats::{Stats, TopQuery};

use super::AppState;

Expand All @@ -18,6 +18,7 @@ pub struct StatsResponse {
pub storage_bytes_used: u64,
pub write_requests_delegated: u64,
pub replication_index: FrameNo,
pub top_queries: Vec<TopQuery>,
}

impl From<&Stats> for StatsResponse {
Expand All @@ -28,6 +29,13 @@ impl From<&Stats> for StatsResponse {
storage_bytes_used: stats.storage_bytes_used(),
write_requests_delegated: stats.write_requests_delegated(),
replication_index: stats.get_current_frame_no(),
top_queries: stats
.top_queries()
.read()
.unwrap()
.iter()
.cloned()
.collect(),
}
}
}
Expand Down
53 changes: 51 additions & 2 deletions libsql-server/src/stats.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Weak};
use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use std::sync::{Arc, RwLock, Weak};

use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use tokio::io::AsyncWriteExt;
use tokio::task::JoinSet;
use tokio::time::Duration;

use crate::replication::FrameNo;

#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct TopQuery {
pub weight: i64,
pub rows_written: i32,
pub rows_read: i32,
pub query: String,
}

impl TopQuery {
pub fn new(query: String, rows_read: i32, rows_written: i32) -> Self {
Self {
weight: rows_read as i64 + rows_written as i64,
rows_read,
rows_written,
query,
}
}
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Stats {
#[serde(default)]
Expand All @@ -22,6 +42,11 @@ pub struct Stats {
write_requests_delegated: AtomicU64,
#[serde(default)]
current_frame_no: AtomicU64,
// Lowest value in currently stored top queries
#[serde(default)]
top_query_threshold: AtomicI64,
#[serde(default)]
top_queries: Arc<RwLock<BTreeSet<TopQuery>>>,
}

impl Stats {
Expand Down Expand Up @@ -91,6 +116,30 @@ impl Stats {
pub(crate) fn get_current_frame_no(&self) -> FrameNo {
self.current_frame_no.load(Ordering::Relaxed)
}

pub(crate) fn add_top_query(&self, query: TopQuery) {
let mut top_queries = self.top_queries.write().unwrap();
tracing::debug!(
"top query: {},{}:{}",
query.rows_read,
query.rows_written,
query.query
);
top_queries.insert(query);
if top_queries.len() > 10 {
top_queries.pop_first();
}
self.top_query_threshold
.store(top_queries.first().unwrap().weight, Ordering::Relaxed);
}

pub(crate) fn qualifies_as_top_query(&self, weight: i64) -> bool {
weight >= self.top_query_threshold.load(Ordering::Relaxed)
}

pub(crate) fn top_queries(&self) -> &Arc<RwLock<BTreeSet<TopQuery>>> {
&self.top_queries
}
}

async fn spawn_stats_persist_thread(stats: Weak<Stats>, path: PathBuf) -> anyhow::Result<()> {
Expand Down

0 comments on commit cc6b3be

Please sign in to comment.