Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add prepared statement metrics #194

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,19 @@ var (
"paused": {GAUGE, "paused", 1, "1 if this database is currently paused, else 0"},
"disabled": {GAUGE, "disabled", 1, "1 if this database is currently disabled, else 0"},
},
"stats": {
"database": {LABEL, "N/A", 1, "N/A"},
"total_query_count": {COUNTER, "queries_pooled_total", 1, "Total number of SQL queries pooled"},
"total_query_time": {COUNTER, "queries_duration_seconds_total", 1e-6, "Total number of seconds spent by pgbouncer when actively connected to PostgreSQL, executing queries"},
"total_received": {COUNTER, "received_bytes_total", 1, "Total volume in bytes of network traffic received by pgbouncer, shown as bytes"},
"total_requests": {COUNTER, "queries_total", 1, "Total number of SQL requests pooled by pgbouncer, shown as requests"},
"total_sent": {COUNTER, "sent_bytes_total", 1, "Total volume in bytes of network traffic sent by pgbouncer, shown as bytes"},
"total_wait_time": {COUNTER, "client_wait_seconds_total", 1e-6, "Time spent by clients waiting for a server in seconds"},
"total_xact_count": {COUNTER, "sql_transactions_pooled_total", 1, "Total number of SQL transactions pooled"},
"total_xact_time": {COUNTER, "server_in_transaction_seconds_total", 1e-6, "Total number of seconds spent by pgbouncer when connected to PostgreSQL in a transaction, either idle in transaction or executing queries"},
"stats_totals": {
"database": {LABEL, "N/A", 1, "N/A"},
"query_count": {COUNTER, "queries_pooled_total", 1, "Total number of SQL queries pooled"},
"query_time": {COUNTER, "queries_duration_seconds_total", 1e-6, "Total number of seconds spent by pgbouncer when actively connected to PostgreSQL, executing queries"},
"bytes_received": {COUNTER, "received_bytes_total", 1, "Total volume in bytes of network traffic received by pgbouncer, shown as bytes"},
"requests": {COUNTER, "queries_total", 1, "Total number of SQL requests pooled by pgbouncer, shown as requests"},
"bytes_sent": {COUNTER, "sent_bytes_total", 1, "Total volume in bytes of network traffic sent by pgbouncer, shown as bytes"},
"wait_time": {COUNTER, "client_wait_seconds_total", 1e-6, "Time spent by clients waiting for a server in seconds"},
"xact_count": {COUNTER, "sql_transactions_pooled_total", 1, "Total number of SQL transactions pooled"},
"xact_time": {COUNTER, "server_in_transaction_seconds_total", 1e-6, "Total number of seconds spent by pgbouncer when connected to PostgreSQL in a transaction, either idle in transaction or executing queries"},
"client_parse_count": {COUNTER, "client_parses_total", 1, "Total number of prepared statement Parse messages received from clients"},
"server_parse_count": {COUNTER, "server_parses_total", 1, "Total number of prepared statement Parse messages sent by pgbouncer to PostgreSQL"},
"bind_count": {COUNTER, "binds_total", 1, "Total number of prepared statements readied for execution with a Bind message"},
},
"pools": {
"database": {LABEL, "N/A", 1, "N/A"},
Expand Down
79 changes: 79 additions & 0 deletions collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,82 @@ func TestQueryShowConfig(t *testing.T) {
t.Errorf("there were unfulfilled exceptions: %s", err)
}
}

func TestQueryShowDatabases(t *testing.T) {
rows := sqlmock.NewRows([]string{"name", "host", "port", "database", "pool_size"}).
AddRow("pg0_db", "10.10.10.1", "5432", "pg0", 20)

expected := []MetricResult{
{labels: labelMap{"name": "pg0_db", "host": "10.10.10.1", "port": "5432", "database": "pg0", "force_user": "", "pool_mode": ""}, metricType: dto.MetricType_GAUGE, value: 20},
}

testQueryNamespaceMapping(t, "databases", rows, expected)
}

func TestQueryShowStats(t *testing.T) {
// columns are listed in the order PgBouncers exposes them, a value of -1 means pgbouncer_exporter does not expose this value as a metric
rows := sqlmock.NewRows([]string{"database",
"server_assignment_count",
"xact_count", "query_count", "bytes_received", "bytes_sent",
"xact_time", "query_time", "wait_time", "client_parse_count", "server_parse_count", "bind_count"}).
AddRow("pg0", -1, 10, 40, 220, 460, 6, 8, 9, 5, 55, 555)

// expected metrics are returned in the same order as the colums
expected := []MetricResult{
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 10}, // xact_count
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 40}, // query_count
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 220}, // bytes_received
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 460}, // bytes_sent
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 6e-6}, // xact_time
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 8e-6}, // query_time
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 9e-6}, // wait_time
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 5}, // client_parse_count
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 55}, // server_parse_count
{labels: labelMap{"database": "pg0"}, metricType: dto.MetricType_COUNTER, value: 555}, // bind_count
}

testQueryNamespaceMapping(t, "stats_totals", rows, expected)
}

func TestQueryShowPools(t *testing.T) {
rows := sqlmock.NewRows([]string{"database", "user", "cl_active"}).
AddRow("pg0", "postgres", 2)

expected := []MetricResult{
{labels: labelMap{"database": "pg0", "user": "postgres"}, metricType: dto.MetricType_GAUGE, value: 2},
}

testQueryNamespaceMapping(t, "pools", rows, expected)
}

func testQueryNamespaceMapping(t *testing.T, namespaceMapping string, rows *sqlmock.Rows, expected []MetricResult) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Error opening a stub db connection: %s", err)
}
defer db.Close()

mock.ExpectQuery("SHOW " + namespaceMapping + ";").WillReturnRows(rows)

logger := slog.Default()

metricMap := makeDescMap(metricMaps, namespace, logger)

ch := make(chan prometheus.Metric)
go func() {
defer close(ch)
if _, err := queryNamespaceMapping(ch, db, namespaceMapping, metricMap[namespaceMapping], logger); err != nil {
t.Errorf("Error running queryNamespaceMapping: %s", err)
}
}()

convey.Convey("Metrics comparison", t, func() {
for _, expect := range expected {
m := readMetric(<-ch)
convey.So(m, convey.ShouldResemble, expect)
}
})
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled exceptions: %s", err)
}
}