LCOV - code coverage report
Current view: top level - compute_tools/src - installed_extensions.rs (source / functions) Coverage Total Hit
Test: 4f58e98c51285c7fa348e0b410c88a10caf68ad2.info Lines: 0.0 % 83 0
Test Date: 2025-01-07 20:58:07 Functions: 0.0 % 7 0

            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 : }
        

Generated by: LCOV version 2.1-beta