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  }