gitlab.com/gitlab-org/labkit@v1.21.0/metrics/sqlmetrics/dbstats_test.go (about) 1 package sqlmetrics 2 3 import ( 4 "bytes" 5 "database/sql" 6 "fmt" 7 "html/template" 8 "testing" 9 10 "github.com/prometheus/client_golang/prometheus" 11 "github.com/prometheus/client_golang/prometheus/testutil" 12 "github.com/stretchr/testify/require" 13 ) 14 15 type dbMock sql.DBStats 16 17 func (db dbMock) Stats() sql.DBStats { 18 return sql.DBStats(db) 19 } 20 21 func TestNewDBStatsCollector(t *testing.T) { 22 db := &dbMock{ 23 MaxOpenConnections: 100, 24 OpenConnections: 10, 25 InUse: 3, 26 Idle: 7, 27 WaitCount: 5, 28 WaitDuration: 12, 29 MaxIdleClosed: 7, 30 MaxIdleTimeClosed: 6, 31 MaxLifetimeClosed: 8, 32 } 33 34 dbName := "foo" 35 defaultLabels := prometheus.Labels{dbNameLabel: dbName} 36 37 tests := []struct { 38 name string 39 opts []DBStatsCollectorOption 40 expectedLabels prometheus.Labels 41 }{ 42 { 43 name: "default", 44 expectedLabels: defaultLabels, 45 }, 46 { 47 name: "with custom labels", 48 opts: []DBStatsCollectorOption{ 49 WithExtraLabels(map[string]string{"x": "y"}), 50 }, 51 expectedLabels: prometheus.Labels{ 52 dbNameLabel: dbName, 53 "x": "y", 54 }, 55 }, 56 { 57 name: "does not override db_name label", 58 opts: []DBStatsCollectorOption{ 59 WithExtraLabels(map[string]string{"db_name": "bar"}), 60 }, 61 expectedLabels: defaultLabels, 62 }, 63 } 64 for _, tt := range tests { 65 t.Run(tt.name, func(t *testing.T) { 66 collector := NewDBStatsCollector(dbName, db, tt.opts...) 67 68 validateMetric(t, collector, maxOpenConnectionsName, maxOpenConnectionsDesc, "gauge", float64(db.MaxOpenConnections), tt.expectedLabels) 69 validateMetric(t, collector, openConnectionsName, openConnectionsDesc, "gauge", float64(db.OpenConnections), tt.expectedLabels) 70 validateMetric(t, collector, inUseName, inUseDesc, "gauge", float64(db.InUse), tt.expectedLabels) 71 validateMetric(t, collector, idleName, idleDesc, "gauge", float64(db.Idle), tt.expectedLabels) 72 validateMetric(t, collector, waitCountName, waitCountDesc, "counter", float64(db.WaitCount), tt.expectedLabels) 73 validateMetric(t, collector, waitDurationName, waitDurationDesc, "counter", db.WaitDuration.Seconds(), tt.expectedLabels) 74 validateMetric(t, collector, maxIdleClosedName, maxIdleClosedDesc, "counter", float64(db.MaxIdleClosed), tt.expectedLabels) 75 validateMetric(t, collector, maxIdleTimeClosedName, maxIdleTimeClosedDesc, "counter", float64(db.MaxIdleTimeClosed), tt.expectedLabels) 76 validateMetric(t, collector, maxLifetimeClosedName, maxLifetimeClosedDesc, "counter", float64(db.MaxLifetimeClosed), tt.expectedLabels) 77 }) 78 } 79 } 80 81 type labelsIter struct { 82 Dict prometheus.Labels 83 Counter int 84 } 85 86 func (l *labelsIter) HasMore() bool { 87 l.Counter++ 88 return l.Counter < len(l.Dict) 89 } 90 91 func validateMetric(t *testing.T, collector prometheus.Collector, name string, desc string, valueType string, value float64, labels prometheus.Labels) { 92 t.Helper() 93 94 tmpl := template.New("") 95 tmpl.Delims("[[", "]]") 96 txt := ` 97 # HELP [[.Name]] [[.Desc]] 98 # TYPE [[.Name]] [[.Type]] 99 [[.Name]]{[[range $k, $v := .Labels.Dict]][[$k]]="[[$v]]"[[if $.Labels.HasMore]],[[end]][[end]]} [[.Value]] 100 ` 101 _, err := tmpl.Parse(txt) 102 require.NoError(t, err) 103 104 var expected bytes.Buffer 105 fullName := fmt.Sprintf("%s_%s_%s", namespace, subsystem, name) 106 107 err = tmpl.Execute(&expected, struct { 108 Name string 109 Desc string 110 Type string 111 Value float64 112 Labels *labelsIter 113 }{ 114 Name: fullName, 115 Desc: desc, 116 Labels: &labelsIter{Dict: labels}, 117 Value: value, 118 Type: valueType, 119 }) 120 require.NoError(t, err) 121 122 err = testutil.CollectAndCompare(collector, &expected, fullName) 123 require.NoError(t, err) 124 }