Line data Source code
1 : use compute_api::responses::{InstalledExtension, InstalledExtensions};
2 : use metrics::proto::MetricFamily;
3 : use std::collections::HashMap;
4 :
5 : use anyhow::Result;
6 : use postgres::{Client, NoTls};
7 :
8 : use metrics::core::Collector;
9 : use metrics::{register_uint_gauge_vec, UIntGaugeVec};
10 : use once_cell::sync::Lazy;
11 :
12 : /// We don't reuse get_existing_dbs() just for code clarity
13 : /// and to make database listing query here more explicit.
14 : ///
15 : /// Limit the number of databases to 500 to avoid excessive load.
16 0 : fn list_dbs(client: &mut Client) -> Result<Vec<String>> {
17 : // `pg_database.datconnlimit = -2` means that the database is in the
18 : // invalid state
19 0 : let databases = client
20 0 : .query(
21 0 : "SELECT datname FROM pg_catalog.pg_database
22 0 : WHERE datallowconn
23 0 : AND datconnlimit <> - 2
24 0 : LIMIT 500",
25 0 : &[],
26 0 : )?
27 0 : .iter()
28 0 : .map(|row| {
29 0 : let db: String = row.get("datname");
30 0 : db
31 0 : })
32 0 : .collect();
33 0 :
34 0 : Ok(databases)
35 0 : }
36 :
37 : /// Connect to every database (see list_dbs above) and get the list of installed extensions.
38 : ///
39 : /// Same extension can be installed in multiple databases with different versions,
40 : /// so we report a separate metric (number of databases where it is installed)
41 : /// for each extension version.
42 0 : pub fn get_installed_extensions(mut conf: postgres::config::Config) -> Result<InstalledExtensions> {
43 0 : conf.application_name("compute_ctl:get_installed_extensions");
44 0 : let mut client = conf.connect(NoTls)?;
45 0 : let databases: Vec<String> = list_dbs(&mut client)?;
46 :
47 0 : let mut extensions_map: HashMap<(String, String, String), InstalledExtension> = HashMap::new();
48 0 : for db in databases.iter() {
49 0 : conf.dbname(db);
50 0 : let mut db_client = conf.connect(NoTls)?;
51 0 : let extensions: Vec<(String, String, i32)> = db_client
52 0 : .query(
53 0 : "SELECT extname, extversion, extowner::integer FROM pg_catalog.pg_extension",
54 0 : &[],
55 0 : )?
56 0 : .iter()
57 0 : .map(|row| {
58 0 : (
59 0 : row.get("extname"),
60 0 : row.get("extversion"),
61 0 : row.get("extowner"),
62 0 : )
63 0 : })
64 0 : .collect();
65 :
66 0 : for (extname, v, extowner) in extensions.iter() {
67 0 : let version = v.to_string();
68 :
69 : // check if the extension is owned by superuser
70 : // 10 is the oid of superuser
71 0 : let owned_by_superuser = if *extowner == 10 { "1" } else { "0" };
72 :
73 0 : extensions_map
74 0 : .entry((
75 0 : extname.to_string(),
76 0 : version.clone(),
77 0 : owned_by_superuser.to_string(),
78 0 : ))
79 0 : .and_modify(|e| {
80 0 : // count the number of databases where the extension is installed
81 0 : e.n_databases += 1;
82 0 : })
83 0 : .or_insert(InstalledExtension {
84 0 : extname: extname.to_string(),
85 0 : version: version.clone(),
86 0 : n_databases: 1,
87 0 : owned_by_superuser: owned_by_superuser.to_string(),
88 0 : });
89 0 : }
90 : }
91 :
92 0 : for (key, ext) in extensions_map.iter() {
93 0 : let (extname, version, owned_by_superuser) = key;
94 0 : let n_databases = ext.n_databases as u64;
95 0 :
96 0 : INSTALLED_EXTENSIONS
97 0 : .with_label_values(&[extname, version, owned_by_superuser])
98 0 : .set(n_databases);
99 0 : }
100 :
101 0 : Ok(InstalledExtensions {
102 0 : extensions: extensions_map.into_values().collect(),
103 0 : })
104 0 : }
105 :
106 0 : static INSTALLED_EXTENSIONS: Lazy<UIntGaugeVec> = Lazy::new(|| {
107 0 : register_uint_gauge_vec!(
108 0 : "compute_installed_extensions",
109 0 : "Number of databases where the version of extension is installed",
110 0 : &["extension_name", "version", "owned_by_superuser"]
111 0 : )
112 0 : .expect("failed to define a metric")
113 0 : });
114 :
115 0 : pub fn collect() -> Vec<MetricFamily> {
116 0 : INSTALLED_EXTENSIONS.collect()
117 0 : }
|