github.com/netdata/go.d.plugin@v0.58.1/modules/postgres/collect.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package postgres 4 5 import ( 6 "context" 7 "database/sql" 8 "fmt" 9 "strconv" 10 "time" 11 12 "github.com/jackc/pgx/v4" 13 "github.com/jackc/pgx/v4/stdlib" 14 ) 15 16 const ( 17 pgVersion94 = 9_04_00 18 pgVersion10 = 10_00_00 19 pgVersion11 = 11_00_00 20 ) 21 22 func (p *Postgres) collect() (map[string]int64, error) { 23 if p.db == nil { 24 db, err := p.openPrimaryConnection() 25 if err != nil { 26 return nil, err 27 } 28 p.db = db 29 } 30 31 if p.pgVersion == 0 { 32 ver, err := p.doQueryServerVersion() 33 if err != nil { 34 return nil, fmt.Errorf("querying server version error: %v", err) 35 } 36 p.pgVersion = ver 37 p.Debugf("connected to PostgreSQL v%d", p.pgVersion) 38 } 39 40 if p.superUser == nil { 41 v, err := p.doQueryIsSuperUser() 42 if err != nil { 43 return nil, fmt.Errorf("querying is super user error: %v", err) 44 } 45 p.superUser = &v 46 p.Debugf("connected as super user: %v", *p.superUser) 47 } 48 49 if p.pgIsInRecovery == nil { 50 v, err := p.doQueryPGIsInRecovery() 51 if err != nil { 52 return nil, fmt.Errorf("querying recovery status error: %v", err) 53 } 54 p.pgIsInRecovery = &v 55 p.Debugf("the instance is in recovery mode: %v", *p.pgIsInRecovery) 56 } 57 58 now := time.Now() 59 60 if now.Sub(p.recheckSettingsTime) > p.recheckSettingsEvery { 61 p.recheckSettingsTime = now 62 maxConn, err := p.doQuerySettingsMaxConnections() 63 if err != nil { 64 return nil, fmt.Errorf("querying settings max connections error: %v", err) 65 } 66 p.mx.maxConnections = maxConn 67 68 maxLocks, err := p.doQuerySettingsMaxLocksHeld() 69 if err != nil { 70 return nil, fmt.Errorf("querying settings max locks held error: %v", err) 71 } 72 p.mx.maxLocksHeld = maxLocks 73 } 74 75 p.resetMetrics() 76 77 if p.pgVersion >= pgVersion10 { 78 // need 'backend_type' in pg_stat_activity 79 p.addXactQueryRunningTimeChartsOnce.Do(func() { 80 p.addTransactionsRunTimeHistogramChart() 81 p.addQueriesRunTimeHistogramChart() 82 }) 83 } 84 if p.isSuperUser() { 85 p.addWALFilesChartsOnce.Do(p.addWALFilesCharts) 86 } 87 88 if err := p.doQueryGlobalMetrics(); err != nil { 89 return nil, err 90 } 91 if err := p.doQueryReplicationMetrics(); err != nil { 92 return nil, err 93 } 94 if err := p.doQueryDatabasesMetrics(); err != nil { 95 return nil, err 96 } 97 if p.dbSr != nil { 98 if err := p.doQueryQueryableDatabases(); err != nil { 99 return nil, err 100 } 101 } 102 if err := p.doQueryTablesMetrics(); err != nil { 103 return nil, err 104 } 105 if err := p.doQueryIndexesMetrics(); err != nil { 106 return nil, err 107 } 108 109 if now.Sub(p.doSlowTime) > p.doSlowEvery { 110 p.doSlowTime = now 111 if err := p.doQueryBloat(); err != nil { 112 return nil, err 113 } 114 if err := p.doQueryColumns(); err != nil { 115 return nil, err 116 } 117 } 118 119 mx := make(map[string]int64) 120 p.collectMetrics(mx) 121 122 return mx, nil 123 } 124 125 func (p *Postgres) openPrimaryConnection() (*sql.DB, error) { 126 db, err := sql.Open("pgx", p.DSN) 127 if err != nil { 128 return nil, fmt.Errorf("error on opening a connection with the Postgres database [%s]: %v", p.DSN, err) 129 } 130 131 db.SetMaxOpenConns(1) 132 db.SetMaxIdleConns(1) 133 db.SetConnMaxLifetime(10 * time.Minute) 134 135 ctx, cancel := context.WithTimeout(context.Background(), p.Timeout.Duration) 136 defer cancel() 137 138 if err := db.PingContext(ctx); err != nil { 139 _ = db.Close() 140 return nil, fmt.Errorf("error on pinging the Postgres database [%s]: %v", p.DSN, err) 141 } 142 143 return db, nil 144 } 145 146 func (p *Postgres) openSecondaryConnection(dbname string) (*sql.DB, string, error) { 147 cfg, err := pgx.ParseConfig(p.DSN) 148 if err != nil { 149 return nil, "", fmt.Errorf("error on parsing DSN [%s]: %v", p.DSN, err) 150 } 151 152 cfg.Database = dbname 153 connStr := stdlib.RegisterConnConfig(cfg) 154 155 db, err := sql.Open("pgx", connStr) 156 if err != nil { 157 stdlib.UnregisterConnConfig(connStr) 158 return nil, "", fmt.Errorf("error on opening a secondary connection with the Postgres database [%s]: %v", dbname, err) 159 } 160 161 db.SetMaxOpenConns(1) 162 db.SetMaxIdleConns(1) 163 db.SetConnMaxLifetime(10 * time.Minute) 164 165 ctx, cancel := context.WithTimeout(context.Background(), p.Timeout.Duration) 166 defer cancel() 167 168 if err := db.PingContext(ctx); err != nil { 169 stdlib.UnregisterConnConfig(connStr) 170 _ = db.Close() 171 return nil, "", fmt.Errorf("error on pinging the secondary Postgres database [%s]: %v", dbname, err) 172 } 173 174 return db, connStr, nil 175 } 176 177 func (p *Postgres) isSuperUser() bool { return p.superUser != nil && *p.superUser } 178 179 func (p *Postgres) isPGInRecovery() bool { return p.pgIsInRecovery != nil && *p.pgIsInRecovery } 180 181 func (p *Postgres) getDBMetrics(name string) *dbMetrics { 182 db, ok := p.mx.dbs[name] 183 if !ok { 184 db = &dbMetrics{name: name} 185 p.mx.dbs[name] = db 186 } 187 return db 188 } 189 190 func (p *Postgres) getTableMetrics(name, db, schema string) *tableMetrics { 191 key := name + "_" + db + "_" + schema 192 m, ok := p.mx.tables[key] 193 if !ok { 194 m = &tableMetrics{db: db, schema: schema, name: name} 195 p.mx.tables[key] = m 196 } 197 return m 198 } 199 200 func (p *Postgres) hasTableMetrics(name, db, schema string) bool { 201 key := name + "_" + db + "_" + schema 202 _, ok := p.mx.tables[key] 203 return ok 204 } 205 206 func (p *Postgres) getIndexMetrics(name, table, db, schema string) *indexMetrics { 207 key := name + "_" + table + "_" + db + "_" + schema 208 m, ok := p.mx.indexes[key] 209 if !ok { 210 m = &indexMetrics{name: name, db: db, schema: schema, table: table} 211 p.mx.indexes[key] = m 212 } 213 return m 214 } 215 216 func (p *Postgres) hasIndexMetrics(name, table, db, schema string) bool { 217 key := name + "_" + table + "_" + db + "_" + schema 218 _, ok := p.mx.indexes[key] 219 return ok 220 } 221 222 func (p *Postgres) getReplAppMetrics(name string) *replStandbyAppMetrics { 223 app, ok := p.mx.replApps[name] 224 if !ok { 225 app = &replStandbyAppMetrics{name: name} 226 p.mx.replApps[name] = app 227 } 228 return app 229 } 230 231 func (p *Postgres) getReplSlotMetrics(name string) *replSlotMetrics { 232 slot, ok := p.mx.replSlots[name] 233 if !ok { 234 slot = &replSlotMetrics{name: name} 235 p.mx.replSlots[name] = slot 236 } 237 return slot 238 } 239 240 func parseInt(s string) int64 { 241 v, _ := strconv.ParseInt(s, 10, 64) 242 return v 243 } 244 245 func parseFloat(s string) int64 { 246 v, _ := strconv.ParseFloat(s, 64) 247 return int64(v) 248 } 249 250 func newInt(v int64) *int64 { 251 return &v 252 } 253 254 func calcPercentage(value, total int64) (v int64) { 255 if total == 0 { 256 return 0 257 } 258 if v = value * 100 / total; v < 0 { 259 v = -v 260 } 261 return v 262 } 263 264 func calcDeltaPercentage(a, b incDelta) int64 { 265 return calcPercentage(a.delta(), a.delta()+b.delta()) 266 }