1use rupg_common::Result;
6use rupg_types::Value;
7
8use crate::{Call, Kernel, bad_value};
9
10const RELATION_COUNTERS: [&str; 18] = [
12 "analyze_count",
13 "autoanalyze_count",
14 "autovacuum_count",
15 "blocks_fetched",
16 "blocks_hit",
17 "dead_tuples",
18 "ins_since_vacuum",
19 "live_tuples",
20 "mod_since_analyze",
21 "numscans",
22 "tuples_deleted",
23 "tuples_fetched",
24 "tuples_hot_updated",
25 "tuples_newpage_updated",
26 "tuples_inserted",
27 "tuples_returned",
28 "tuples_updated",
29 "vacuum_count",
30];
31
32const DATABASE_COUNTERS: [&str; 25] = [
34 "blocks_fetched",
35 "blocks_hit",
36 "conflict_bufferpin",
37 "conflict_lock",
38 "conflict_snapshot",
39 "conflict_startup_deadlock",
40 "conflict_tablespace",
41 "conflict_logicalslot",
42 "conflict_all",
43 "deadlocks",
44 "sessions",
45 "sessions_abandoned",
46 "sessions_fatal",
47 "sessions_killed",
48 "parallel_workers_to_launch",
49 "parallel_workers_launched",
50 "temp_bytes",
51 "temp_files",
52 "tuples_deleted",
53 "tuples_fetched",
54 "tuples_inserted",
55 "tuples_returned",
56 "tuples_updated",
57 "xact_commit",
58 "xact_rollback",
59];
60
61const DATABASE_TIMES: [&str; 5] =
63 ["active_time", "blk_read_time", "blk_write_time", "idle_in_transaction_time", "session_time"];
64
65const RELATION_TIMES: [&str; 4] =
67 ["total_vacuum_time", "total_autovacuum_time", "total_analyze_time", "total_autoanalyze_time"];
68
69const RELATION_STAMPS: [&str; 6] = [
71 "last_analyze_time",
72 "last_autoanalyze_time",
73 "last_autovacuum_time",
74 "last_vacuum_time",
75 "lastscan",
76 "stat_reset_time",
77];
78
79#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
81pub struct RelationCounts {
82 pub numscans: i64,
84 pub lastscan: Option<i64>,
86 pub written: Writes,
88 pub live_tuples: i64,
90 pub dead_tuples: i64,
92 pub mod_since_analyze: i64,
94 pub ins_since_vacuum: i64,
96}
97
98#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
100pub struct Writes {
101 pub inserted: i64,
103 pub updated: i64,
105 pub deleted: i64,
107}
108
109impl std::ops::Add for Writes {
110 type Output = Self;
111
112 fn add(self, other: Self) -> Self {
113 Self {
114 inserted: self.inserted + other.inserted,
115 updated: self.updated + other.updated,
116 deleted: self.deleted + other.deleted,
117 }
118 }
119}
120
121impl std::ops::Sub for Writes {
122 type Output = Self;
123
124 fn sub(self, other: Self) -> Self {
125 Self {
126 inserted: self.inserted - other.inserted,
127 updated: self.updated - other.updated,
128 deleted: self.deleted - other.deleted,
129 }
130 }
131}
132
133fn object(args: &[Value]) -> Result<u32> {
135 args.first().and_then(Value::as_oid).ok_or_else(bad_value)
136}
137
138fn counter(_: &Call<'_>, args: &[Value]) -> Result<Value> {
140 object(args)?;
141 Ok(Value::Int8(0))
142}
143
144fn numscans(call: &Call<'_>, args: &[Value]) -> Result<Value> {
146 let counts = call.session.relation_counts(object(args)?);
147 Ok(Value::Int8(counts.map_or(0, |c| c.numscans)))
148}
149
150fn lastscan(call: &Call<'_>, args: &[Value]) -> Result<Value> {
152 let counts = call.session.relation_counts(object(args)?);
153 Ok(counts.and_then(|c| c.lastscan).map_or(Value::Null, Value::TimestampTz))
154}
155
156fn xact_numscans(call: &Call<'_>, args: &[Value]) -> Result<Value> {
158 Ok(Value::Int8(call.session.pending_scans(object(args)?)))
159}
160
161fn relation_counter(
163 call: &Call<'_>,
164 args: &[Value],
165 read: fn(&RelationCounts) -> i64,
166) -> Result<Value> {
167 let counts = call.session.relation_counts(object(args)?);
168 Ok(Value::Int8(counts.as_ref().map_or(0, read)))
169}
170
171fn xact_counter(call: &Call<'_>, args: &[Value], read: fn(&Writes) -> i64) -> Result<Value> {
173 Ok(Value::Int8(read(&call.session.pending_writes(object(args)?))))
174}
175
176fn tuples_inserted(call: &Call<'_>, args: &[Value]) -> Result<Value> {
178 relation_counter(call, args, |c| c.written.inserted)
179}
180
181fn tuples_updated(call: &Call<'_>, args: &[Value]) -> Result<Value> {
183 relation_counter(call, args, |c| c.written.updated)
184}
185
186fn tuples_deleted(call: &Call<'_>, args: &[Value]) -> Result<Value> {
188 relation_counter(call, args, |c| c.written.deleted)
189}
190
191fn live_tuples(call: &Call<'_>, args: &[Value]) -> Result<Value> {
193 relation_counter(call, args, |c| c.live_tuples)
194}
195
196fn dead_tuples(call: &Call<'_>, args: &[Value]) -> Result<Value> {
198 relation_counter(call, args, |c| c.dead_tuples)
199}
200
201fn mod_since_analyze(call: &Call<'_>, args: &[Value]) -> Result<Value> {
203 relation_counter(call, args, |c| c.mod_since_analyze)
204}
205
206fn ins_since_vacuum(call: &Call<'_>, args: &[Value]) -> Result<Value> {
208 relation_counter(call, args, |c| c.ins_since_vacuum)
209}
210
211fn xact_inserted(call: &Call<'_>, args: &[Value]) -> Result<Value> {
213 xact_counter(call, args, |w| w.inserted)
214}
215
216fn xact_updated(call: &Call<'_>, args: &[Value]) -> Result<Value> {
218 xact_counter(call, args, |w| w.updated)
219}
220
221fn xact_deleted(call: &Call<'_>, args: &[Value]) -> Result<Value> {
223 xact_counter(call, args, |w| w.deleted)
224}
225
226fn total_time(_: &Call<'_>, args: &[Value]) -> Result<Value> {
228 object(args)?;
229 Ok(Value::Float8(0.0))
230}
231
232fn none(_: &Call<'_>, args: &[Value]) -> Result<Value> {
234 object(args)?;
235 Ok(Value::Null)
236}
237
238fn checksum_failures(call: &Call<'_>, args: &[Value]) -> Result<Value> {
240 object(args)?;
241 Ok(match call.session.setting("data_checksums").as_deref() {
242 Some("on") => Value::Int8(0),
243 _ => Value::Null,
244 })
245}
246
247fn force_next_flush(_: &Call<'_>, _: &[Value]) -> Result<Value> {
249 Ok(Value::text(""))
250}
251
252pub(crate) fn by_src(src: &str) -> Option<Kernel> {
254 if src == "pg_stat_force_next_flush" {
255 return Some(force_next_flush);
256 }
257 let name = src.strip_prefix("pg_stat_get_")?;
258 if let Some(name) = name.strip_prefix("db_") {
259 return Some(match name {
260 "checksum_failures" => checksum_failures,
261 "checksum_last_failure" | "stat_reset_time" => none,
262 _ if DATABASE_COUNTERS.contains(&name) => counter,
263 _ if DATABASE_TIMES.contains(&name) => total_time,
264 _ => return None,
265 });
266 }
267 if let Some(name) = name.strip_prefix("xact_") {
268 return Some(match name {
269 "function_calls" | "function_total_time" | "function_self_time" => none,
270 "numscans" => xact_numscans,
271 "tuples_inserted" => xact_inserted,
272 "tuples_updated" => xact_updated,
273 "tuples_deleted" => xact_deleted,
274 _ if RELATION_COUNTERS.contains(&name) => counter,
275 _ => return None,
276 });
277 }
278 Some(match name {
279 "function_calls" | "function_total_time" | "function_self_time" => none,
280 "function_stat_reset_time" => none,
281 "numscans" => numscans,
282 "lastscan" => lastscan,
283 "tuples_inserted" => tuples_inserted,
284 "tuples_updated" => tuples_updated,
285 "tuples_deleted" => tuples_deleted,
286 "live_tuples" => live_tuples,
287 "dead_tuples" => dead_tuples,
288 "mod_since_analyze" => mod_since_analyze,
289 "ins_since_vacuum" => ins_since_vacuum,
290 _ if RELATION_COUNTERS.contains(&name) => counter,
291 _ if RELATION_TIMES.contains(&name) => total_time,
292 _ if RELATION_STAMPS.contains(&name) => none,
293 _ => return None,
294 })
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
303 fn covers_the_functions_of_one_object() {
304 let mut found = 0;
305 for proc in rupg_pgcatalog::builtin::procs() {
306 if !proc.src.starts_with("pg_stat_get_") || by_src(proc.src).is_none() {
307 continue;
308 }
309 found += 1;
310 assert_eq!(proc.argtypes, [26], "{}", proc.src);
311 assert!(matches!(proc.rettype, 20 | 701 | 1184), "{}", proc.src);
312 }
313 assert_eq!(found, 78);
314 }
315}