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