| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package postgres |
| 4 | |
| 5 | import ( |
| 6 | "bufio" |
| 7 | "bytes" |
| 8 | "context" |
| 9 | "database/sql/driver" |
| 10 | "errors" |
| 11 | "fmt" |
| 12 | "os" |
| 13 | "strings" |
| 14 | "testing" |
| 15 | |
| 16 | "github.com/netdata/netdata/go/plugins/pkg/matcher" |
| 17 | "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/cloudauth" |
| 18 | "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest" |
| 19 | |
| 20 | "github.com/DATA-DOG/go-sqlmock" |
| 21 | "github.com/stretchr/testify/assert" |
| 22 | "github.com/stretchr/testify/require" |
| 23 | ) |
| 24 | |
| 25 | var ( |
| 26 | dataConfigJSON, _ = os.ReadFile("testdata/config.json") |
| 27 | dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") |
| 28 | |
| 29 | dataVer140004ServerVersionNum, _ = os.ReadFile("testdata/v14.4/server_version_num.txt") |
| 30 | dataVer140004IsSuperUserFalse, _ = os.ReadFile("testdata/v14.4/is_super_user-false.txt") |
| 31 | dataVer140004IsSuperUserTrue, _ = os.ReadFile("testdata/v14.4/is_super_user-true.txt") |
| 32 | dataVer140004PGIsInRecoveryTrue, _ = os.ReadFile("testdata/v14.4/pg_is_in_recovery-true.txt") |
| 33 | dataVer140004SettingsMaxConnections, _ = os.ReadFile("testdata/v14.4/settings_max_connections.txt") |
| 34 | dataVer140004SettingsMaxLocksHeld, _ = os.ReadFile("testdata/v14.4/settings_max_locks_held.txt") |
| 35 | dataVer140004ServerCurrentConnections, _ = os.ReadFile("testdata/v14.4/server_current_connections.txt") |
| 36 | dataVer140004ServerConnectionsState, _ = os.ReadFile("testdata/v14.4/server_connections_state.txt") |
| 37 | dataVer140004Checkpoints, _ = os.ReadFile("testdata/v14.4/checkpoints.txt") |
| 38 | dataVer140004ServerUptime, _ = os.ReadFile("testdata/v14.4/uptime.txt") |
| 39 | dataVer140004TXIDWraparound, _ = os.ReadFile("testdata/v14.4/txid_wraparound.txt") |
| 40 | dataVer140004WALWrites, _ = os.ReadFile("testdata/v14.4/wal_writes.txt") |
| 41 | dataVer140004WALFiles, _ = os.ReadFile("testdata/v14.4/wal_files.txt") |
| 42 | dataVer140004WALArchiveFiles, _ = os.ReadFile("testdata/v14.4/wal_archive_files.txt") |
| 43 | dataVer140004CatalogRelations, _ = os.ReadFile("testdata/v14.4/catalog_relations.txt") |
| 44 | dataVer140004AutovacuumWorkers, _ = os.ReadFile("testdata/v14.4/autovacuum_workers.txt") |
| 45 | dataVer140004XactQueryRunningTime, _ = os.ReadFile("testdata/v14.4/xact_query_running_time.txt") |
| 46 | dataVer140004ReplStandbyAppDelta, _ = os.ReadFile("testdata/v14.4/replication_standby_app_wal_delta.txt") |
| 47 | dataVer140004ReplStandbyAppLag, _ = os.ReadFile("testdata/v14.4/replication_standby_app_wal_lag.txt") |
| 48 | dataVer140004ReplSlotFiles, _ = os.ReadFile("testdata/v14.4/replication_slot_files.txt") |
| 49 | dataVer140004DatabaseStats, _ = os.ReadFile("testdata/v14.4/database_stats.txt") |
| 50 | dataVer140004DatabaseSize, _ = os.ReadFile("testdata/v14.4/database_size.txt") |
| 51 | dataVer140004DatabaseConflicts, _ = os.ReadFile("testdata/v14.4/database_conflicts.txt") |
| 52 | dataVer140004DatabaseLocks, _ = os.ReadFile("testdata/v14.4/database_locks.txt") |
| 53 | dataVer140004QueryableDatabaseList, _ = os.ReadFile("testdata/v14.4/queryable_database_list.txt") |
| 54 | dataVer140004StatUserTablesDBPostgres, _ = os.ReadFile("testdata/v14.4/stat_user_tables_db_postgres.txt") |
| 55 | dataVer140004StatIOUserTablesDBPostgres, _ = os.ReadFile("testdata/v14.4/statio_user_tables_db_postgres.txt") |
| 56 | dataVer140004StatUserIndexesDBPostgres, _ = os.ReadFile("testdata/v14.4/stat_user_indexes_db_postgres.txt") |
| 57 | dataVer140004Bloat, _ = os.ReadFile("testdata/v14.4/bloat_tables.txt") |
| 58 | dataVer140004ColumnsStats, _ = os.ReadFile("testdata/v14.4/table_columns_stats.txt") |
| 59 | ) |
| 60 | |
| 61 | func Test_testDataIsValid(t *testing.T) { |
| 62 | for name, data := range map[string][]byte{ |
| 63 | "dataConfigJSON": dataConfigJSON, |
| 64 | "dataConfigYAML": dataConfigYAML, |
| 65 | "dataVer140004ServerVersionNum": dataVer140004ServerVersionNum, |
| 66 | "dataVer140004IsSuperUserFalse": dataVer140004IsSuperUserFalse, |
| 67 | "dataVer140004IsSuperUserTrue": dataVer140004IsSuperUserTrue, |
| 68 | "dataVer140004PGIsInRecoveryTrue": dataVer140004PGIsInRecoveryTrue, |
| 69 | "dataVer140004SettingsMaxConnections": dataVer140004SettingsMaxConnections, |
| 70 | "dataVer140004SettingsMaxLocksHeld": dataVer140004SettingsMaxLocksHeld, |
| 71 | "dataVer140004ServerCurrentConnections": dataVer140004ServerCurrentConnections, |
| 72 | "dataVer140004ServerConnectionsState": dataVer140004ServerConnectionsState, |
| 73 | "dataVer140004Checkpoints": dataVer140004Checkpoints, |
| 74 | "dataVer140004ServerUptime": dataVer140004ServerUptime, |
| 75 | "dataVer140004TXIDWraparound": dataVer140004TXIDWraparound, |
| 76 | "dataVer140004WALWrites": dataVer140004WALWrites, |
| 77 | "dataVer140004WALFiles": dataVer140004WALFiles, |
| 78 | "dataVer140004WALArchiveFiles": dataVer140004WALArchiveFiles, |
| 79 | "dataVer140004CatalogRelations": dataVer140004CatalogRelations, |
| 80 | "dataVer140004AutovacuumWorkers": dataVer140004AutovacuumWorkers, |
| 81 | "dataVer140004XactQueryRunningTime": dataVer140004XactQueryRunningTime, |
| 82 | "dataV14004ReplStandbyAppDelta": dataVer140004ReplStandbyAppDelta, |
| 83 | "dataV14004ReplStandbyAppLag": dataVer140004ReplStandbyAppLag, |
| 84 | "dataVer140004ReplSlotFiles": dataVer140004ReplSlotFiles, |
| 85 | "dataVer140004DatabaseStats": dataVer140004DatabaseStats, |
| 86 | "dataVer140004DatabaseSize": dataVer140004DatabaseSize, |
| 87 | "dataVer140004DatabaseConflicts": dataVer140004DatabaseConflicts, |
| 88 | "dataVer140004DatabaseLocks": dataVer140004DatabaseLocks, |
| 89 | "dataVer140004QueryableDatabaseList": dataVer140004QueryableDatabaseList, |
| 90 | "dataVer140004StatUserTablesDBPostgres": dataVer140004StatUserTablesDBPostgres, |
| 91 | "dataVer140004StatIOUserTablesDBPostgres": dataVer140004StatIOUserTablesDBPostgres, |
| 92 | "dataVer140004StatUserIndexesDBPostgres": dataVer140004StatUserIndexesDBPostgres, |
| 93 | "dataVer140004Bloat": dataVer140004Bloat, |
| 94 | "dataVer140004ColumnsStats": dataVer140004ColumnsStats, |
| 95 | } { |
| 96 | require.NotNil(t, data, name) |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | func TestCollector_ConfigurationSerialize(t *testing.T) { |
| 101 | collecttest.TestConfigurationSerialize(t, &Collector{}, dataConfigJSON, dataConfigYAML) |
| 102 | } |
| 103 | |
| 104 | func TestCollector_Init(t *testing.T) { |
| 105 | tests := map[string]struct { |
| 106 | wantFail bool |
| 107 | config Config |
| 108 | }{ |
| 109 | "Success with default": { |
| 110 | wantFail: false, |
| 111 | config: New().Config, |
| 112 | }, |
| 113 | "Fail when DSN not set": { |
| 114 | wantFail: true, |
| 115 | config: Config{DSN: ""}, |
| 116 | }, |
| 117 | "Fail on invalid Azure AD configuration": { |
| 118 | wantFail: true, |
| 119 | config: Config{ |
| 120 | DSN: "postgresql://netdata@127.0.0.1:5432/postgres", |
| 121 | CloudAuth: cloudauth.Config{ |
| 122 | Provider: cloudauth.ProviderAzureAD, |
| 123 | AzureAD: &cloudauth.AzureADAuthConfig{ |
| 124 | Mode: "service_principal", |
| 125 | ModeServicePrincipal: &cloudauth.AzureADModeServicePrincipalConfig{ |
| 126 | TenantID: "tenant-id", |
| 127 | ClientID: "client-id", |
| 128 | // Missing client_secret. |
| 129 | }, |
| 130 | }, |
| 131 | }, |
| 132 | }, |
| 133 | }, |
| 134 | } |
| 135 | |
| 136 | for name, test := range tests { |
| 137 | t.Run(name, func(t *testing.T) { |
| 138 | collr := New() |
| 139 | collr.Config = test.config |
| 140 | |
| 141 | if test.wantFail { |
| 142 | assert.Error(t, collr.Init(context.Background())) |
| 143 | } else { |
| 144 | assert.NoError(t, collr.Init(context.Background())) |
| 145 | } |
| 146 | }) |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | func TestCollector_Init_AzureADInitializesTokenProvider(t *testing.T) { |
| 151 | c := New() |
| 152 | c.CloudAuth = cloudauth.Config{ |
| 153 | Provider: cloudauth.ProviderAzureAD, |
| 154 | AzureAD: &cloudauth.AzureADAuthConfig{ |
| 155 | Mode: cloudauth.AzureADAuthModeServicePrincipal, |
| 156 | ModeServicePrincipal: &cloudauth.AzureADModeServicePrincipalConfig{ |
| 157 | TenantID: "tenant-id", |
| 158 | ClientID: "client-id", |
| 159 | ClientSecret: "client-secret", |
| 160 | }, |
| 161 | }, |
| 162 | } |
| 163 | |
| 164 | require.NoError(t, c.Init(context.Background())) |
| 165 | assert.NotNil(t, c.azureTokenProvider) |
| 166 | } |
| 167 | |
| 168 | func TestCollector_Cleanup(t *testing.T) { |
| 169 | |
| 170 | } |
| 171 | |
| 172 | func TestCollector_Charts(t *testing.T) { |
| 173 | assert.NotNil(t, New().Charts()) |
| 174 | } |
| 175 | |
| 176 | func TestCollector_Check(t *testing.T) { |
| 177 | tests := map[string]struct { |
| 178 | prepareMock func(*testing.T, *Collector, sqlmock.Sqlmock) |
| 179 | wantFail bool |
| 180 | }{ |
| 181 | "Success when all queries are successful (v14.4)": { |
| 182 | wantFail: false, |
| 183 | prepareMock: func(t *testing.T, collr *Collector, m sqlmock.Sqlmock) { |
| 184 | collr.dbSr = matcher.TRUE() |
| 185 | |
| 186 | mockExpect(t, m, queryServerVersion(), dataVer140004ServerVersionNum) |
| 187 | mockExpect(t, m, queryIsSuperUser(), dataVer140004IsSuperUserTrue) |
| 188 | mockExpect(t, m, queryCanExecutePgLsDir(), dataVer140004IsSuperUserTrue) |
| 189 | mockExpect(t, m, queryPGIsInRecovery(), dataVer140004PGIsInRecoveryTrue) |
| 190 | |
| 191 | mockExpect(t, m, querySettingsMaxConnections(), dataVer140004SettingsMaxConnections) |
| 192 | mockExpect(t, m, querySettingsMaxLocksHeld(), dataVer140004SettingsMaxLocksHeld) |
| 193 | |
| 194 | mockExpect(t, m, queryServerCurrentConnectionsUsed(), dataVer140004ServerCurrentConnections) |
| 195 | mockExpect(t, m, queryServerConnectionsState(), dataVer140004ServerConnectionsState) |
| 196 | mockExpect(t, m, queryCheckpoints(140004), dataVer140004Checkpoints) |
| 197 | mockExpect(t, m, queryServerUptime(), dataVer140004ServerUptime) |
| 198 | mockExpect(t, m, queryTXIDWraparound(), dataVer140004TXIDWraparound) |
| 199 | mockExpect(t, m, queryWALWrites(140004), dataVer140004WALWrites) |
| 200 | mockExpect(t, m, queryCatalogRelations(), dataVer140004CatalogRelations) |
| 201 | mockExpect(t, m, queryAutovacuumWorkers(), dataVer140004AutovacuumWorkers) |
| 202 | mockExpect(t, m, queryXactQueryRunningTime(), dataVer140004XactQueryRunningTime) |
| 203 | |
| 204 | mockExpect(t, m, queryWALFiles(140004), dataVer140004WALFiles) |
| 205 | mockExpect(t, m, queryWALArchiveFiles(140004), dataVer140004WALArchiveFiles) |
| 206 | |
| 207 | mockExpect(t, m, queryReplicationStandbyAppDelta(140004), dataVer140004ReplStandbyAppDelta) |
| 208 | mockExpect(t, m, queryReplicationStandbyAppLag(), dataVer140004ReplStandbyAppLag) |
| 209 | mockExpect(t, m, queryReplicationSlotFiles(140004), dataVer140004ReplSlotFiles) |
| 210 | |
| 211 | mockExpect(t, m, queryDatabaseStats(), dataVer140004DatabaseStats) |
| 212 | mockExpect(t, m, queryDatabaseSize(140004), dataVer140004DatabaseSize) |
| 213 | mockExpect(t, m, queryDatabaseConflicts(), dataVer140004DatabaseConflicts) |
| 214 | mockExpect(t, m, queryDatabaseLocks(), dataVer140004DatabaseLocks) |
| 215 | |
| 216 | mockExpect(t, m, queryQueryableDatabaseList(), dataVer140004QueryableDatabaseList) |
| 217 | mockExpect(t, m, queryStatUserTables(), dataVer140004StatUserTablesDBPostgres) |
| 218 | mockExpect(t, m, queryStatIOUserTables(), dataVer140004StatIOUserTablesDBPostgres) |
| 219 | mockExpect(t, m, queryStatUserIndexes(), dataVer140004StatUserIndexesDBPostgres) |
| 220 | mockExpect(t, m, queryBloat(), dataVer140004Bloat) |
| 221 | mockExpect(t, m, queryColumnsStats(), dataVer140004ColumnsStats) |
| 222 | }, |
| 223 | }, |
| 224 | "Fail when the second query unsuccessful (v14.4)": { |
| 225 | wantFail: true, |
| 226 | prepareMock: func(t *testing.T, collr *Collector, m sqlmock.Sqlmock) { |
| 227 | mockExpect(t, m, queryServerVersion(), dataVer140004ServerVersionNum) |
| 228 | mockExpect(t, m, queryIsSuperUser(), dataVer140004IsSuperUserTrue) |
| 229 | mockExpect(t, m, queryCanExecutePgLsDir(), dataVer140004IsSuperUserTrue) |
| 230 | mockExpect(t, m, queryPGIsInRecovery(), dataVer140004PGIsInRecoveryTrue) |
| 231 | |
| 232 | mockExpect(t, m, querySettingsMaxConnections(), dataVer140004ServerVersionNum) |
| 233 | mockExpect(t, m, querySettingsMaxLocksHeld(), dataVer140004SettingsMaxLocksHeld) |
| 234 | |
| 235 | mockExpect(t, m, queryServerCurrentConnectionsUsed(), dataVer140004ServerCurrentConnections) |
| 236 | mockExpectErr(m, queryServerConnectionsState()) |
| 237 | }, |
| 238 | }, |
| 239 | "Fail when querying the database version returns an error": { |
| 240 | wantFail: true, |
| 241 | prepareMock: func(t *testing.T, collr *Collector, m sqlmock.Sqlmock) { |
| 242 | mockExpectErr(m, queryServerVersion()) |
| 243 | }, |
| 244 | }, |
| 245 | "Fail when querying settings max connection returns an error": { |
| 246 | wantFail: true, |
| 247 | prepareMock: func(t *testing.T, collr *Collector, m sqlmock.Sqlmock) { |
| 248 | mockExpect(t, m, queryServerVersion(), dataVer140004ServerVersionNum) |
| 249 | mockExpect(t, m, queryIsSuperUser(), dataVer140004IsSuperUserTrue) |
| 250 | mockExpect(t, m, queryCanExecutePgLsDir(), dataVer140004IsSuperUserTrue) |
| 251 | mockExpect(t, m, queryPGIsInRecovery(), dataVer140004PGIsInRecoveryTrue) |
| 252 | |
| 253 | mockExpectErr(m, querySettingsMaxConnections()) |
| 254 | }, |
| 255 | }, |
| 256 | } |
| 257 | |
| 258 | for name, test := range tests { |
| 259 | t.Run(name, func(t *testing.T) { |
| 260 | db, mock, err := sqlmock.New( |
| 261 | sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual), |
| 262 | ) |
| 263 | require.NoError(t, err) |
| 264 | |
| 265 | collr := New() |
| 266 | collr.db = db |
| 267 | defer func() { _ = db.Close() }() |
| 268 | |
| 269 | require.NoError(t, collr.Init(context.Background())) |
| 270 | |
| 271 | test.prepareMock(t, collr, mock) |
| 272 | |
| 273 | if test.wantFail { |
| 274 | assert.Error(t, collr.Check(context.Background())) |
| 275 | } else { |
| 276 | assert.NoError(t, collr.Check(context.Background())) |
| 277 | } |
| 278 | assert.NoError(t, mock.ExpectationsWereMet()) |
| 279 | }) |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | func TestCollector_Collect(t *testing.T) { |
| 284 | type testCaseStep struct { |
| 285 | prepareMock func(*testing.T, *Collector, sqlmock.Sqlmock) |
| 286 | check func(*testing.T, *Collector) |
| 287 | } |
| 288 | tests := map[string][]testCaseStep{ |
| 289 | "Success on all queries, collect all dbs (v14.4)": { |
| 290 | { |
| 291 | prepareMock: func(t *testing.T, collr *Collector, m sqlmock.Sqlmock) { |
| 292 | collr.dbSr = matcher.TRUE() |
| 293 | mockExpect(t, m, queryServerVersion(), dataVer140004ServerVersionNum) |
| 294 | mockExpect(t, m, queryIsSuperUser(), dataVer140004IsSuperUserTrue) |
| 295 | mockExpect(t, m, queryCanExecutePgLsDir(), dataVer140004IsSuperUserTrue) |
| 296 | mockExpect(t, m, queryPGIsInRecovery(), dataVer140004PGIsInRecoveryTrue) |
| 297 | |
| 298 | mockExpect(t, m, querySettingsMaxConnections(), dataVer140004SettingsMaxConnections) |
| 299 | mockExpect(t, m, querySettingsMaxLocksHeld(), dataVer140004SettingsMaxLocksHeld) |
| 300 | |
| 301 | mockExpect(t, m, queryServerCurrentConnectionsUsed(), dataVer140004ServerCurrentConnections) |
| 302 | mockExpect(t, m, queryServerConnectionsState(), dataVer140004ServerConnectionsState) |
| 303 | mockExpect(t, m, queryCheckpoints(140004), dataVer140004Checkpoints) |
| 304 | mockExpect(t, m, queryServerUptime(), dataVer140004ServerUptime) |
| 305 | mockExpect(t, m, queryTXIDWraparound(), dataVer140004TXIDWraparound) |
| 306 | mockExpect(t, m, queryWALWrites(140004), dataVer140004WALWrites) |
| 307 | mockExpect(t, m, queryCatalogRelations(), dataVer140004CatalogRelations) |
| 308 | mockExpect(t, m, queryAutovacuumWorkers(), dataVer140004AutovacuumWorkers) |
| 309 | mockExpect(t, m, queryXactQueryRunningTime(), dataVer140004XactQueryRunningTime) |
| 310 | |
| 311 | mockExpect(t, m, queryWALFiles(140004), dataVer140004WALFiles) |
| 312 | mockExpect(t, m, queryWALArchiveFiles(140004), dataVer140004WALArchiveFiles) |
| 313 | |
| 314 | mockExpect(t, m, queryReplicationStandbyAppDelta(140004), dataVer140004ReplStandbyAppDelta) |
| 315 | mockExpect(t, m, queryReplicationStandbyAppLag(), dataVer140004ReplStandbyAppLag) |
| 316 | mockExpect(t, m, queryReplicationSlotFiles(140004), dataVer140004ReplSlotFiles) |
| 317 | |
| 318 | mockExpect(t, m, queryDatabaseStats(), dataVer140004DatabaseStats) |
| 319 | mockExpect(t, m, queryDatabaseSize(140004), dataVer140004DatabaseSize) |
| 320 | mockExpect(t, m, queryDatabaseConflicts(), dataVer140004DatabaseConflicts) |
| 321 | mockExpect(t, m, queryDatabaseLocks(), dataVer140004DatabaseLocks) |
| 322 | |
| 323 | mockExpect(t, m, queryQueryableDatabaseList(), dataVer140004QueryableDatabaseList) |
| 324 | mockExpect(t, m, queryStatUserTables(), dataVer140004StatUserTablesDBPostgres) |
| 325 | mockExpect(t, m, queryStatIOUserTables(), dataVer140004StatIOUserTablesDBPostgres) |
| 326 | mockExpect(t, m, queryStatUserIndexes(), dataVer140004StatUserIndexesDBPostgres) |
| 327 | mockExpect(t, m, queryBloat(), dataVer140004Bloat) |
| 328 | mockExpect(t, m, queryColumnsStats(), dataVer140004ColumnsStats) |
| 329 | }, |
| 330 | check: func(t *testing.T, collr *Collector) { |
| 331 | mx := collr.Collect(context.Background()) |
| 332 | |
| 333 | expected := map[string]int64{ |
| 334 | "autovacuum_analyze": 0, |
| 335 | "autovacuum_brin_summarize": 0, |
| 336 | "autovacuum_vacuum": 0, |
| 337 | "autovacuum_vacuum_analyze": 0, |
| 338 | "autovacuum_vacuum_freeze": 0, |
| 339 | "buffers_alloc": 27295744, |
| 340 | "buffers_backend": 0, |
| 341 | "buffers_backend_fsync": 0, |
| 342 | "buffers_checkpoint": 32768, |
| 343 | "buffers_clean": 0, |
| 344 | "catalog_relkind_I_count": 0, |
| 345 | "catalog_relkind_I_size": 0, |
| 346 | "catalog_relkind_S_count": 0, |
| 347 | "catalog_relkind_S_size": 0, |
| 348 | "catalog_relkind_c_count": 0, |
| 349 | "catalog_relkind_c_size": 0, |
| 350 | "catalog_relkind_f_count": 0, |
| 351 | "catalog_relkind_f_size": 0, |
| 352 | "catalog_relkind_i_count": 155, |
| 353 | "catalog_relkind_i_size": 3678208, |
| 354 | "catalog_relkind_m_count": 0, |
| 355 | "catalog_relkind_m_size": 0, |
| 356 | "catalog_relkind_p_count": 0, |
| 357 | "catalog_relkind_p_size": 0, |
| 358 | "catalog_relkind_r_count": 66, |
| 359 | "catalog_relkind_r_size": 3424256, |
| 360 | "catalog_relkind_t_count": 38, |
| 361 | "catalog_relkind_t_size": 548864, |
| 362 | "catalog_relkind_v_count": 137, |
| 363 | "catalog_relkind_v_size": 0, |
| 364 | "checkpoint_sync_time": 47, |
| 365 | "checkpoint_write_time": 167, |
| 366 | "checkpoints_req": 16, |
| 367 | "checkpoints_timed": 1814, |
| 368 | "databases_count": 2, |
| 369 | "db_postgres_blks_hit": 1221125, |
| 370 | "db_postgres_blks_read": 3252, |
| 371 | "db_postgres_blks_read_perc": 0, |
| 372 | "db_postgres_confl_bufferpin": 0, |
| 373 | "db_postgres_confl_deadlock": 0, |
| 374 | "db_postgres_confl_lock": 0, |
| 375 | "db_postgres_confl_snapshot": 0, |
| 376 | "db_postgres_confl_tablespace": 0, |
| 377 | "db_postgres_conflicts": 0, |
| 378 | "db_postgres_deadlocks": 0, |
| 379 | "db_postgres_lock_mode_AccessExclusiveLock_awaited": 0, |
| 380 | "db_postgres_lock_mode_AccessExclusiveLock_held": 0, |
| 381 | "db_postgres_lock_mode_AccessShareLock_awaited": 0, |
| 382 | "db_postgres_lock_mode_AccessShareLock_held": 99, |
| 383 | "db_postgres_lock_mode_ExclusiveLock_awaited": 0, |
| 384 | "db_postgres_lock_mode_ExclusiveLock_held": 0, |
| 385 | "db_postgres_lock_mode_RowExclusiveLock_awaited": 0, |
| 386 | "db_postgres_lock_mode_RowExclusiveLock_held": 99, |
| 387 | "db_postgres_lock_mode_RowShareLock_awaited": 0, |
| 388 | "db_postgres_lock_mode_RowShareLock_held": 99, |
| 389 | "db_postgres_lock_mode_ShareLock_awaited": 0, |
| 390 | "db_postgres_lock_mode_ShareLock_held": 0, |
| 391 | "db_postgres_lock_mode_ShareRowExclusiveLock_awaited": 0, |
| 392 | "db_postgres_lock_mode_ShareRowExclusiveLock_held": 0, |
| 393 | "db_postgres_lock_mode_ShareUpdateExclusiveLock_awaited": 0, |
| 394 | "db_postgres_lock_mode_ShareUpdateExclusiveLock_held": 0, |
| 395 | "db_postgres_numbackends": 3, |
| 396 | "db_postgres_numbackends_utilization": 10, |
| 397 | "db_postgres_size": 8758051, |
| 398 | "db_postgres_temp_bytes": 0, |
| 399 | "db_postgres_temp_files": 0, |
| 400 | "db_postgres_tup_deleted": 0, |
| 401 | "db_postgres_tup_fetched": 359833, |
| 402 | "db_postgres_tup_fetched_perc": 2, |
| 403 | "db_postgres_tup_inserted": 0, |
| 404 | "db_postgres_tup_returned": 13207245, |
| 405 | "db_postgres_tup_updated": 0, |
| 406 | "db_postgres_xact_commit": 1438660, |
| 407 | "db_postgres_xact_rollback": 70, |
| 408 | "db_production_blks_hit": 0, |
| 409 | "db_production_blks_read": 0, |
| 410 | "db_production_blks_read_perc": 0, |
| 411 | "db_production_confl_bufferpin": 0, |
| 412 | "db_production_confl_deadlock": 0, |
| 413 | "db_production_confl_lock": 0, |
| 414 | "db_production_confl_snapshot": 0, |
| 415 | "db_production_confl_tablespace": 0, |
| 416 | "db_production_conflicts": 0, |
| 417 | "db_production_deadlocks": 0, |
| 418 | "db_production_lock_mode_AccessExclusiveLock_awaited": 0, |
| 419 | "db_production_lock_mode_AccessExclusiveLock_held": 0, |
| 420 | "db_production_lock_mode_AccessShareLock_awaited": 0, |
| 421 | "db_production_lock_mode_AccessShareLock_held": 0, |
| 422 | "db_production_lock_mode_ExclusiveLock_awaited": 0, |
| 423 | "db_production_lock_mode_ExclusiveLock_held": 0, |
| 424 | "db_production_lock_mode_RowExclusiveLock_awaited": 0, |
| 425 | "db_production_lock_mode_RowExclusiveLock_held": 0, |
| 426 | "db_production_lock_mode_RowShareLock_awaited": 0, |
| 427 | "db_production_lock_mode_RowShareLock_held": 0, |
| 428 | "db_production_lock_mode_ShareLock_awaited": 99, |
| 429 | "db_production_lock_mode_ShareLock_held": 0, |
| 430 | "db_production_lock_mode_ShareRowExclusiveLock_awaited": 0, |
| 431 | "db_production_lock_mode_ShareRowExclusiveLock_held": 0, |
| 432 | "db_production_lock_mode_ShareUpdateExclusiveLock_awaited": 0, |
| 433 | "db_production_lock_mode_ShareUpdateExclusiveLock_held": 99, |
| 434 | "db_production_numbackends": 1, |
| 435 | "db_production_numbackends_utilization": 1, |
| 436 | "db_production_size": 8602115, |
| 437 | "db_production_temp_bytes": 0, |
| 438 | "db_production_temp_files": 0, |
| 439 | "db_production_tup_deleted": 0, |
| 440 | "db_production_tup_fetched": 0, |
| 441 | "db_production_tup_fetched_perc": 0, |
| 442 | "db_production_tup_inserted": 0, |
| 443 | "db_production_tup_returned": 0, |
| 444 | "db_production_tup_updated": 0, |
| 445 | "db_production_xact_commit": 0, |
| 446 | "db_production_xact_rollback": 0, |
| 447 | "index_myaccounts_email_key_table_myaccounts_db_postgres_schema_myschema_size": 8192, |
| 448 | "index_myaccounts_email_key_table_myaccounts_db_postgres_schema_myschema_usage_status_unused": 1, |
| 449 | "index_myaccounts_email_key_table_myaccounts_db_postgres_schema_myschema_usage_status_used": 0, |
| 450 | "index_myaccounts_email_key_table_myaccounts_db_postgres_schema_public_size": 8192, |
| 451 | "index_myaccounts_email_key_table_myaccounts_db_postgres_schema_public_usage_status_unused": 1, |
| 452 | "index_myaccounts_email_key_table_myaccounts_db_postgres_schema_public_usage_status_used": 0, |
| 453 | "index_myaccounts_pkey_table_myaccounts_db_postgres_schema_myschema_size": 8192, |
| 454 | "index_myaccounts_pkey_table_myaccounts_db_postgres_schema_myschema_usage_status_unused": 1, |
| 455 | "index_myaccounts_pkey_table_myaccounts_db_postgres_schema_myschema_usage_status_used": 0, |
| 456 | "index_myaccounts_pkey_table_myaccounts_db_postgres_schema_public_size": 8192, |
| 457 | "index_myaccounts_pkey_table_myaccounts_db_postgres_schema_public_usage_status_unused": 1, |
| 458 | "index_myaccounts_pkey_table_myaccounts_db_postgres_schema_public_usage_status_used": 0, |
| 459 | "index_myaccounts_username_key_table_myaccounts_db_postgres_schema_myschema_size": 8192, |
| 460 | "index_myaccounts_username_key_table_myaccounts_db_postgres_schema_myschema_usage_status_unused": 1, |
| 461 | "index_myaccounts_username_key_table_myaccounts_db_postgres_schema_myschema_usage_status_used": 0, |
| 462 | "index_myaccounts_username_key_table_myaccounts_db_postgres_schema_public_size": 8192, |
| 463 | "index_myaccounts_username_key_table_myaccounts_db_postgres_schema_public_usage_status_unused": 1, |
| 464 | "index_myaccounts_username_key_table_myaccounts_db_postgres_schema_public_usage_status_used": 0, |
| 465 | "index_pgbench_accounts_pkey_table_pgbench_accounts_db_postgres_schema_public_bloat_size": 0, |
| 466 | "index_pgbench_accounts_pkey_table_pgbench_accounts_db_postgres_schema_public_bloat_size_perc": 0, |
| 467 | "index_pgbench_accounts_pkey_table_pgbench_accounts_db_postgres_schema_public_size": 112336896, |
| 468 | "index_pgbench_accounts_pkey_table_pgbench_accounts_db_postgres_schema_public_usage_status_unused": 0, |
| 469 | "index_pgbench_accounts_pkey_table_pgbench_accounts_db_postgres_schema_public_usage_status_used": 1, |
| 470 | "index_pgbench_branches_pkey_table_pgbench_branches_db_postgres_schema_public_size": 16384, |
| 471 | "index_pgbench_branches_pkey_table_pgbench_branches_db_postgres_schema_public_usage_status_unused": 1, |
| 472 | "index_pgbench_branches_pkey_table_pgbench_branches_db_postgres_schema_public_usage_status_used": 0, |
| 473 | "index_pgbench_tellers_pkey_table_pgbench_tellers_db_postgres_schema_public_size": 32768, |
| 474 | "index_pgbench_tellers_pkey_table_pgbench_tellers_db_postgres_schema_public_usage_status_unused": 1, |
| 475 | "index_pgbench_tellers_pkey_table_pgbench_tellers_db_postgres_schema_public_usage_status_used": 0, |
| 476 | "locks_utilization": 6, |
| 477 | "maxwritten_clean": 0, |
| 478 | "oldest_current_xid": 9, |
| 479 | "percent_towards_emergency_autovacuum": 0, |
| 480 | "percent_towards_wraparound": 0, |
| 481 | "query_running_time_hist_bucket_1": 1, |
| 482 | "query_running_time_hist_bucket_2": 0, |
| 483 | "query_running_time_hist_bucket_3": 0, |
| 484 | "query_running_time_hist_bucket_4": 0, |
| 485 | "query_running_time_hist_bucket_5": 0, |
| 486 | "query_running_time_hist_bucket_6": 0, |
| 487 | "query_running_time_hist_bucket_inf": 0, |
| 488 | "query_running_time_hist_count": 1, |
| 489 | "query_running_time_hist_sum": 0, |
| 490 | "repl_slot_ocean_replslot_files": 0, |
| 491 | "repl_slot_ocean_replslot_wal_keep": 0, |
| 492 | "repl_standby_app_phys-standby2_wal_flush_lag_size": 0, |
| 493 | "repl_standby_app_phys-standby2_wal_flush_lag_time": 0, |
| 494 | "repl_standby_app_phys-standby2_wal_replay_lag_size": 0, |
| 495 | "repl_standby_app_phys-standby2_wal_replay_lag_time": 0, |
| 496 | "repl_standby_app_phys-standby2_wal_sent_lag_size": 0, |
| 497 | "repl_standby_app_phys-standby2_wal_write_lag_size": 0, |
| 498 | "repl_standby_app_phys-standby2_wal_write_time": 0, |
| 499 | "repl_standby_app_walreceiver_wal_flush_lag_size": 2, |
| 500 | "repl_standby_app_walreceiver_wal_flush_lag_time": 2, |
| 501 | "repl_standby_app_walreceiver_wal_replay_lag_size": 2, |
| 502 | "repl_standby_app_walreceiver_wal_replay_lag_time": 2, |
| 503 | "repl_standby_app_walreceiver_wal_sent_lag_size": 2, |
| 504 | "repl_standby_app_walreceiver_wal_write_lag_size": 2, |
| 505 | "repl_standby_app_walreceiver_wal_write_time": 2, |
| 506 | "server_connections_available": 97, |
| 507 | "server_connections_state_active": 1, |
| 508 | "server_connections_state_disabled": 1, |
| 509 | "server_connections_state_fastpath_function_call": 1, |
| 510 | "server_connections_state_idle": 14, |
| 511 | "server_connections_state_idle_in_transaction": 7, |
| 512 | "server_connections_state_idle_in_transaction_aborted": 1, |
| 513 | "server_connections_used": 3, |
| 514 | "server_connections_utilization": 3, |
| 515 | "server_uptime": 499906, |
| 516 | "table_pgbench_accounts_db_postgres_schema_public_bloat_size": 9863168, |
| 517 | "table_pgbench_accounts_db_postgres_schema_public_bloat_size_perc": 1, |
| 518 | "table_pgbench_accounts_db_postgres_schema_public_heap_blks_hit": 224484753408, |
| 519 | "table_pgbench_accounts_db_postgres_schema_public_heap_blks_read": 1803882668032, |
| 520 | "table_pgbench_accounts_db_postgres_schema_public_heap_blks_read_perc": 88, |
| 521 | "table_pgbench_accounts_db_postgres_schema_public_idx_blks_hit": 7138635948032, |
| 522 | "table_pgbench_accounts_db_postgres_schema_public_idx_blks_read": 973310976000, |
| 523 | "table_pgbench_accounts_db_postgres_schema_public_idx_blks_read_perc": 11, |
| 524 | "table_pgbench_accounts_db_postgres_schema_public_idx_scan": 99955, |
| 525 | "table_pgbench_accounts_db_postgres_schema_public_idx_tup_fetch": 99955, |
| 526 | "table_pgbench_accounts_db_postgres_schema_public_last_analyze_ago": 377149, |
| 527 | "table_pgbench_accounts_db_postgres_schema_public_last_vacuum_ago": 377149, |
| 528 | "table_pgbench_accounts_db_postgres_schema_public_n_dead_tup": 1000048, |
| 529 | "table_pgbench_accounts_db_postgres_schema_public_n_dead_tup_perc": 16, |
| 530 | "table_pgbench_accounts_db_postgres_schema_public_n_live_tup": 5000048, |
| 531 | "table_pgbench_accounts_db_postgres_schema_public_n_tup_del": 0, |
| 532 | "table_pgbench_accounts_db_postgres_schema_public_n_tup_hot_upd": 0, |
| 533 | "table_pgbench_accounts_db_postgres_schema_public_n_tup_hot_upd_perc": 0, |
| 534 | "table_pgbench_accounts_db_postgres_schema_public_n_tup_ins": 5000000, |
| 535 | "table_pgbench_accounts_db_postgres_schema_public_n_tup_upd": 0, |
| 536 | "table_pgbench_accounts_db_postgres_schema_public_seq_scan": 2, |
| 537 | "table_pgbench_accounts_db_postgres_schema_public_seq_tup_read": 5000000, |
| 538 | "table_pgbench_accounts_db_postgres_schema_public_tidx_blks_hit": -1, |
| 539 | "table_pgbench_accounts_db_postgres_schema_public_tidx_blks_read": -1, |
| 540 | "table_pgbench_accounts_db_postgres_schema_public_tidx_blks_read_perc": 50, |
| 541 | "table_pgbench_accounts_db_postgres_schema_public_toast_blks_hit": -1, |
| 542 | "table_pgbench_accounts_db_postgres_schema_public_toast_blks_read": -1, |
| 543 | "table_pgbench_accounts_db_postgres_schema_public_toast_blks_read_perc": 50, |
| 544 | "table_pgbench_accounts_db_postgres_schema_public_total_size": 784031744, |
| 545 | "table_pgbench_branches_db_postgres_schema_public_heap_blks_hit": 304316416, |
| 546 | "table_pgbench_branches_db_postgres_schema_public_heap_blks_read": 507150336, |
| 547 | "table_pgbench_branches_db_postgres_schema_public_heap_blks_read_perc": 62, |
| 548 | "table_pgbench_branches_db_postgres_schema_public_idx_blks_hit": 101441536, |
| 549 | "table_pgbench_branches_db_postgres_schema_public_idx_blks_read": 101425152, |
| 550 | "table_pgbench_branches_db_postgres_schema_public_idx_blks_read_perc": 49, |
| 551 | "table_pgbench_branches_db_postgres_schema_public_idx_scan": 0, |
| 552 | "table_pgbench_branches_db_postgres_schema_public_idx_tup_fetch": 0, |
| 553 | "table_pgbench_branches_db_postgres_schema_public_last_analyze_ago": 377149, |
| 554 | "table_pgbench_branches_db_postgres_schema_public_last_vacuum_ago": 371719, |
| 555 | "table_pgbench_branches_db_postgres_schema_public_n_dead_tup": 0, |
| 556 | "table_pgbench_branches_db_postgres_schema_public_n_dead_tup_perc": 0, |
| 557 | "table_pgbench_branches_db_postgres_schema_public_n_live_tup": 50, |
| 558 | "table_pgbench_branches_db_postgres_schema_public_n_tup_del": 0, |
| 559 | "table_pgbench_branches_db_postgres_schema_public_n_tup_hot_upd": 0, |
| 560 | "table_pgbench_branches_db_postgres_schema_public_n_tup_hot_upd_perc": 0, |
| 561 | "table_pgbench_branches_db_postgres_schema_public_n_tup_ins": 50, |
| 562 | "table_pgbench_branches_db_postgres_schema_public_n_tup_upd": 0, |
| 563 | "table_pgbench_branches_db_postgres_schema_public_seq_scan": 6, |
| 564 | "table_pgbench_branches_db_postgres_schema_public_seq_tup_read": 300, |
| 565 | "table_pgbench_branches_db_postgres_schema_public_tidx_blks_hit": -1, |
| 566 | "table_pgbench_branches_db_postgres_schema_public_tidx_blks_read": -1, |
| 567 | "table_pgbench_branches_db_postgres_schema_public_tidx_blks_read_perc": 50, |
| 568 | "table_pgbench_branches_db_postgres_schema_public_toast_blks_hit": -1, |
| 569 | "table_pgbench_branches_db_postgres_schema_public_toast_blks_read": -1, |
| 570 | "table_pgbench_branches_db_postgres_schema_public_toast_blks_read_perc": 50, |
| 571 | "table_pgbench_branches_db_postgres_schema_public_total_size": 57344, |
| 572 | "table_pgbench_history_db_postgres_schema_public_heap_blks_hit": 0, |
| 573 | "table_pgbench_history_db_postgres_schema_public_heap_blks_read": 0, |
| 574 | "table_pgbench_history_db_postgres_schema_public_heap_blks_read_perc": 0, |
| 575 | "table_pgbench_history_db_postgres_schema_public_idx_blks_hit": -1, |
| 576 | "table_pgbench_history_db_postgres_schema_public_idx_blks_read": -1, |
| 577 | "table_pgbench_history_db_postgres_schema_public_idx_blks_read_perc": 50, |
| 578 | "table_pgbench_history_db_postgres_schema_public_idx_scan": 0, |
| 579 | "table_pgbench_history_db_postgres_schema_public_idx_tup_fetch": 0, |
| 580 | "table_pgbench_history_db_postgres_schema_public_last_analyze_ago": 377149, |
| 581 | "table_pgbench_history_db_postgres_schema_public_last_vacuum_ago": 377149, |
| 582 | "table_pgbench_history_db_postgres_schema_public_n_dead_tup": 0, |
| 583 | "table_pgbench_history_db_postgres_schema_public_n_dead_tup_perc": 0, |
| 584 | "table_pgbench_history_db_postgres_schema_public_n_live_tup": 0, |
| 585 | "table_pgbench_history_db_postgres_schema_public_n_tup_del": 0, |
| 586 | "table_pgbench_history_db_postgres_schema_public_n_tup_hot_upd": 0, |
| 587 | "table_pgbench_history_db_postgres_schema_public_n_tup_hot_upd_perc": 0, |
| 588 | "table_pgbench_history_db_postgres_schema_public_n_tup_ins": 0, |
| 589 | "table_pgbench_history_db_postgres_schema_public_n_tup_upd": 0, |
| 590 | "table_pgbench_history_db_postgres_schema_public_seq_scan": 0, |
| 591 | "table_pgbench_history_db_postgres_schema_public_seq_tup_read": 0, |
| 592 | "table_pgbench_history_db_postgres_schema_public_tidx_blks_hit": -1, |
| 593 | "table_pgbench_history_db_postgres_schema_public_tidx_blks_read": -1, |
| 594 | "table_pgbench_history_db_postgres_schema_public_tidx_blks_read_perc": 50, |
| 595 | "table_pgbench_history_db_postgres_schema_public_toast_blks_hit": -1, |
| 596 | "table_pgbench_history_db_postgres_schema_public_toast_blks_read": -1, |
| 597 | "table_pgbench_history_db_postgres_schema_public_toast_blks_read_perc": 50, |
| 598 | "table_pgbench_history_db_postgres_schema_public_total_size": 0, |
| 599 | "table_pgbench_tellers_db_postgres_schema_public_heap_blks_hit": 491937792, |
| 600 | "table_pgbench_tellers_db_postgres_schema_public_heap_blks_read": 623828992, |
| 601 | "table_pgbench_tellers_db_postgres_schema_public_heap_blks_read_perc": 55, |
| 602 | "table_pgbench_tellers_db_postgres_schema_public_idx_blks_hit": 0, |
| 603 | "table_pgbench_tellers_db_postgres_schema_public_idx_blks_read": 101433344, |
| 604 | "table_pgbench_tellers_db_postgres_schema_public_idx_blks_read_perc": 100, |
| 605 | "table_pgbench_tellers_db_postgres_schema_public_idx_scan": 0, |
| 606 | "table_pgbench_tellers_db_postgres_schema_public_idx_tup_fetch": 0, |
| 607 | "table_pgbench_tellers_db_postgres_schema_public_last_analyze_ago": 377149, |
| 608 | "table_pgbench_tellers_db_postgres_schema_public_last_vacuum_ago": 371719, |
| 609 | "table_pgbench_tellers_db_postgres_schema_public_n_dead_tup": 0, |
| 610 | "table_pgbench_tellers_db_postgres_schema_public_n_dead_tup_perc": 0, |
| 611 | "table_pgbench_tellers_db_postgres_schema_public_n_live_tup": 500, |
| 612 | "table_pgbench_tellers_db_postgres_schema_public_n_tup_del": 0, |
| 613 | "table_pgbench_tellers_db_postgres_schema_public_n_tup_hot_upd": 0, |
| 614 | "table_pgbench_tellers_db_postgres_schema_public_n_tup_hot_upd_perc": 0, |
| 615 | "table_pgbench_tellers_db_postgres_schema_public_n_tup_ins": 500, |
| 616 | "table_pgbench_tellers_db_postgres_schema_public_n_tup_upd": 0, |
| 617 | "table_pgbench_tellers_db_postgres_schema_public_null_columns": 1, |
| 618 | "table_pgbench_tellers_db_postgres_schema_public_seq_scan": 1, |
| 619 | "table_pgbench_tellers_db_postgres_schema_public_seq_tup_read": 500, |
| 620 | "table_pgbench_tellers_db_postgres_schema_public_tidx_blks_hit": -1, |
| 621 | "table_pgbench_tellers_db_postgres_schema_public_tidx_blks_read": -1, |
| 622 | "table_pgbench_tellers_db_postgres_schema_public_tidx_blks_read_perc": 50, |
| 623 | "table_pgbench_tellers_db_postgres_schema_public_toast_blks_hit": -1, |
| 624 | "table_pgbench_tellers_db_postgres_schema_public_toast_blks_read": -1, |
| 625 | "table_pgbench_tellers_db_postgres_schema_public_toast_blks_read_perc": 50, |
| 626 | "table_pgbench_tellers_db_postgres_schema_public_total_size": 90112, |
| 627 | "transaction_running_time_hist_bucket_1": 1, |
| 628 | "transaction_running_time_hist_bucket_2": 0, |
| 629 | "transaction_running_time_hist_bucket_3": 0, |
| 630 | "transaction_running_time_hist_bucket_4": 0, |
| 631 | "transaction_running_time_hist_bucket_5": 0, |
| 632 | "transaction_running_time_hist_bucket_6": 0, |
| 633 | "transaction_running_time_hist_bucket_inf": 7, |
| 634 | "transaction_running_time_hist_count": 8, |
| 635 | "transaction_running_time_hist_sum": 4022, |
| 636 | "wal_archive_files_done_count": 1, |
| 637 | "wal_archive_files_ready_count": 1, |
| 638 | "wal_recycled_files": 0, |
| 639 | "wal_writes": 24103144, |
| 640 | "wal_written_files": 1, |
| 641 | } |
| 642 | |
| 643 | assert.Equal(t, expected, mx) |
| 644 | }, |
| 645 | }, |
| 646 | }, |
| 647 | "Fail when querying the database version returns an error": { |
| 648 | { |
| 649 | prepareMock: func(t *testing.T, collr *Collector, m sqlmock.Sqlmock) { |
| 650 | mockExpectErr(m, queryServerVersion()) |
| 651 | }, |
| 652 | check: func(t *testing.T, collr *Collector) { |
| 653 | mx := collr.Collect(context.Background()) |
| 654 | var expected map[string]int64 |
| 655 | assert.Equal(t, expected, mx) |
| 656 | }, |
| 657 | }, |
| 658 | }, |
| 659 | "Fail when querying settings max connections returns an error": { |
| 660 | { |
| 661 | prepareMock: func(t *testing.T, collr *Collector, m sqlmock.Sqlmock) { |
| 662 | mockExpect(t, m, queryServerVersion(), dataVer140004ServerVersionNum) |
| 663 | mockExpect(t, m, queryIsSuperUser(), dataVer140004IsSuperUserTrue) |
| 664 | mockExpect(t, m, queryCanExecutePgLsDir(), dataVer140004IsSuperUserTrue) |
| 665 | mockExpect(t, m, queryPGIsInRecovery(), dataVer140004PGIsInRecoveryTrue) |
| 666 | |
| 667 | mockExpectErr(m, querySettingsMaxConnections()) |
| 668 | }, |
| 669 | check: func(t *testing.T, collr *Collector) { |
| 670 | mx := collr.Collect(context.Background()) |
| 671 | var expected map[string]int64 |
| 672 | assert.Equal(t, expected, mx) |
| 673 | }, |
| 674 | }, |
| 675 | }, |
| 676 | "Fail when querying the server connections returns an error": { |
| 677 | { |
| 678 | prepareMock: func(t *testing.T, collr *Collector, m sqlmock.Sqlmock) { |
| 679 | mockExpect(t, m, queryServerVersion(), dataVer140004ServerVersionNum) |
| 680 | mockExpect(t, m, queryIsSuperUser(), dataVer140004IsSuperUserTrue) |
| 681 | mockExpect(t, m, queryCanExecutePgLsDir(), dataVer140004IsSuperUserTrue) |
| 682 | mockExpect(t, m, queryPGIsInRecovery(), dataVer140004PGIsInRecoveryTrue) |
| 683 | |
| 684 | mockExpect(t, m, querySettingsMaxConnections(), dataVer140004SettingsMaxConnections) |
| 685 | mockExpect(t, m, querySettingsMaxLocksHeld(), dataVer140004SettingsMaxLocksHeld) |
| 686 | |
| 687 | mockExpectErr(m, queryServerCurrentConnectionsUsed()) |
| 688 | }, |
| 689 | check: func(t *testing.T, collr *Collector) { |
| 690 | mx := collr.Collect(context.Background()) |
| 691 | var expected map[string]int64 |
| 692 | assert.Equal(t, expected, mx) |
| 693 | }, |
| 694 | }, |
| 695 | }, |
| 696 | } |
| 697 | |
| 698 | for name, test := range tests { |
| 699 | t.Run(name, func(t *testing.T) { |
| 700 | db, mock, err := sqlmock.New( |
| 701 | sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual), |
| 702 | ) |
| 703 | require.NoError(t, err) |
| 704 | |
| 705 | collr := New() |
| 706 | collr.db = db |
| 707 | defer func() { _ = db.Close() }() |
| 708 | |
| 709 | require.NoError(t, collr.Init(context.Background())) |
| 710 | |
| 711 | for i, step := range test { |
| 712 | t.Run(fmt.Sprintf("step[%d]", i), func(t *testing.T) { |
| 713 | step.prepareMock(t, collr, mock) |
| 714 | step.check(t, collr) |
| 715 | }) |
| 716 | } |
| 717 | assert.NoError(t, mock.ExpectationsWereMet()) |
| 718 | }) |
| 719 | } |
| 720 | } |
| 721 | |
| 722 | func TestCollector_doQueryReplicationMetrics_replSlotFilesGate(t *testing.T) { |
| 723 | boolPtr := func(v bool) *bool { return &v } |
| 724 | |
| 725 | tests := map[string]struct { |
| 726 | superUser *bool |
| 727 | canExecutePgLsDir *bool |
| 728 | expectSlotFilesCall bool |
| 729 | }{ |
| 730 | "superuser collects slot files even without pg_ls_dir privilege": { |
| 731 | superUser: boolPtr(true), |
| 732 | canExecutePgLsDir: boolPtr(false), |
| 733 | expectSlotFilesCall: true, |
| 734 | }, |
| 735 | "non-superuser with pg_ls_dir privilege collects slot files": { |
| 736 | superUser: boolPtr(false), |
| 737 | canExecutePgLsDir: boolPtr(true), |
| 738 | expectSlotFilesCall: true, |
| 739 | }, |
| 740 | "non-superuser without pg_ls_dir privilege skips slot files": { |
| 741 | superUser: boolPtr(false), |
| 742 | canExecutePgLsDir: boolPtr(false), |
| 743 | expectSlotFilesCall: false, |
| 744 | }, |
| 745 | } |
| 746 | |
| 747 | for name, test := range tests { |
| 748 | t.Run(name, func(t *testing.T) { |
| 749 | db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) |
| 750 | require.NoError(t, err) |
| 751 | defer func() { _ = db.Close() }() |
| 752 | |
| 753 | collr := New() |
| 754 | collr.db = db |
| 755 | require.NoError(t, collr.Init(context.Background())) |
| 756 | |
| 757 | collr.pgVersion = 140004 |
| 758 | collr.superUser = test.superUser |
| 759 | collr.canExecutePgLsDir = test.canExecutePgLsDir |
| 760 | |
| 761 | mockExpect(t, mock, queryReplicationStandbyAppDelta(collr.pgVersion), dataVer140004ReplStandbyAppDelta) |
| 762 | mockExpect(t, mock, queryReplicationStandbyAppLag(), dataVer140004ReplStandbyAppLag) |
| 763 | if test.expectSlotFilesCall { |
| 764 | mockExpect(t, mock, queryReplicationSlotFiles(collr.pgVersion), dataVer140004ReplSlotFiles) |
| 765 | } |
| 766 | |
| 767 | assert.NoError(t, collr.doQueryReplicationMetrics()) |
| 768 | assert.NoError(t, mock.ExpectationsWereMet()) |
| 769 | }) |
| 770 | } |
| 771 | } |
| 772 | |
| 773 | func mockExpect(t *testing.T, mock sqlmock.Sqlmock, query string, rows []byte) { |
| 774 | mock.ExpectQuery(query).WillReturnRows(mustMockRows(t, rows)).RowsWillBeClosed() |
| 775 | } |
| 776 | |
| 777 | func mockExpectErr(mock sqlmock.Sqlmock, query string) { |
| 778 | mock.ExpectQuery(query).WillReturnError(errors.New("mock error")) |
| 779 | } |
| 780 | |
| 781 | func mustMockRows(t *testing.T, data []byte) *sqlmock.Rows { |
| 782 | rows, err := prepareMockRows(data) |
| 783 | require.NoError(t, err) |
| 784 | return rows |
| 785 | } |
| 786 | |
| 787 | func prepareMockRows(data []byte) (*sqlmock.Rows, error) { |
| 788 | r := bytes.NewReader(data) |
| 789 | sc := bufio.NewScanner(r) |
| 790 | |
| 791 | var numColumns int |
| 792 | var rows *sqlmock.Rows |
| 793 | |
| 794 | for sc.Scan() { |
| 795 | s := strings.TrimSpace(sc.Text()) |
| 796 | if s == "" || strings.HasPrefix(s, "---") { |
| 797 | continue |
| 798 | } |
| 799 | |
| 800 | parts := strings.Split(s, "|") |
| 801 | for i, v := range parts { |
| 802 | parts[i] = strings.TrimSpace(v) |
| 803 | } |
| 804 | |
| 805 | if rows == nil { |
| 806 | numColumns = len(parts) |
| 807 | rows = sqlmock.NewRows(parts) |
| 808 | continue |
| 809 | } |
| 810 | |
| 811 | if len(parts) != numColumns { |
| 812 | return nil, fmt.Errorf("prepareMockRows(): columns != values (%d/%d)", numColumns, len(parts)) |
| 813 | } |
| 814 | |
| 815 | values := make([]driver.Value, len(parts)) |
| 816 | for i, v := range parts { |
| 817 | values[i] = v |
| 818 | } |
| 819 | rows.AddRow(values...) |
| 820 | } |
| 821 | |
| 822 | if rows == nil { |
| 823 | return nil, errors.New("prepareMockRows(): nil rows result") |
| 824 | } |
| 825 | |
| 826 | return rows, nil |
| 827 | } |