| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package postgres |
| 4 | |
| 5 | import "fmt" |
| 6 | |
| 7 | func (c *Collector) collectMetrics(mx map[string]int64) { |
| 8 | mx["server_connections_used"] = c.mx.connUsed |
| 9 | if c.mx.maxConnections > 0 { |
| 10 | mx["server_connections_available"] = c.mx.maxConnections - c.mx.connUsed |
| 11 | mx["server_connections_utilization"] = calcPercentage(c.mx.connUsed, c.mx.maxConnections) |
| 12 | } |
| 13 | c.mx.xactTimeHist.WriteTo(mx, "transaction_running_time_hist", 1, 1) |
| 14 | c.mx.queryTimeHist.WriteTo(mx, "query_running_time_hist", 1, 1) |
| 15 | mx["server_uptime"] = c.mx.uptime |
| 16 | mx["server_connections_state_active"] = c.mx.connStateActive |
| 17 | mx["server_connections_state_idle"] = c.mx.connStateIdle |
| 18 | mx["server_connections_state_idle_in_transaction"] = c.mx.connStateIdleInTrans |
| 19 | mx["server_connections_state_idle_in_transaction_aborted"] = c.mx.connStateIdleInTransAborted |
| 20 | mx["server_connections_state_fastpath_function_call"] = c.mx.connStateFastpathFunctionCall |
| 21 | mx["server_connections_state_disabled"] = c.mx.connStateDisabled |
| 22 | mx["checkpoints_timed"] = c.mx.checkpointsTimed |
| 23 | mx["checkpoints_req"] = c.mx.checkpointsReq |
| 24 | mx["checkpoint_write_time"] = c.mx.checkpointWriteTime |
| 25 | mx["checkpoint_sync_time"] = c.mx.checkpointSyncTime |
| 26 | mx["buffers_checkpoint"] = c.mx.buffersCheckpoint |
| 27 | mx["buffers_clean"] = c.mx.buffersClean |
| 28 | mx["maxwritten_clean"] = c.mx.maxwrittenClean |
| 29 | mx["buffers_backend"] = c.mx.buffersBackend |
| 30 | mx["buffers_backend_fsync"] = c.mx.buffersBackendFsync |
| 31 | mx["buffers_alloc"] = c.mx.buffersAlloc |
| 32 | mx["oldest_current_xid"] = c.mx.oldestXID |
| 33 | mx["percent_towards_wraparound"] = c.mx.percentTowardsWraparound |
| 34 | mx["percent_towards_emergency_autovacuum"] = c.mx.percentTowardsEmergencyAutovacuum |
| 35 | mx["wal_writes"] = c.mx.walWrites |
| 36 | mx["wal_recycled_files"] = c.mx.walRecycledFiles |
| 37 | mx["wal_written_files"] = c.mx.walWrittenFiles |
| 38 | mx["wal_archive_files_ready_count"] = c.mx.walArchiveFilesReady |
| 39 | mx["wal_archive_files_done_count"] = c.mx.walArchiveFilesDone |
| 40 | mx["catalog_relkind_r_count"] = c.mx.relkindOrdinaryTable |
| 41 | mx["catalog_relkind_i_count"] = c.mx.relkindIndex |
| 42 | mx["catalog_relkind_S_count"] = c.mx.relkindSequence |
| 43 | mx["catalog_relkind_t_count"] = c.mx.relkindTOASTTable |
| 44 | mx["catalog_relkind_v_count"] = c.mx.relkindView |
| 45 | mx["catalog_relkind_m_count"] = c.mx.relkindMatView |
| 46 | mx["catalog_relkind_c_count"] = c.mx.relkindCompositeType |
| 47 | mx["catalog_relkind_f_count"] = c.mx.relkindForeignTable |
| 48 | mx["catalog_relkind_p_count"] = c.mx.relkindPartitionedTable |
| 49 | mx["catalog_relkind_I_count"] = c.mx.relkindPartitionedIndex |
| 50 | mx["catalog_relkind_r_size"] = c.mx.relkindOrdinaryTableSize |
| 51 | mx["catalog_relkind_i_size"] = c.mx.relkindIndexSize |
| 52 | mx["catalog_relkind_S_size"] = c.mx.relkindSequenceSize |
| 53 | mx["catalog_relkind_t_size"] = c.mx.relkindTOASTTableSize |
| 54 | mx["catalog_relkind_v_size"] = c.mx.relkindViewSize |
| 55 | mx["catalog_relkind_m_size"] = c.mx.relkindMatViewSize |
| 56 | mx["catalog_relkind_c_size"] = c.mx.relkindCompositeTypeSize |
| 57 | mx["catalog_relkind_f_size"] = c.mx.relkindForeignTableSize |
| 58 | mx["catalog_relkind_p_size"] = c.mx.relkindPartitionedTableSize |
| 59 | mx["catalog_relkind_I_size"] = c.mx.relkindPartitionedIndexSize |
| 60 | mx["autovacuum_analyze"] = c.mx.autovacuumWorkersAnalyze |
| 61 | mx["autovacuum_vacuum_analyze"] = c.mx.autovacuumWorkersVacuumAnalyze |
| 62 | mx["autovacuum_vacuum"] = c.mx.autovacuumWorkersVacuum |
| 63 | mx["autovacuum_vacuum_freeze"] = c.mx.autovacuumWorkersVacuumFreeze |
| 64 | mx["autovacuum_brin_summarize"] = c.mx.autovacuumWorkersBrinSummarize |
| 65 | |
| 66 | var locksHeld int64 |
| 67 | for name, m := range c.mx.dbs { |
| 68 | if !m.updated { |
| 69 | delete(c.mx.dbs, name) |
| 70 | c.removeDatabaseCharts(m) |
| 71 | continue |
| 72 | } |
| 73 | if !m.hasCharts { |
| 74 | m.hasCharts = true |
| 75 | c.addNewDatabaseCharts(m) |
| 76 | if c.isPGInRecovery() { |
| 77 | c.addDBConflictsCharts(m) |
| 78 | } |
| 79 | } |
| 80 | px := "db_" + m.name + "_" |
| 81 | mx[px+"numbackends"] = m.numBackends |
| 82 | if m.datConnLimit <= 0 { |
| 83 | mx[px+"numbackends_utilization"] = calcPercentage(m.numBackends, c.mx.maxConnections) |
| 84 | } else { |
| 85 | mx[px+"numbackends_utilization"] = calcPercentage(m.numBackends, m.datConnLimit) |
| 86 | } |
| 87 | mx[px+"xact_commit"] = m.xactCommit |
| 88 | mx[px+"xact_rollback"] = m.xactRollback |
| 89 | mx[px+"blks_read"] = m.blksRead.last |
| 90 | mx[px+"blks_hit"] = m.blksHit.last |
| 91 | mx[px+"blks_read_perc"] = calcDeltaPercentage(m.blksRead, m.blksHit) |
| 92 | m.blksRead.prev, m.blksHit.prev = m.blksRead.last, m.blksHit.last |
| 93 | mx[px+"tup_returned"] = m.tupReturned.last |
| 94 | mx[px+"tup_fetched"] = m.tupFetched.last |
| 95 | mx[px+"tup_fetched_perc"] = calcPercentage(m.tupFetched.delta(), m.tupReturned.delta()) |
| 96 | m.tupReturned.prev, m.tupFetched.prev = m.tupReturned.last, m.tupFetched.last |
| 97 | mx[px+"tup_inserted"] = m.tupInserted |
| 98 | mx[px+"tup_updated"] = m.tupUpdated |
| 99 | mx[px+"tup_deleted"] = m.tupDeleted |
| 100 | mx[px+"conflicts"] = m.conflicts |
| 101 | if m.size != nil { |
| 102 | mx[px+"size"] = *m.size |
| 103 | } |
| 104 | mx[px+"temp_files"] = m.tempFiles |
| 105 | mx[px+"temp_bytes"] = m.tempBytes |
| 106 | mx[px+"deadlocks"] = m.deadlocks |
| 107 | mx[px+"confl_tablespace"] = m.conflTablespace |
| 108 | mx[px+"confl_lock"] = m.conflLock |
| 109 | mx[px+"confl_snapshot"] = m.conflSnapshot |
| 110 | mx[px+"confl_bufferpin"] = m.conflBufferpin |
| 111 | mx[px+"confl_deadlock"] = m.conflDeadlock |
| 112 | mx[px+"lock_mode_AccessShareLock_held"] = m.accessShareLockHeld |
| 113 | mx[px+"lock_mode_RowShareLock_held"] = m.rowShareLockHeld |
| 114 | mx[px+"lock_mode_RowExclusiveLock_held"] = m.rowExclusiveLockHeld |
| 115 | mx[px+"lock_mode_ShareUpdateExclusiveLock_held"] = m.shareUpdateExclusiveLockHeld |
| 116 | mx[px+"lock_mode_ShareLock_held"] = m.shareLockHeld |
| 117 | mx[px+"lock_mode_ShareRowExclusiveLock_held"] = m.shareRowExclusiveLockHeld |
| 118 | mx[px+"lock_mode_ExclusiveLock_held"] = m.exclusiveLockHeld |
| 119 | mx[px+"lock_mode_AccessExclusiveLock_held"] = m.accessExclusiveLockHeld |
| 120 | mx[px+"lock_mode_AccessShareLock_awaited"] = m.accessShareLockAwaited |
| 121 | mx[px+"lock_mode_RowShareLock_awaited"] = m.rowShareLockAwaited |
| 122 | mx[px+"lock_mode_RowExclusiveLock_awaited"] = m.rowExclusiveLockAwaited |
| 123 | mx[px+"lock_mode_ShareUpdateExclusiveLock_awaited"] = m.shareUpdateExclusiveLockAwaited |
| 124 | mx[px+"lock_mode_ShareLock_awaited"] = m.shareLockAwaited |
| 125 | mx[px+"lock_mode_ShareRowExclusiveLock_awaited"] = m.shareRowExclusiveLockAwaited |
| 126 | mx[px+"lock_mode_ExclusiveLock_awaited"] = m.exclusiveLockAwaited |
| 127 | mx[px+"lock_mode_AccessExclusiveLock_awaited"] = m.accessExclusiveLockAwaited |
| 128 | locksHeld += m.accessShareLockHeld + m.rowShareLockHeld + |
| 129 | m.rowExclusiveLockHeld + m.shareUpdateExclusiveLockHeld + |
| 130 | m.shareLockHeld + m.shareRowExclusiveLockHeld + |
| 131 | m.exclusiveLockHeld + m.accessExclusiveLockHeld |
| 132 | } |
| 133 | mx["databases_count"] = int64(len(c.mx.dbs)) |
| 134 | mx["locks_utilization"] = calcPercentage(locksHeld, c.mx.maxLocksHeld) |
| 135 | |
| 136 | for name, m := range c.mx.tables { |
| 137 | if !m.updated { |
| 138 | delete(c.mx.tables, name) |
| 139 | c.removeTableCharts(m) |
| 140 | continue |
| 141 | } |
| 142 | if !m.hasCharts { |
| 143 | m.hasCharts = true |
| 144 | c.addNewTableCharts(m) |
| 145 | } |
| 146 | if !m.hasLastAutoVacuumChart && m.lastAutoVacuumAgo > 0 { |
| 147 | m.hasLastAutoVacuumChart = true |
| 148 | c.addTableLastAutoVacuumAgoChart(m) |
| 149 | } |
| 150 | if !m.hasLastVacuumChart && m.lastVacuumAgo > 0 { |
| 151 | m.hasLastVacuumChart = true |
| 152 | c.addTableLastVacuumAgoChart(m) |
| 153 | } |
| 154 | if !m.hasLastAutoAnalyzeChart && m.lastAutoAnalyzeAgo > 0 { |
| 155 | m.hasLastAutoAnalyzeChart = true |
| 156 | c.addTableLastAutoAnalyzeAgoChart(m) |
| 157 | } |
| 158 | if !m.hasLastAnalyzeChart && m.lastAnalyzeAgo > 0 { |
| 159 | m.hasLastAnalyzeChart = true |
| 160 | c.addTableLastAnalyzeAgoChart(m) |
| 161 | } |
| 162 | if !m.hasTableIOCharts && m.heapBlksRead.last != -1 { |
| 163 | m.hasTableIOCharts = true |
| 164 | c.addTableIOChartsCharts(m) |
| 165 | } |
| 166 | if !m.hasTableIdxIOCharts && m.idxBlksRead.last != -1 { |
| 167 | m.hasTableIdxIOCharts = true |
| 168 | c.addTableIndexIOCharts(m) |
| 169 | } |
| 170 | if !m.hasTableTOASTIOCharts && m.toastBlksRead.last != -1 { |
| 171 | m.hasTableTOASTIOCharts = true |
| 172 | c.addTableTOASTIOCharts(m) |
| 173 | } |
| 174 | if !m.hasTableTOASTIdxIOCharts && m.tidxBlksRead.last != -1 { |
| 175 | m.hasTableTOASTIdxIOCharts = true |
| 176 | c.addTableTOASTIndexIOCharts(m) |
| 177 | } |
| 178 | |
| 179 | px := fmt.Sprintf("table_%s_db_%s_schema_%s_", m.name, m.db, m.schema) |
| 180 | |
| 181 | mx[px+"seq_scan"] = m.seqScan |
| 182 | mx[px+"seq_tup_read"] = m.seqTupRead |
| 183 | mx[px+"idx_scan"] = m.idxScan |
| 184 | mx[px+"idx_tup_fetch"] = m.idxTupFetch |
| 185 | mx[px+"n_live_tup"] = m.nLiveTup |
| 186 | mx[px+"n_dead_tup"] = m.nDeadTup |
| 187 | mx[px+"n_dead_tup_perc"] = calcPercentage(m.nDeadTup, m.nDeadTup+m.nLiveTup) |
| 188 | mx[px+"n_tup_ins"] = m.nTupIns |
| 189 | mx[px+"n_tup_upd"] = m.nTupUpd.last |
| 190 | mx[px+"n_tup_del"] = m.nTupDel |
| 191 | mx[px+"n_tup_hot_upd"] = m.nTupHotUpd.last |
| 192 | if m.lastAutoVacuumAgo != -1 { |
| 193 | mx[px+"last_autovacuum_ago"] = m.lastAutoVacuumAgo |
| 194 | } |
| 195 | if m.lastVacuumAgo != -1 { |
| 196 | mx[px+"last_vacuum_ago"] = m.lastVacuumAgo |
| 197 | } |
| 198 | if m.lastAutoAnalyzeAgo != -1 { |
| 199 | mx[px+"last_autoanalyze_ago"] = m.lastAutoAnalyzeAgo |
| 200 | } |
| 201 | if m.lastAnalyzeAgo != -1 { |
| 202 | mx[px+"last_analyze_ago"] = m.lastAnalyzeAgo |
| 203 | } |
| 204 | mx[px+"total_size"] = m.totalSize |
| 205 | if m.bloatSize != nil && m.bloatSizePerc != nil { |
| 206 | mx[px+"bloat_size"] = *m.bloatSize |
| 207 | mx[px+"bloat_size_perc"] = *m.bloatSizePerc |
| 208 | } |
| 209 | if m.nullColumns != nil { |
| 210 | mx[px+"null_columns"] = *m.nullColumns |
| 211 | } |
| 212 | |
| 213 | mx[px+"n_tup_hot_upd_perc"] = calcPercentage(m.nTupHotUpd.delta(), m.nTupUpd.delta()) |
| 214 | m.nTupHotUpd.prev, m.nTupUpd.prev = m.nTupHotUpd.last, m.nTupUpd.last |
| 215 | |
| 216 | mx[px+"heap_blks_read"] = m.heapBlksRead.last |
| 217 | mx[px+"heap_blks_hit"] = m.heapBlksHit.last |
| 218 | mx[px+"heap_blks_read_perc"] = calcDeltaPercentage(m.heapBlksRead, m.heapBlksHit) |
| 219 | m.heapBlksHit.prev, m.heapBlksRead.prev = m.heapBlksHit.last, m.heapBlksRead.last |
| 220 | |
| 221 | mx[px+"idx_blks_read"] = m.idxBlksRead.last |
| 222 | mx[px+"idx_blks_hit"] = m.idxBlksHit.last |
| 223 | mx[px+"idx_blks_read_perc"] = calcDeltaPercentage(m.idxBlksRead, m.idxBlksHit) |
| 224 | m.idxBlksHit.prev, m.idxBlksRead.prev = m.idxBlksHit.last, m.idxBlksRead.last |
| 225 | |
| 226 | mx[px+"toast_blks_read"] = m.toastBlksRead.last |
| 227 | mx[px+"toast_blks_hit"] = m.toastBlksHit.last |
| 228 | mx[px+"toast_blks_read_perc"] = calcDeltaPercentage(m.toastBlksRead, m.toastBlksHit) |
| 229 | m.toastBlksHit.prev, m.toastBlksRead.prev = m.toastBlksHit.last, m.toastBlksRead.last |
| 230 | |
| 231 | mx[px+"tidx_blks_read"] = m.tidxBlksRead.last |
| 232 | mx[px+"tidx_blks_hit"] = m.tidxBlksHit.last |
| 233 | mx[px+"tidx_blks_read_perc"] = calcDeltaPercentage(m.tidxBlksRead, m.tidxBlksHit) |
| 234 | m.tidxBlksHit.prev, m.tidxBlksRead.prev = m.tidxBlksHit.last, m.tidxBlksRead.last |
| 235 | } |
| 236 | |
| 237 | for name, m := range c.mx.indexes { |
| 238 | if !m.updated { |
| 239 | delete(c.mx.indexes, name) |
| 240 | c.removeIndexCharts(m) |
| 241 | continue |
| 242 | } |
| 243 | if !m.hasCharts { |
| 244 | m.hasCharts = true |
| 245 | c.addNewIndexCharts(m) |
| 246 | } |
| 247 | |
| 248 | px := fmt.Sprintf("index_%s_table_%s_db_%s_schema_%s_", m.name, m.table, m.db, m.schema) |
| 249 | mx[px+"size"] = m.size |
| 250 | if m.bloatSize != nil && m.bloatSizePerc != nil { |
| 251 | mx[px+"bloat_size"] = *m.bloatSize |
| 252 | mx[px+"bloat_size_perc"] = *m.bloatSizePerc |
| 253 | } |
| 254 | if m.idxScan+m.idxTupRead+m.idxTupFetch > 0 { |
| 255 | mx[px+"usage_status_used"], mx[px+"usage_status_unused"] = 1, 0 |
| 256 | } else { |
| 257 | mx[px+"usage_status_used"], mx[px+"usage_status_unused"] = 0, 1 |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | for name, m := range c.mx.replApps { |
| 262 | if !m.updated { |
| 263 | delete(c.mx.replApps, name) |
| 264 | c.removeReplicationStandbyAppCharts(name) |
| 265 | continue |
| 266 | } |
| 267 | if !m.hasCharts { |
| 268 | m.hasCharts = true |
| 269 | c.addNewReplicationStandbyAppCharts(name) |
| 270 | } |
| 271 | px := "repl_standby_app_" + m.name + "_wal_" |
| 272 | mx[px+"sent_lag_size"] = m.walSentDelta |
| 273 | mx[px+"write_lag_size"] = m.walWriteDelta |
| 274 | mx[px+"flush_lag_size"] = m.walFlushDelta |
| 275 | mx[px+"replay_lag_size"] = m.walReplayDelta |
| 276 | mx[px+"write_time"] = m.walWriteLag |
| 277 | mx[px+"flush_lag_time"] = m.walFlushLag |
| 278 | mx[px+"replay_lag_time"] = m.walReplayLag |
| 279 | } |
| 280 | |
| 281 | for name, m := range c.mx.replSlots { |
| 282 | if !m.updated { |
| 283 | delete(c.mx.replSlots, name) |
| 284 | c.removeReplicationSlotCharts(name) |
| 285 | continue |
| 286 | } |
| 287 | if !m.hasCharts { |
| 288 | m.hasCharts = true |
| 289 | c.addNewReplicationSlotCharts(name) |
| 290 | } |
| 291 | px := "repl_slot_" + m.name + "_" |
| 292 | mx[px+"replslot_wal_keep"] = m.walKeep |
| 293 | mx[px+"replslot_files"] = m.files |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | func (c *Collector) resetMetrics() { |
| 298 | c.mx.srvMetrics = srvMetrics{ |
| 299 | xactTimeHist: c.mx.xactTimeHist, |
| 300 | queryTimeHist: c.mx.queryTimeHist, |
| 301 | maxConnections: c.mx.maxConnections, |
| 302 | maxLocksHeld: c.mx.maxLocksHeld, |
| 303 | } |
| 304 | for name, m := range c.mx.dbs { |
| 305 | c.mx.dbs[name] = &dbMetrics{ |
| 306 | name: m.name, |
| 307 | hasCharts: m.hasCharts, |
| 308 | blksRead: incDelta{prev: m.blksRead.prev}, |
| 309 | blksHit: incDelta{prev: m.blksHit.prev}, |
| 310 | tupReturned: incDelta{prev: m.tupReturned.prev}, |
| 311 | tupFetched: incDelta{prev: m.tupFetched.prev}, |
| 312 | } |
| 313 | } |
| 314 | for name, m := range c.mx.tables { |
| 315 | c.mx.tables[name] = &tableMetrics{ |
| 316 | db: m.db, |
| 317 | schema: m.schema, |
| 318 | name: m.name, |
| 319 | hasCharts: m.hasCharts, |
| 320 | hasLastAutoVacuumChart: m.hasLastAutoVacuumChart, |
| 321 | hasLastVacuumChart: m.hasLastVacuumChart, |
| 322 | hasLastAutoAnalyzeChart: m.hasLastAutoAnalyzeChart, |
| 323 | hasLastAnalyzeChart: m.hasLastAnalyzeChart, |
| 324 | hasTableIOCharts: m.hasTableIOCharts, |
| 325 | hasTableIdxIOCharts: m.hasTableIdxIOCharts, |
| 326 | hasTableTOASTIOCharts: m.hasTableTOASTIOCharts, |
| 327 | hasTableTOASTIdxIOCharts: m.hasTableTOASTIdxIOCharts, |
| 328 | nTupUpd: incDelta{prev: m.nTupUpd.prev}, |
| 329 | nTupHotUpd: incDelta{prev: m.nTupHotUpd.prev}, |
| 330 | heapBlksRead: incDelta{prev: m.heapBlksRead.prev}, |
| 331 | heapBlksHit: incDelta{prev: m.heapBlksHit.prev}, |
| 332 | idxBlksRead: incDelta{prev: m.idxBlksRead.prev}, |
| 333 | idxBlksHit: incDelta{prev: m.idxBlksHit.prev}, |
| 334 | toastBlksRead: incDelta{prev: m.toastBlksRead.prev}, |
| 335 | toastBlksHit: incDelta{prev: m.toastBlksHit.prev}, |
| 336 | tidxBlksRead: incDelta{prev: m.tidxBlksRead.prev}, |
| 337 | tidxBlksHit: incDelta{prev: m.tidxBlksHit.prev}, |
| 338 | bloatSize: m.bloatSize, |
| 339 | bloatSizePerc: m.bloatSizePerc, |
| 340 | nullColumns: m.nullColumns, |
| 341 | } |
| 342 | } |
| 343 | for name, m := range c.mx.indexes { |
| 344 | c.mx.indexes[name] = &indexMetrics{ |
| 345 | name: m.name, |
| 346 | db: m.db, |
| 347 | schema: m.schema, |
| 348 | table: m.table, |
| 349 | updated: m.updated, |
| 350 | hasCharts: m.hasCharts, |
| 351 | bloatSize: m.bloatSize, |
| 352 | bloatSizePerc: m.bloatSizePerc, |
| 353 | } |
| 354 | } |
| 355 | for name, m := range c.mx.replApps { |
| 356 | c.mx.replApps[name] = &replStandbyAppMetrics{ |
| 357 | name: m.name, |
| 358 | hasCharts: m.hasCharts, |
| 359 | } |
| 360 | } |
| 361 | for name, m := range c.mx.replSlots { |
| 362 | c.mx.replSlots[name] = &replSlotMetrics{ |
| 363 | name: m.name, |
| 364 | hasCharts: m.hasCharts, |
| 365 | } |
| 366 | } |
| 367 | } |