You've already forked postgres_exporter
mirror of
https://github.com/prometheus-community/postgres_exporter.git
synced 2025-08-05 06:21:12 +03:00
Bug fix: Make collector not fail on null values (#823)
* Make all values nullable --------- Signed-off-by: Felix Yuan <felix.yuan@reddit.com> Co-authored-by: Ben Kochie <superq@gmail.com>
This commit is contained in:
@@ -15,6 +15,7 @@ package collector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@@ -79,32 +80,39 @@ func (c PGDatabaseCollector) Update(ctx context.Context, instance *instance, ch
|
|||||||
var databases []string
|
var databases []string
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var datname string
|
var datname sql.NullString
|
||||||
if err := rows.Scan(&datname); err != nil {
|
if err := rows.Scan(&datname); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !datname.Valid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Ignore excluded databases
|
// Ignore excluded databases
|
||||||
// Filtering is done here instead of in the query to avoid
|
// Filtering is done here instead of in the query to avoid
|
||||||
// a complicated NOT IN query with a variable number of parameters
|
// a complicated NOT IN query with a variable number of parameters
|
||||||
if sliceContains(c.excludedDatabases, datname) {
|
if sliceContains(c.excludedDatabases, datname.String) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
databases = append(databases, datname)
|
databases = append(databases, datname.String)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query the size of the databases
|
// Query the size of the databases
|
||||||
for _, datname := range databases {
|
for _, datname := range databases {
|
||||||
var size int64
|
var size sql.NullFloat64
|
||||||
err = db.QueryRowContext(ctx, pgDatabaseSizeQuery, datname).Scan(&size)
|
err = db.QueryRowContext(ctx, pgDatabaseSizeQuery, datname).Scan(&size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sizeMetric := 0.0
|
||||||
|
if size.Valid {
|
||||||
|
sizeMetric = size.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
pgDatabaseSizeDesc,
|
pgDatabaseSizeDesc,
|
||||||
prometheus.GaugeValue, float64(size), datname,
|
prometheus.GaugeValue, sizeMetric, datname,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
|
@@ -59,3 +59,43 @@ func TestPGDatabaseCollector(t *testing.T) {
|
|||||||
t.Errorf("there were unfulfilled exceptions: %s", err)
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO add a null db test
|
||||||
|
|
||||||
|
func TestPGDatabaseCollectorNullMetric(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
mock.ExpectQuery(sanitizeQuery(pgDatabaseQuery)).WillReturnRows(sqlmock.NewRows([]string{"datname"}).
|
||||||
|
AddRow("postgres"))
|
||||||
|
|
||||||
|
mock.ExpectQuery(sanitizeQuery(pgDatabaseSizeQuery)).WithArgs("postgres").WillReturnRows(sqlmock.NewRows([]string{"pg_database_size"}).
|
||||||
|
AddRow(nil))
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGDatabaseCollector{}
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGDatabaseCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"datname": "postgres"}, value: 0, metricType: dto.MetricType_GAUGE},
|
||||||
|
}
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -15,6 +15,7 @@ package collector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
@@ -51,14 +52,18 @@ func (c *PGPostmasterCollector) Update(ctx context.Context, instance *instance,
|
|||||||
row := db.QueryRowContext(ctx,
|
row := db.QueryRowContext(ctx,
|
||||||
pgPostmasterQuery)
|
pgPostmasterQuery)
|
||||||
|
|
||||||
var startTimeSeconds float64
|
var startTimeSeconds sql.NullFloat64
|
||||||
err := row.Scan(&startTimeSeconds)
|
err := row.Scan(&startTimeSeconds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
startTimeSecondsMetric := 0.0
|
||||||
|
if startTimeSeconds.Valid {
|
||||||
|
startTimeSecondsMetric = startTimeSeconds.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
pgPostMasterStartTimeSeconds,
|
pgPostMasterStartTimeSeconds,
|
||||||
prometheus.GaugeValue, startTimeSeconds,
|
prometheus.GaugeValue, startTimeSecondsMetric,
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -57,3 +57,39 @@ func TestPgPostmasterCollector(t *testing.T) {
|
|||||||
t.Errorf("there were unfulfilled exceptions: %s", err)
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPgPostmasterCollectorNullTime(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
mock.ExpectQuery(sanitizeQuery(pgPostmasterQuery)).WillReturnRows(sqlmock.NewRows([]string{"pg_postmaster_start_time"}).
|
||||||
|
AddRow(nil))
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGPostmasterCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGPostmasterCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{}, value: 0, metricType: dto.MetricType_GAUGE},
|
||||||
|
}
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -15,21 +15,23 @@ package collector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const processIdleSubsystem = "process_idle"
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerCollector(processIdleSubsystem, defaultEnabled, NewPGProcessIdleCollector)
|
// Making this default disabled because we have no tests for it
|
||||||
|
registerCollector(processIdleSubsystem, defaultDisabled, NewPGProcessIdleCollector)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PGProcessIdleCollector struct {
|
type PGProcessIdleCollector struct {
|
||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const processIdleSubsystem = "process_idle"
|
||||||
|
|
||||||
func NewPGProcessIdleCollector(config collectorConfig) (Collector, error) {
|
func NewPGProcessIdleCollector(config collectorConfig) (Collector, error) {
|
||||||
return &PGProcessIdleCollector{log: config.logger}, nil
|
return &PGProcessIdleCollector{log: config.logger}, nil
|
||||||
}
|
}
|
||||||
@@ -41,8 +43,8 @@ var pgProcessIdleSeconds = prometheus.NewDesc(
|
|||||||
prometheus.Labels{},
|
prometheus.Labels{},
|
||||||
)
|
)
|
||||||
|
|
||||||
func (PGProcessIdleCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
|
func (PGProcessIdleCollector) Update(ctx context.Context, inst *instance, ch chan<- prometheus.Metric) error {
|
||||||
db := instance.getDB()
|
db := inst.getDB()
|
||||||
row := db.QueryRowContext(ctx,
|
row := db.QueryRowContext(ctx,
|
||||||
`WITH
|
`WITH
|
||||||
metrics AS (
|
metrics AS (
|
||||||
@@ -79,9 +81,9 @@ func (PGProcessIdleCollector) Update(ctx context.Context, instance *instance, ch
|
|||||||
FROM metrics JOIN buckets USING (application_name)
|
FROM metrics JOIN buckets USING (application_name)
|
||||||
GROUP BY 1, 2, 3;`)
|
GROUP BY 1, 2, 3;`)
|
||||||
|
|
||||||
var applicationName string
|
var applicationName sql.NullString
|
||||||
var secondsSum int64
|
var secondsSum sql.NullInt64
|
||||||
var secondsCount uint64
|
var secondsCount sql.NullInt64
|
||||||
var seconds []int64
|
var seconds []int64
|
||||||
var secondsBucket []uint64
|
var secondsBucket []uint64
|
||||||
|
|
||||||
@@ -97,10 +99,24 @@ func (PGProcessIdleCollector) Update(ctx context.Context, instance *instance, ch
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applicationNameLabel := "unknown"
|
||||||
|
if applicationName.Valid {
|
||||||
|
applicationNameLabel = applicationName.String
|
||||||
|
}
|
||||||
|
|
||||||
|
var secondsCountMetric uint64
|
||||||
|
if secondsCount.Valid {
|
||||||
|
secondsCountMetric = uint64(secondsCount.Int64)
|
||||||
|
}
|
||||||
|
secondsSumMetric := 0.0
|
||||||
|
if secondsSum.Valid {
|
||||||
|
secondsSumMetric = float64(secondsSum.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstHistogram(
|
ch <- prometheus.MustNewConstHistogram(
|
||||||
pgProcessIdleSeconds,
|
pgProcessIdleSeconds,
|
||||||
secondsCount, float64(secondsSum), buckets,
|
secondsCountMetric, secondsSumMetric, buckets,
|
||||||
applicationName,
|
applicationNameLabel,
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ package collector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@@ -82,32 +83,44 @@ func (PGReplicationSlotCollector) Update(ctx context.Context, instance *instance
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var slotName string
|
var slotName sql.NullString
|
||||||
var walLSN int64
|
var walLSN sql.NullFloat64
|
||||||
var flushLSN int64
|
var flushLSN sql.NullFloat64
|
||||||
var isActive bool
|
var isActive sql.NullBool
|
||||||
if err := rows.Scan(&slotName, &walLSN, &flushLSN, &isActive); err != nil {
|
if err := rows.Scan(&slotName, &walLSN, &flushLSN, &isActive); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
isActiveValue := 0
|
isActiveValue := 0.0
|
||||||
if isActive {
|
if isActive.Valid && isActive.Bool {
|
||||||
isActiveValue = 1
|
isActiveValue = 1.0
|
||||||
|
}
|
||||||
|
slotNameLabel := "unknown"
|
||||||
|
if slotName.Valid {
|
||||||
|
slotNameLabel = slotName.String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var walLSNMetric float64
|
||||||
|
if walLSN.Valid {
|
||||||
|
walLSNMetric = walLSN.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
pgReplicationSlotCurrentWalDesc,
|
pgReplicationSlotCurrentWalDesc,
|
||||||
prometheus.GaugeValue, float64(walLSN), slotName,
|
prometheus.GaugeValue, walLSNMetric, slotNameLabel,
|
||||||
)
|
)
|
||||||
if isActive {
|
if isActive.Valid && isActive.Bool {
|
||||||
|
var flushLSNMetric float64
|
||||||
|
if flushLSN.Valid {
|
||||||
|
flushLSNMetric = flushLSN.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
pgReplicationSlotCurrentFlushDesc,
|
pgReplicationSlotCurrentFlushDesc,
|
||||||
prometheus.GaugeValue, float64(flushLSN), slotName,
|
prometheus.GaugeValue, flushLSNMetric, slotNameLabel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
pgReplicationSlotIsActiveDesc,
|
pgReplicationSlotIsActiveDesc,
|
||||||
prometheus.GaugeValue, float64(isActiveValue), slotName,
|
prometheus.GaugeValue, isActiveValue, slotNameLabel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
|
@@ -103,3 +103,84 @@ func TestPgReplicationSlotCollectorInActive(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPgReplicationSlotCollectorActiveNil(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
columns := []string{"slot_name", "current_wal_lsn", "confirmed_flush_lsn", "active"}
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow("test_slot", 6, 12, nil)
|
||||||
|
mock.ExpectQuery(sanitizeQuery(pgReplicationSlotQuery)).WillReturnRows(rows)
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGReplicationSlotCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGReplicationSlotCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"slot_name": "test_slot"}, value: 6, metricType: dto.MetricType_GAUGE},
|
||||||
|
{labels: labelMap{"slot_name": "test_slot"}, value: 0, metricType: dto.MetricType_GAUGE},
|
||||||
|
}
|
||||||
|
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPgReplicationSlotCollectorTestNilValues(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
columns := []string{"slot_name", "current_wal_lsn", "confirmed_flush_lsn", "active"}
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow(nil, nil, nil, true)
|
||||||
|
mock.ExpectQuery(sanitizeQuery(pgReplicationSlotQuery)).WillReturnRows(rows)
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGReplicationSlotCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGReplicationSlotCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"slot_name": "unknown"}, value: 0, metricType: dto.MetricType_GAUGE},
|
||||||
|
{labels: labelMap{"slot_name": "unknown"}, value: 0, metricType: dto.MetricType_GAUGE},
|
||||||
|
{labels: labelMap{"slot_name": "unknown"}, value: 1, metricType: dto.MetricType_GAUGE},
|
||||||
|
}
|
||||||
|
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -15,7 +15,7 @@ package collector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
@@ -121,77 +121,113 @@ func (PGStatBGWriterCollector) Update(ctx context.Context, instance *instance, c
|
|||||||
row := db.QueryRowContext(ctx,
|
row := db.QueryRowContext(ctx,
|
||||||
statBGWriterQuery)
|
statBGWriterQuery)
|
||||||
|
|
||||||
var cpt int
|
var cpt, cpr, bcp, bc, mwc, bb, bbf, ba sql.NullInt64
|
||||||
var cpr int
|
var cpwt, cpst sql.NullFloat64
|
||||||
var cpwt float64
|
var sr sql.NullTime
|
||||||
var cpst float64
|
|
||||||
var bcp int
|
|
||||||
var bc int
|
|
||||||
var mwc int
|
|
||||||
var bb int
|
|
||||||
var bbf int
|
|
||||||
var ba int
|
|
||||||
var sr time.Time
|
|
||||||
|
|
||||||
err := row.Scan(&cpt, &cpr, &cpwt, &cpst, &bcp, &bc, &mwc, &bb, &bbf, &ba, &sr)
|
err := row.Scan(&cpt, &cpr, &cpwt, &cpst, &bcp, &bc, &mwc, &bb, &bbf, &ba, &sr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cptMetric := 0.0
|
||||||
|
if cpt.Valid {
|
||||||
|
cptMetric = float64(cpt.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterCheckpointsTimedDesc,
|
statBGWriterCheckpointsTimedDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(cpt),
|
cptMetric,
|
||||||
)
|
)
|
||||||
|
cprMetric := 0.0
|
||||||
|
if cpr.Valid {
|
||||||
|
cprMetric = float64(cpr.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterCheckpointsReqDesc,
|
statBGWriterCheckpointsReqDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(cpr),
|
cprMetric,
|
||||||
)
|
)
|
||||||
|
cpwtMetric := 0.0
|
||||||
|
if cpwt.Valid {
|
||||||
|
cpwtMetric = float64(cpwt.Float64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterCheckpointsReqTimeDesc,
|
statBGWriterCheckpointsReqTimeDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(cpwt),
|
cpwtMetric,
|
||||||
)
|
)
|
||||||
|
cpstMetric := 0.0
|
||||||
|
if cpst.Valid {
|
||||||
|
cpstMetric = float64(cpst.Float64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterCheckpointsSyncTimeDesc,
|
statBGWriterCheckpointsSyncTimeDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(cpst),
|
cpstMetric,
|
||||||
)
|
)
|
||||||
|
bcpMetric := 0.0
|
||||||
|
if bcp.Valid {
|
||||||
|
bcpMetric = float64(bcp.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterBuffersCheckpointDesc,
|
statBGWriterBuffersCheckpointDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(bcp),
|
bcpMetric,
|
||||||
)
|
)
|
||||||
|
bcMetric := 0.0
|
||||||
|
if bc.Valid {
|
||||||
|
bcMetric = float64(bc.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterBuffersCleanDesc,
|
statBGWriterBuffersCleanDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(bc),
|
bcMetric,
|
||||||
)
|
)
|
||||||
|
mwcMetric := 0.0
|
||||||
|
if mwc.Valid {
|
||||||
|
mwcMetric = float64(mwc.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterMaxwrittenCleanDesc,
|
statBGWriterMaxwrittenCleanDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(mwc),
|
mwcMetric,
|
||||||
)
|
)
|
||||||
|
bbMetric := 0.0
|
||||||
|
if bb.Valid {
|
||||||
|
bbMetric = float64(bb.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterBuffersBackendDesc,
|
statBGWriterBuffersBackendDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(bb),
|
bbMetric,
|
||||||
)
|
)
|
||||||
|
bbfMetric := 0.0
|
||||||
|
if bbf.Valid {
|
||||||
|
bbfMetric = float64(bbf.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterBuffersBackendFsyncDesc,
|
statBGWriterBuffersBackendFsyncDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(bbf),
|
bbfMetric,
|
||||||
)
|
)
|
||||||
|
baMetric := 0.0
|
||||||
|
if ba.Valid {
|
||||||
|
baMetric = float64(ba.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterBuffersAllocDesc,
|
statBGWriterBuffersAllocDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(ba),
|
baMetric,
|
||||||
)
|
)
|
||||||
|
srMetric := 0.0
|
||||||
|
if sr.Valid {
|
||||||
|
srMetric = float64(sr.Time.Unix())
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statBGWriterStatsResetDesc,
|
statBGWriterStatsResetDesc,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(sr.Unix()),
|
srMetric,
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@@ -88,3 +88,64 @@ func TestPGStatBGWriterCollector(t *testing.T) {
|
|||||||
t.Errorf("there were unfulfilled exceptions: %s", err)
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPGStatBGWriterCollectorNullValues(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
columns := []string{
|
||||||
|
"checkpoints_timed",
|
||||||
|
"checkpoints_req",
|
||||||
|
"checkpoint_write_time",
|
||||||
|
"checkpoint_sync_time",
|
||||||
|
"buffers_checkpoint",
|
||||||
|
"buffers_clean",
|
||||||
|
"maxwritten_clean",
|
||||||
|
"buffers_backend",
|
||||||
|
"buffers_backend_fsync",
|
||||||
|
"buffers_alloc",
|
||||||
|
"stats_reset"}
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||||
|
mock.ExpectQuery(sanitizeQuery(statBGWriterQuery)).WillReturnRows(rows)
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGStatBGWriterCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGStatBGWriterCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -202,12 +202,9 @@ var (
|
|||||||
[]string{"datid", "datname"},
|
[]string{"datid", "datname"},
|
||||||
prometheus.Labels{},
|
prometheus.Labels{},
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
func (PGStatDatabaseCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
|
statDatabaseQuery = `
|
||||||
db := instance.getDB()
|
SELECT
|
||||||
rows, err := db.QueryContext(ctx,
|
|
||||||
`SELECT
|
|
||||||
datid
|
datid
|
||||||
,datname
|
,datname
|
||||||
,numbackends
|
,numbackends
|
||||||
@@ -228,7 +225,13 @@ func (PGStatDatabaseCollector) Update(ctx context.Context, instance *instance, c
|
|||||||
,blk_write_time
|
,blk_write_time
|
||||||
,stats_reset
|
,stats_reset
|
||||||
FROM pg_stat_database;
|
FROM pg_stat_database;
|
||||||
`,
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func (PGStatDatabaseCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
|
||||||
|
db := instance.getDB()
|
||||||
|
rows, err := db.QueryContext(ctx,
|
||||||
|
statDatabaseQuery,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -236,24 +239,8 @@ func (PGStatDatabaseCollector) Update(ctx context.Context, instance *instance, c
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var datid string
|
var datid, datname sql.NullString
|
||||||
var datname string
|
var numBackends, xactCommit, xactRollback, blksRead, blksHit, tupReturned, tupFetched, tupInserted, tupUpdated, tupDeleted, conflicts, tempFiles, tempBytes, deadlocks, blkReadTime, blkWriteTime sql.NullFloat64
|
||||||
var numBackends float64
|
|
||||||
var xactCommit float64
|
|
||||||
var xactRollback float64
|
|
||||||
var blksRead float64
|
|
||||||
var blksHit float64
|
|
||||||
var tupReturned float64
|
|
||||||
var tupFetched float64
|
|
||||||
var tupInserted float64
|
|
||||||
var tupUpdated float64
|
|
||||||
var tupDeleted float64
|
|
||||||
var conflicts float64
|
|
||||||
var tempFiles float64
|
|
||||||
var tempBytes float64
|
|
||||||
var deadlocks float64
|
|
||||||
var blkReadTime float64
|
|
||||||
var blkWriteTime float64
|
|
||||||
var statsReset sql.NullTime
|
var statsReset sql.NullTime
|
||||||
|
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
@@ -280,152 +267,218 @@ func (PGStatDatabaseCollector) Update(ctx context.Context, instance *instance, c
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
datidLabel := "unknown"
|
||||||
|
if datid.Valid {
|
||||||
|
datidLabel = datid.String
|
||||||
|
}
|
||||||
|
datnameLabel := "unknown"
|
||||||
|
if datname.Valid {
|
||||||
|
datnameLabel = datname.String
|
||||||
|
}
|
||||||
|
|
||||||
|
numBackendsMetric := 0.0
|
||||||
|
if numBackends.Valid {
|
||||||
|
numBackendsMetric = numBackends.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseNumbackends,
|
statDatabaseNumbackends,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
numBackends,
|
numBackendsMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
xactCommitMetric := 0.0
|
||||||
|
if xactCommit.Valid {
|
||||||
|
xactCommitMetric = xactCommit.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseXactCommit,
|
statDatabaseXactCommit,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
xactCommit,
|
xactCommitMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
xactRollbackMetric := 0.0
|
||||||
|
if xactRollback.Valid {
|
||||||
|
xactRollbackMetric = xactRollback.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseXactRollback,
|
statDatabaseXactRollback,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
xactRollback,
|
xactRollbackMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
blksReadMetric := 0.0
|
||||||
|
if blksRead.Valid {
|
||||||
|
blksReadMetric = blksRead.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseBlksRead,
|
statDatabaseBlksRead,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
blksRead,
|
blksReadMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
blksHitMetric := 0.0
|
||||||
|
if blksHit.Valid {
|
||||||
|
blksHitMetric = blksHit.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseBlksHit,
|
statDatabaseBlksHit,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
blksHit,
|
blksHitMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tupReturnedMetric := 0.0
|
||||||
|
if tupReturned.Valid {
|
||||||
|
tupReturnedMetric = tupReturned.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseTupReturned,
|
statDatabaseTupReturned,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
tupReturned,
|
tupReturnedMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tupFetchedMetric := 0.0
|
||||||
|
if tupFetched.Valid {
|
||||||
|
tupFetchedMetric = tupFetched.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseTupFetched,
|
statDatabaseTupFetched,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
tupFetched,
|
tupFetchedMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tupInsertedMetric := 0.0
|
||||||
|
if tupInserted.Valid {
|
||||||
|
tupInsertedMetric = tupInserted.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseTupInserted,
|
statDatabaseTupInserted,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
tupInserted,
|
tupInsertedMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tupUpdatedMetric := 0.0
|
||||||
|
if tupUpdated.Valid {
|
||||||
|
tupUpdatedMetric = tupUpdated.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseTupUpdated,
|
statDatabaseTupUpdated,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
tupUpdated,
|
tupUpdatedMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tupDeletedMetric := 0.0
|
||||||
|
if tupDeleted.Valid {
|
||||||
|
tupDeletedMetric = tupDeleted.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseTupDeleted,
|
statDatabaseTupDeleted,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
tupDeleted,
|
tupDeletedMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
conflictsMetric := 0.0
|
||||||
|
if conflicts.Valid {
|
||||||
|
conflictsMetric = conflicts.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseConflicts,
|
statDatabaseConflicts,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
conflicts,
|
conflictsMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tempFilesMetric := 0.0
|
||||||
|
if tempFiles.Valid {
|
||||||
|
tempFilesMetric = tempFiles.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseTempFiles,
|
statDatabaseTempFiles,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
tempFiles,
|
tempFilesMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tempBytesMetric := 0.0
|
||||||
|
if tempBytes.Valid {
|
||||||
|
tempBytesMetric = tempBytes.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseTempBytes,
|
statDatabaseTempBytes,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
tempBytes,
|
tempBytesMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
deadlocksMetric := 0.0
|
||||||
|
if deadlocks.Valid {
|
||||||
|
deadlocksMetric = deadlocks.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseDeadlocks,
|
statDatabaseDeadlocks,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
deadlocks,
|
deadlocksMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
blkReadTimeMetric := 0.0
|
||||||
|
if blkReadTime.Valid {
|
||||||
|
blkReadTimeMetric = blkReadTime.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseBlkReadTime,
|
statDatabaseBlkReadTime,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
blkReadTime,
|
blkReadTimeMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
blkWriteTimeMetric := 0.0
|
||||||
|
if blkWriteTime.Valid {
|
||||||
|
blkWriteTimeMetric = blkWriteTime.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statDatabaseBlkWriteTime,
|
statDatabaseBlkWriteTime,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
blkWriteTime,
|
blkWriteTimeMetric,
|
||||||
datid,
|
datidLabel,
|
||||||
datname,
|
datnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
statsResetMetric := 0.0
|
||||||
if statsReset.Valid {
|
if statsReset.Valid {
|
||||||
ch <- prometheus.MustNewConstMetric(
|
statsResetMetric = float64(statsReset.Time.Unix())
|
||||||
statDatabaseStatsReset,
|
|
||||||
prometheus.CounterValue,
|
|
||||||
float64(statsReset.Time.Unix()),
|
|
||||||
datid,
|
|
||||||
datname,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ch <- prometheus.MustNewConstMetric(
|
|
||||||
statDatabaseStatsReset,
|
|
||||||
prometheus.CounterValue,
|
|
||||||
0,
|
|
||||||
datid,
|
|
||||||
datname,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
statDatabaseStatsReset,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
statsResetMetric,
|
||||||
|
datidLabel,
|
||||||
|
datnameLabel,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
359
collector/pg_stat_database_test.go
Normal file
359
collector/pg_stat_database_test.go
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
// Copyright 2023 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
package collector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
"github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPGStatDatabaseCollector(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
columns := []string{
|
||||||
|
"datid",
|
||||||
|
"datname",
|
||||||
|
"numbackends",
|
||||||
|
"xact_commit",
|
||||||
|
"xact_rollback",
|
||||||
|
"blks_read",
|
||||||
|
"blks_hit",
|
||||||
|
"tup_returned",
|
||||||
|
"tup_fetched",
|
||||||
|
"tup_inserted",
|
||||||
|
"tup_updated",
|
||||||
|
"tup_deleted",
|
||||||
|
"conflicts",
|
||||||
|
"temp_files",
|
||||||
|
"temp_bytes",
|
||||||
|
"deadlocks",
|
||||||
|
"blk_read_time",
|
||||||
|
"blk_write_time",
|
||||||
|
"stats_reset",
|
||||||
|
}
|
||||||
|
|
||||||
|
srT, err := time.Parse("2006-01-02 15:04:05.00000-07", "2023-05-25 17:10:42.81132-07")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error parsing time: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow(
|
||||||
|
"pid",
|
||||||
|
"postgres",
|
||||||
|
354,
|
||||||
|
4945,
|
||||||
|
289097744,
|
||||||
|
1242257,
|
||||||
|
3275602074,
|
||||||
|
89320867,
|
||||||
|
450139,
|
||||||
|
2034563757,
|
||||||
|
0,
|
||||||
|
2725688749,
|
||||||
|
23,
|
||||||
|
52,
|
||||||
|
74,
|
||||||
|
925,
|
||||||
|
16,
|
||||||
|
823,
|
||||||
|
srT)
|
||||||
|
|
||||||
|
mock.ExpectQuery(sanitizeQuery(statDatabaseQuery)).WillReturnRows(rows)
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGStatDatabaseCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGStatDatabaseCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_GAUGE, value: 354},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 4945},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 289097744},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 1242257},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 3275602074},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 89320867},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 450139},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 2034563757},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 2725688749},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 23},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 52},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 74},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 925},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 16},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 823},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 1685059842},
|
||||||
|
}
|
||||||
|
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPGStatDatabaseCollectorNullValues(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
columns := []string{
|
||||||
|
"datid",
|
||||||
|
"datname",
|
||||||
|
"numbackends",
|
||||||
|
"xact_commit",
|
||||||
|
"xact_rollback",
|
||||||
|
"blks_read",
|
||||||
|
"blks_hit",
|
||||||
|
"tup_returned",
|
||||||
|
"tup_fetched",
|
||||||
|
"tup_inserted",
|
||||||
|
"tup_updated",
|
||||||
|
"tup_deleted",
|
||||||
|
"conflicts",
|
||||||
|
"temp_files",
|
||||||
|
"temp_bytes",
|
||||||
|
"deadlocks",
|
||||||
|
"blk_read_time",
|
||||||
|
"blk_write_time",
|
||||||
|
"stats_reset",
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow(
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
mock.ExpectQuery(sanitizeQuery(statDatabaseQuery)).WillReturnRows(rows)
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGStatDatabaseCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGStatDatabaseCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestPGStatDatabaseCollectorRowLeakTest(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
columns := []string{
|
||||||
|
"datid",
|
||||||
|
"datname",
|
||||||
|
"numbackends",
|
||||||
|
"xact_commit",
|
||||||
|
"xact_rollback",
|
||||||
|
"blks_read",
|
||||||
|
"blks_hit",
|
||||||
|
"tup_returned",
|
||||||
|
"tup_fetched",
|
||||||
|
"tup_inserted",
|
||||||
|
"tup_updated",
|
||||||
|
"tup_deleted",
|
||||||
|
"conflicts",
|
||||||
|
"temp_files",
|
||||||
|
"temp_bytes",
|
||||||
|
"deadlocks",
|
||||||
|
"blk_read_time",
|
||||||
|
"blk_write_time",
|
||||||
|
"stats_reset",
|
||||||
|
}
|
||||||
|
|
||||||
|
srT, err := time.Parse("2006-01-02 15:04:05.00000-07", "2023-05-25 17:10:42.81132-07")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error parsing time: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow(
|
||||||
|
"pid",
|
||||||
|
"postgres",
|
||||||
|
354,
|
||||||
|
4945,
|
||||||
|
289097744,
|
||||||
|
1242257,
|
||||||
|
3275602074,
|
||||||
|
89320867,
|
||||||
|
450139,
|
||||||
|
2034563757,
|
||||||
|
0,
|
||||||
|
2725688749,
|
||||||
|
23,
|
||||||
|
52,
|
||||||
|
74,
|
||||||
|
925,
|
||||||
|
16,
|
||||||
|
823,
|
||||||
|
srT).
|
||||||
|
AddRow(
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock.ExpectQuery(sanitizeQuery(statDatabaseQuery)).WillReturnRows(rows)
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGStatDatabaseCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGStatDatabaseCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_GAUGE, value: 354},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 4945},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 289097744},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 1242257},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 3275602074},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 89320867},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 450139},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 2034563757},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 2725688749},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 23},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 52},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 74},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 925},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 16},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 823},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 1685059842},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "unknown", "datname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
@@ -15,6 +15,7 @@ package collector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@@ -101,48 +102,80 @@ func (PGStatStatementsCollector) Update(ctx context.Context, instance *instance,
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var user string
|
var user, datname, queryid sql.NullString
|
||||||
var datname string
|
var callsTotal, rowsTotal sql.NullInt64
|
||||||
var queryid string
|
var secondsTotal, blockReadSecondsTotal, blockWriteSecondsTotal sql.NullFloat64
|
||||||
var callsTotal int64
|
|
||||||
var secondsTotal float64
|
|
||||||
var rowsTotal int64
|
|
||||||
var blockReadSecondsTotal float64
|
|
||||||
var blockWriteSecondsTotal float64
|
|
||||||
|
|
||||||
if err := rows.Scan(&user, &datname, &queryid, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal); err != nil {
|
if err := rows.Scan(&user, &datname, &queryid, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userLabel := "unknown"
|
||||||
|
if user.Valid {
|
||||||
|
userLabel = user.String
|
||||||
|
}
|
||||||
|
datnameLabel := "unknown"
|
||||||
|
if datname.Valid {
|
||||||
|
datnameLabel = datname.String
|
||||||
|
}
|
||||||
|
queryidLabel := "unknown"
|
||||||
|
if queryid.Valid {
|
||||||
|
queryidLabel = queryid.String
|
||||||
|
}
|
||||||
|
|
||||||
|
callsTotalMetric := 0.0
|
||||||
|
if callsTotal.Valid {
|
||||||
|
callsTotalMetric = float64(callsTotal.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statSTatementsCallsTotal,
|
statSTatementsCallsTotal,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(callsTotal),
|
callsTotalMetric,
|
||||||
user, datname, queryid,
|
userLabel, datnameLabel, queryidLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
secondsTotalMetric := 0.0
|
||||||
|
if secondsTotal.Valid {
|
||||||
|
secondsTotalMetric = secondsTotal.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statStatementsSecondsTotal,
|
statStatementsSecondsTotal,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
secondsTotal,
|
secondsTotalMetric,
|
||||||
user, datname, queryid,
|
userLabel, datnameLabel, queryidLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rowsTotalMetric := 0.0
|
||||||
|
if rowsTotal.Valid {
|
||||||
|
rowsTotalMetric = float64(rowsTotal.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statStatementsRowsTotal,
|
statStatementsRowsTotal,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(rowsTotal),
|
rowsTotalMetric,
|
||||||
user, datname, queryid,
|
userLabel, datnameLabel, queryidLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
blockReadSecondsTotalMetric := 0.0
|
||||||
|
if blockReadSecondsTotal.Valid {
|
||||||
|
blockReadSecondsTotalMetric = blockReadSecondsTotal.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statStatementsBlockReadSecondsTotal,
|
statStatementsBlockReadSecondsTotal,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
blockReadSecondsTotal,
|
blockReadSecondsTotalMetric,
|
||||||
user, datname, queryid,
|
userLabel, datnameLabel, queryidLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
blockWriteSecondsTotalMetric := 0.0
|
||||||
|
if blockWriteSecondsTotal.Valid {
|
||||||
|
blockWriteSecondsTotalMetric = blockWriteSecondsTotal.Float64
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statStatementsBlockWriteSecondsTotal,
|
statStatementsBlockWriteSecondsTotal,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
blockWriteSecondsTotal,
|
blockWriteSecondsTotalMetric,
|
||||||
user, datname, queryid,
|
userLabel, datnameLabel, queryidLabel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
|
@@ -64,3 +64,46 @@ func TestPGStateStatementsCollector(t *testing.T) {
|
|||||||
t.Errorf("there were unfulfilled exceptions: %s", err)
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPGStateStatementsCollectorNull(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow(nil, nil, nil, nil, nil, nil, nil, nil)
|
||||||
|
mock.ExpectQuery(sanitizeQuery(pgStatStatementsQuery)).WillReturnRows(rows)
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGStatStatementsCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGStatStatementsCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -15,7 +15,7 @@ package collector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@@ -189,146 +189,235 @@ func (c *PGStatUserTablesCollector) Update(ctx context.Context, instance *instan
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var datname string
|
var datname, schemaname, relname sql.NullString
|
||||||
var schemaname string
|
var seqScan, seqTupRead, idxScan, idxTupFetch, nTupIns, nTupUpd, nTupDel, nTupHotUpd, nLiveTup, nDeadTup,
|
||||||
var relname string
|
nModSinceAnalyze, vacuumCount, autovacuumCount, analyzeCount, autoanalyzeCount sql.NullInt64
|
||||||
var seqScan int64
|
var lastVacuum, lastAutovacuum, lastAnalyze, lastAutoanalyze sql.NullTime
|
||||||
var seqTupRead int64
|
|
||||||
var idxScan int64
|
|
||||||
var idxTupFetch int64
|
|
||||||
var nTupIns int64
|
|
||||||
var nTupUpd int64
|
|
||||||
var nTupDel int64
|
|
||||||
var nTupHotUpd int64
|
|
||||||
var nLiveTup int64
|
|
||||||
var nDeadTup int64
|
|
||||||
var nModSinceAnalyze int64
|
|
||||||
var lastVacuum time.Time
|
|
||||||
var lastAutovacuum time.Time
|
|
||||||
var lastAnalyze time.Time
|
|
||||||
var lastAutoanalyze time.Time
|
|
||||||
var vacuumCount int64
|
|
||||||
var autovacuumCount int64
|
|
||||||
var analyzeCount int64
|
|
||||||
var autoanalyzeCount int64
|
|
||||||
|
|
||||||
if err := rows.Scan(&datname, &schemaname, &relname, &seqScan, &seqTupRead, &idxScan, &idxTupFetch, &nTupIns, &nTupUpd, &nTupDel, &nTupHotUpd, &nLiveTup, &nDeadTup, &nModSinceAnalyze, &lastVacuum, &lastAutovacuum, &lastAnalyze, &lastAutoanalyze, &vacuumCount, &autovacuumCount, &analyzeCount, &autoanalyzeCount); err != nil {
|
if err := rows.Scan(&datname, &schemaname, &relname, &seqScan, &seqTupRead, &idxScan, &idxTupFetch, &nTupIns, &nTupUpd, &nTupDel, &nTupHotUpd, &nLiveTup, &nDeadTup, &nModSinceAnalyze, &lastVacuum, &lastAutovacuum, &lastAnalyze, &lastAutoanalyze, &vacuumCount, &autovacuumCount, &analyzeCount, &autoanalyzeCount); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
datnameLabel := "unknown"
|
||||||
|
if datname.Valid {
|
||||||
|
datnameLabel = datname.String
|
||||||
|
}
|
||||||
|
schemanameLabel := "unknown"
|
||||||
|
if schemaname.Valid {
|
||||||
|
schemanameLabel = schemaname.String
|
||||||
|
}
|
||||||
|
relnameLabel := "unknown"
|
||||||
|
if relname.Valid {
|
||||||
|
relnameLabel = relname.String
|
||||||
|
}
|
||||||
|
|
||||||
|
seqScanMetric := 0.0
|
||||||
|
if seqScan.Valid {
|
||||||
|
seqScanMetric = float64(seqScan.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesSeqScan,
|
statUserTablesSeqScan,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(seqScan),
|
seqScanMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
seqTupReadMetric := 0.0
|
||||||
|
if seqTupRead.Valid {
|
||||||
|
seqTupReadMetric = float64(seqTupRead.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesSeqTupRead,
|
statUserTablesSeqTupRead,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(seqTupRead),
|
seqTupReadMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
idxScanMetric := 0.0
|
||||||
|
if idxScan.Valid {
|
||||||
|
idxScanMetric = float64(idxScan.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesIdxScan,
|
statUserTablesIdxScan,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(idxScan),
|
idxScanMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
idxTupFetchMetric := 0.0
|
||||||
|
if idxTupFetch.Valid {
|
||||||
|
idxTupFetchMetric = float64(idxTupFetch.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesIdxTupFetch,
|
statUserTablesIdxTupFetch,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(idxTupFetch),
|
idxTupFetchMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
nTupInsMetric := 0.0
|
||||||
|
if nTupIns.Valid {
|
||||||
|
nTupInsMetric = float64(nTupIns.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesNTupIns,
|
statUserTablesNTupIns,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(nTupIns),
|
nTupInsMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
nTupUpdMetric := 0.0
|
||||||
|
if nTupUpd.Valid {
|
||||||
|
nTupUpdMetric = float64(nTupUpd.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesNTupUpd,
|
statUserTablesNTupUpd,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(nTupUpd),
|
nTupUpdMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
nTupDelMetric := 0.0
|
||||||
|
if nTupDel.Valid {
|
||||||
|
nTupDelMetric = float64(nTupDel.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesNTupDel,
|
statUserTablesNTupDel,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(nTupDel),
|
nTupDelMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
nTupHotUpdMetric := 0.0
|
||||||
|
if nTupHotUpd.Valid {
|
||||||
|
nTupHotUpdMetric = float64(nTupHotUpd.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesNTupHotUpd,
|
statUserTablesNTupHotUpd,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(nTupHotUpd),
|
nTupHotUpdMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
nLiveTupMetric := 0.0
|
||||||
|
if nLiveTup.Valid {
|
||||||
|
nLiveTupMetric = float64(nLiveTup.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesNLiveTup,
|
statUserTablesNLiveTup,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
float64(nLiveTup),
|
nLiveTupMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
nDeadTupMetric := 0.0
|
||||||
|
if nDeadTup.Valid {
|
||||||
|
nDeadTupMetric = float64(nDeadTup.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesNDeadTup,
|
statUserTablesNDeadTup,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
float64(nDeadTup),
|
nDeadTupMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
nModSinceAnalyzeMetric := 0.0
|
||||||
|
if nModSinceAnalyze.Valid {
|
||||||
|
nModSinceAnalyzeMetric = float64(nModSinceAnalyze.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesNModSinceAnalyze,
|
statUserTablesNModSinceAnalyze,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
float64(nModSinceAnalyze),
|
nModSinceAnalyzeMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
lastVacuumMetric := 0.0
|
||||||
|
if lastVacuum.Valid {
|
||||||
|
lastVacuumMetric = float64(lastVacuum.Time.Unix())
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesLastVacuum,
|
statUserTablesLastVacuum,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
float64(lastVacuum.Unix()),
|
lastVacuumMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
lastAutovacuumMetric := 0.0
|
||||||
|
if lastAutovacuum.Valid {
|
||||||
|
lastAutovacuumMetric = float64(lastAutovacuum.Time.Unix())
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesLastAutovacuum,
|
statUserTablesLastAutovacuum,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
float64(lastAutovacuum.Unix()),
|
lastAutovacuumMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
lastAnalyzeMetric := 0.0
|
||||||
|
if lastAnalyze.Valid {
|
||||||
|
lastAnalyzeMetric = float64(lastAnalyze.Time.Unix())
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesLastAnalyze,
|
statUserTablesLastAnalyze,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
float64(lastAnalyze.Unix()),
|
lastAnalyzeMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
lastAutoanalyzeMetric := 0.0
|
||||||
|
if lastAutoanalyze.Valid {
|
||||||
|
lastAutoanalyzeMetric = float64(lastAutoanalyze.Time.Unix())
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesLastAutoanalyze,
|
statUserTablesLastAutoanalyze,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
float64(lastAutoanalyze.Unix()),
|
lastAutoanalyzeMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
vacuumCountMetric := 0.0
|
||||||
|
if vacuumCount.Valid {
|
||||||
|
vacuumCountMetric = float64(vacuumCount.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesVacuumCount,
|
statUserTablesVacuumCount,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(vacuumCount),
|
vacuumCountMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
autovacuumCountMetric := 0.0
|
||||||
|
if autovacuumCount.Valid {
|
||||||
|
autovacuumCountMetric = float64(autovacuumCount.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesAutovacuumCount,
|
statUserTablesAutovacuumCount,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(autovacuumCount),
|
autovacuumCountMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
analyzeCountMetric := 0.0
|
||||||
|
if analyzeCount.Valid {
|
||||||
|
analyzeCountMetric = float64(analyzeCount.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesAnalyzeCount,
|
statUserTablesAnalyzeCount,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(analyzeCount),
|
analyzeCountMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
autoanalyzeCountMetric := 0.0
|
||||||
|
if autoanalyzeCount.Valid {
|
||||||
|
autoanalyzeCountMetric = float64(autoanalyzeCount.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statUserTablesAutoanalyzeCount,
|
statUserTablesAutoanalyzeCount,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(autoanalyzeCount),
|
autoanalyzeCountMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -138,3 +138,102 @@ func TestPGStatUserTablesCollector(t *testing.T) {
|
|||||||
t.Errorf("there were unfulfilled exceptions: %s", err)
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPGStatUserTablesCollectorNullValues(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
columns := []string{
|
||||||
|
"datname",
|
||||||
|
"schemaname",
|
||||||
|
"relname",
|
||||||
|
"seq_scan",
|
||||||
|
"seq_tup_read",
|
||||||
|
"idx_scan",
|
||||||
|
"idx_tup_fetch",
|
||||||
|
"n_tup_ins",
|
||||||
|
"n_tup_upd",
|
||||||
|
"n_tup_del",
|
||||||
|
"n_tup_hot_upd",
|
||||||
|
"n_live_tup",
|
||||||
|
"n_dead_tup",
|
||||||
|
"n_mod_since_analyze",
|
||||||
|
"last_vacuum",
|
||||||
|
"last_autovacuum",
|
||||||
|
"last_analyze",
|
||||||
|
"last_autoanalyze",
|
||||||
|
"vacuum_count",
|
||||||
|
"autovacuum_count",
|
||||||
|
"analyze_count",
|
||||||
|
"autoanalyze_count"}
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow("postgres",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil)
|
||||||
|
mock.ExpectQuery(sanitizeQuery(statUserTablesQuery)).WillReturnRows(rows)
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGStatUserTablesCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGStatUserTablesCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "postgres", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -15,6 +15,7 @@ package collector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@@ -110,69 +111,111 @@ func (PGStatIOUserTablesCollector) Update(ctx context.Context, instance *instanc
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var datname string
|
var datname, schemaname, relname sql.NullString
|
||||||
var schemaname string
|
var heapBlksRead, heapBlksHit, idxBlksRead, idxBlksHit, toastBlksRead, toastBlksHit, tidxBlksRead, tidxBlksHit sql.NullInt64
|
||||||
var relname string
|
|
||||||
var heapBlksRead int64
|
|
||||||
var heapBlksHit int64
|
|
||||||
var idxBlksRead int64
|
|
||||||
var idxBlksHit int64
|
|
||||||
var toastBlksRead int64
|
|
||||||
var toastBlksHit int64
|
|
||||||
var tidxBlksRead int64
|
|
||||||
var tidxBlksHit int64
|
|
||||||
|
|
||||||
if err := rows.Scan(&datname, &schemaname, &relname, &heapBlksRead, &heapBlksHit, &idxBlksRead, &idxBlksHit, &toastBlksRead, &toastBlksHit, &tidxBlksRead, &tidxBlksHit); err != nil {
|
if err := rows.Scan(&datname, &schemaname, &relname, &heapBlksRead, &heapBlksHit, &idxBlksRead, &idxBlksHit, &toastBlksRead, &toastBlksHit, &tidxBlksRead, &tidxBlksHit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
datnameLabel := "unknown"
|
||||||
|
if datname.Valid {
|
||||||
|
datnameLabel = datname.String
|
||||||
|
}
|
||||||
|
schemanameLabel := "unknown"
|
||||||
|
if schemaname.Valid {
|
||||||
|
schemanameLabel = schemaname.String
|
||||||
|
}
|
||||||
|
relnameLabel := "unknown"
|
||||||
|
if relname.Valid {
|
||||||
|
relnameLabel = relname.String
|
||||||
|
}
|
||||||
|
|
||||||
|
heapBlksReadMetric := 0.0
|
||||||
|
if heapBlksRead.Valid {
|
||||||
|
heapBlksReadMetric = float64(heapBlksRead.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statioUserTablesHeapBlksRead,
|
statioUserTablesHeapBlksRead,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(heapBlksRead),
|
heapBlksReadMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
heapBlksHitMetric := 0.0
|
||||||
|
if heapBlksHit.Valid {
|
||||||
|
heapBlksHitMetric = float64(heapBlksHit.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statioUserTablesHeapBlksHit,
|
statioUserTablesHeapBlksHit,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(heapBlksHit),
|
heapBlksHitMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
idxBlksReadMetric := 0.0
|
||||||
|
if idxBlksRead.Valid {
|
||||||
|
idxBlksReadMetric = float64(idxBlksRead.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statioUserTablesIdxBlksRead,
|
statioUserTablesIdxBlksRead,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(idxBlksRead),
|
idxBlksReadMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
idxBlksHitMetric := 0.0
|
||||||
|
if idxBlksHit.Valid {
|
||||||
|
idxBlksHitMetric = float64(idxBlksHit.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statioUserTablesIdxBlksHit,
|
statioUserTablesIdxBlksHit,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(idxBlksHit),
|
idxBlksHitMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
toastBlksReadMetric := 0.0
|
||||||
|
if toastBlksRead.Valid {
|
||||||
|
toastBlksReadMetric = float64(toastBlksRead.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statioUserTablesToastBlksRead,
|
statioUserTablesToastBlksRead,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(toastBlksRead),
|
toastBlksReadMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
toastBlksHitMetric := 0.0
|
||||||
|
if toastBlksHit.Valid {
|
||||||
|
toastBlksHitMetric = float64(toastBlksHit.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statioUserTablesToastBlksHit,
|
statioUserTablesToastBlksHit,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(toastBlksHit),
|
toastBlksHitMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tidxBlksReadMetric := 0.0
|
||||||
|
if tidxBlksRead.Valid {
|
||||||
|
tidxBlksReadMetric = float64(tidxBlksRead.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statioUserTablesTidxBlksRead,
|
statioUserTablesTidxBlksRead,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(tidxBlksRead),
|
tidxBlksReadMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tidxBlksHitMetric := 0.0
|
||||||
|
if tidxBlksHit.Valid {
|
||||||
|
tidxBlksHitMetric = float64(tidxBlksHit.Int64)
|
||||||
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
statioUserTablesTidxBlksHit,
|
statioUserTablesTidxBlksHit,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
float64(tidxBlksHit),
|
tidxBlksHitMetric,
|
||||||
datname, schemaname, relname,
|
datnameLabel, schemanameLabel, relnameLabel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
|
@@ -88,3 +88,70 @@ func TestPGStatIOUserTablesCollector(t *testing.T) {
|
|||||||
t.Errorf("there were unfulfilled exceptions: %s", err)
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPGStatIOUserTablesCollectorNullValues(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening a stub db connection: %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
inst := &instance{db: db}
|
||||||
|
|
||||||
|
columns := []string{
|
||||||
|
"datname",
|
||||||
|
"schemaname",
|
||||||
|
"relname",
|
||||||
|
"heap_blks_read",
|
||||||
|
"heap_blks_hit",
|
||||||
|
"idx_blks_read",
|
||||||
|
"idx_blks_hit",
|
||||||
|
"toast_blks_read",
|
||||||
|
"toast_blks_hit",
|
||||||
|
"tidx_blks_read",
|
||||||
|
"tidx_blks_hit",
|
||||||
|
}
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow(nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil)
|
||||||
|
mock.ExpectQuery(sanitizeQuery(statioUserTablesQuery)).WillReturnRows(rows)
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGStatIOUserTablesCollector{}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGStatIOUserTablesCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"datname": "unknown", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "unknown", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "unknown", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "unknown", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "unknown", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "unknown", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "unknown", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datname": "unknown", "schemaname": "unknown", "relname": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
convey.Convey("Metrics comparison", t, func() {
|
||||||
|
for _, expect := range expected {
|
||||||
|
m := readMetric(<-ch)
|
||||||
|
convey.So(expect, convey.ShouldResemble, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("there were unfulfilled exceptions: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user