Skip to main content

rupg_func/
stat.rs

1//! The functions of the cumulative statistics for one object, from `pgstatfuncs.c`: the counters of a relation or an index, of a function and of a database, and the `xact` forms that read the counts of the transaction.
2//!
3//! rupg keeps the sequential scans of a relation, their count `numscans` and the time of the last one `lastscan`, and the counters of the rows that the transactions wrote: `tuples_inserted`, `tuples_updated`, `tuples_deleted`, `live_tuples`, `dead_tuples`, `mod_since_analyze` and `ins_since_vacuum`. The other functions give the value that PostgreSQL gives for an object that has no entry in the statistics: 0 for a counter and for a total time of a relation or a database, and null for a time stamp and for each value of a function. The views `pg_stat_*` and `pg_statio_*` then have a row for each object, with the columns and the types of PostgreSQL (spec/07 section 7.14).
4
5use rupg_common::Result;
6use rupg_types::Value;
7
8use crate::{Call, Kernel, bad_value};
9
10/// The counters of a relation that `PG_STAT_GET_RELENTRY_INT64` and `PG_STAT_GET_XACT_RELENTRY_INT64` read.
11const 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
32/// The counters of a database that `PG_STAT_GET_DBENTRY_INT64` reads, after `pg_stat_get_db_`.
33const 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
61/// The times of a database in milliseconds that `PG_STAT_GET_DBENTRY_FLOAT8_MS` reads, after `pg_stat_get_db_`.
62const DATABASE_TIMES: [&str; 5] =
63    ["active_time", "blk_read_time", "blk_write_time", "idle_in_transaction_time", "session_time"];
64
65/// The total times of a relation that `PG_STAT_GET_RELENTRY_FLOAT8` reads.
66const RELATION_TIMES: [&str; 4] =
67    ["total_vacuum_time", "total_autovacuum_time", "total_analyze_time", "total_autoanalyze_time"];
68
69/// The time stamps of a relation that `PG_STAT_GET_RELENTRY_TIMESTAMPTZ` reads.
70const 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/// The counters of a relation in the statistics of the server, the part of `PgStat_StatTabEntry` that rupg keeps.
80#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
81pub struct RelationCounts {
82    /// `numscans`: the count of the sequential scans of the relation.
83    pub numscans: i64,
84    /// `lastscan`: the end of the last transaction that scanned the relation, in microseconds since 2000-01-01 UTC, or `None` before the first scan.
85    pub lastscan: Option<i64>,
86    /// `tuples_inserted`, `tuples_updated` and `tuples_deleted`: the rows that the transactions wrote, also the transactions that rolled back.
87    pub written: Writes,
88    /// `live_tuples`: the rows that the committed transactions inserted and did not delete.
89    pub live_tuples: i64,
90    /// `dead_tuples`: the old versions of the rows that the committed updates and deletes left, and the rows that the rolled back transactions inserted or updated.
91    pub dead_tuples: i64,
92    /// `mod_since_analyze`: the rows that the committed transactions wrote.
93    pub mod_since_analyze: i64,
94    /// `ins_since_vacuum`: the rows that the transactions inserted.
95    pub ins_since_vacuum: i64,
96}
97
98/// The rows that a transaction inserted, updated and deleted in one relation, as `pgstat_count_heap_insert`, `pgstat_count_heap_update` and `pgstat_count_heap_delete` count them.
99#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
100pub struct Writes {
101    /// The rows that the transaction inserted.
102    pub inserted: i64,
103    /// The rows that the transaction updated.
104    pub updated: i64,
105    /// The rows that the transaction deleted.
106    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
133/// The OID of the object. A strict function does not get null, and PostgreSQL does not check that the object exists.
134fn object(args: &[Value]) -> Result<u32> {
135    args.first().and_then(Value::as_oid).ok_or_else(bad_value)
136}
137
138/// A counter of an object with no entry.
139fn counter(_: &Call<'_>, args: &[Value]) -> Result<Value> {
140    object(args)?;
141    Ok(Value::Int8(0))
142}
143
144/// `pg_stat_get_numscans`: the sequential scans of a relation that the server reported.
145fn 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
150/// `pg_stat_get_lastscan`: the time of the last sequential scan of a relation, or null.
151fn 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
156/// `pg_stat_get_xact_numscans`: the sequential scans of a relation that the transaction counted.
157fn xact_numscans(call: &Call<'_>, args: &[Value]) -> Result<Value> {
158    Ok(Value::Int8(call.session.pending_scans(object(args)?)))
159}
160
161/// A counter of a relation that the server reported, or 0 for a relation with no entry.
162fn 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
171/// A count of the rows that the transaction wrote in a relation.
172fn 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
176/// `pg_stat_get_tuples_inserted`.
177fn tuples_inserted(call: &Call<'_>, args: &[Value]) -> Result<Value> {
178    relation_counter(call, args, |c| c.written.inserted)
179}
180
181/// `pg_stat_get_tuples_updated`.
182fn tuples_updated(call: &Call<'_>, args: &[Value]) -> Result<Value> {
183    relation_counter(call, args, |c| c.written.updated)
184}
185
186/// `pg_stat_get_tuples_deleted`.
187fn tuples_deleted(call: &Call<'_>, args: &[Value]) -> Result<Value> {
188    relation_counter(call, args, |c| c.written.deleted)
189}
190
191/// `pg_stat_get_live_tuples`.
192fn live_tuples(call: &Call<'_>, args: &[Value]) -> Result<Value> {
193    relation_counter(call, args, |c| c.live_tuples)
194}
195
196/// `pg_stat_get_dead_tuples`.
197fn dead_tuples(call: &Call<'_>, args: &[Value]) -> Result<Value> {
198    relation_counter(call, args, |c| c.dead_tuples)
199}
200
201/// `pg_stat_get_mod_since_analyze`.
202fn mod_since_analyze(call: &Call<'_>, args: &[Value]) -> Result<Value> {
203    relation_counter(call, args, |c| c.mod_since_analyze)
204}
205
206/// `pg_stat_get_ins_since_vacuum`.
207fn ins_since_vacuum(call: &Call<'_>, args: &[Value]) -> Result<Value> {
208    relation_counter(call, args, |c| c.ins_since_vacuum)
209}
210
211/// `pg_stat_get_xact_tuples_inserted`.
212fn xact_inserted(call: &Call<'_>, args: &[Value]) -> Result<Value> {
213    xact_counter(call, args, |w| w.inserted)
214}
215
216/// `pg_stat_get_xact_tuples_updated`.
217fn xact_updated(call: &Call<'_>, args: &[Value]) -> Result<Value> {
218    xact_counter(call, args, |w| w.updated)
219}
220
221/// `pg_stat_get_xact_tuples_deleted`.
222fn xact_deleted(call: &Call<'_>, args: &[Value]) -> Result<Value> {
223    xact_counter(call, args, |w| w.deleted)
224}
225
226/// A total time of an object with no entry.
227fn total_time(_: &Call<'_>, args: &[Value]) -> Result<Value> {
228    object(args)?;
229    Ok(Value::Float8(0.0))
230}
231
232/// A time stamp of an object with no entry, or a value of a function with no entry.
233fn none(_: &Call<'_>, args: &[Value]) -> Result<Value> {
234    object(args)?;
235    Ok(Value::Null)
236}
237
238/// `pg_stat_get_db_checksum_failures`: null when the data has no checksums, as `DataChecksumsEnabled` tells, else the count of a database with no entry.
239fn 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
247/// `pg_stat_force_next_flush`: the next end of a transaction reports the counts at once. rupg reports them at the end of each transaction, so the call has no effect. The result is `void`, whose value the engine keeps as an empty text.
248fn force_next_flush(_: &Call<'_>, _: &[Value]) -> Result<Value> {
249    Ok(Value::text(""))
250}
251
252/// The kernel of a function of this module by its `prosrc`.
253pub(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    /// The module has the 78 functions of one object of `pg_proc`, each with one argument of the type `oid` and a result of the type `int8`, `float8` or `timestamptz`.
302    #[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}