master
go 827 lines 45 KB
Raw
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 }