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

Move queries from queries.yaml to collectors #801

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
105a5de
Move pg_post_master query to collector
Sticksman May 31, 2023
a943807
move pg_replication to collector
Sticksman May 31, 2023
91bd052
Move pg_stat_user_tables query to collector
Sticksman May 31, 2023
11438ac
Forgot to finish adding all the extra vars
Sticksman May 31, 2023
8892838
Move statio_user_tables to collector
Sticksman May 31, 2023
1a46234
Move pg_stat_statements to collectors
Sticksman May 31, 2023
6d3bb7d
Move pg_process_idle to collectors
Sticksman May 31, 2023
d0f4c26
Remove all queries from queries.yaml
Sticksman May 31, 2023
610fffb
Fix pgreplication
Sticksman Jun 1, 2023
15d2426
Add is replica to replication metrics
Sticksman Jun 1, 2023
6a358b8
Add note to queries.yaml
Sticksman Jun 2, 2023
741d6e6
Change desc pattern to use variables instead of a map
Sticksman Jun 2, 2023
865c4c5
Add first set of tests + some helper functions cribbed from
Sticksman Jun 2, 2023
cd84f17
Add test for postmaster
Sticksman Jun 2, 2023
b588ec9
Add replication slot test
Sticksman Jun 2, 2023
090b41e
Add pg_replication_test
Sticksman Jun 2, 2023
b83c2a5
Add license header to all files
Sticksman Jun 2, 2023
5043cfa
Add pg_stat_bg_writer_test
Sticksman Jun 2, 2023
c647528
Add pg_stat_statements test
Sticksman Jun 2, 2023
b6c6f77
Add test for stat_user_tables
Sticksman Jun 3, 2023
d6b2bfa
Add statio_user_tables_test
Sticksman Jun 3, 2023
a317503
Add license
Sticksman Jun 3, 2023
55302d6
Fix up go mod
Sticksman Jun 3, 2023
09ce114
Remove replication test to see if it fixes tests
Sticksman Jun 3, 2023
add68f5
Revert "Remove replication test to see if it fixes tests"
Sticksman Jun 3, 2023
438140d
Move mock before calling query
Sticksman Jun 3, 2023
2784315
Fix var names
Sticksman Jun 5, 2023
cbc41cf
Add some var blocks and look for snake case
Sticksman Jun 5, 2023
d703cf7
Cleanup
Sticksman Jun 5, 2023
6ee7013
Merge branch 'master' into cleanup/move-queries-to-collector
Sticksman Jun 5, 2023
ac08ee6
Go mod tidy and some other fixes
Sticksman Jun 5, 2023
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
4 changes: 2 additions & 2 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const (
// Namespace for all metrics.
namespace = "pg"

defaultEnabled = true
// defaultDisabled = false
defaultEnabled = true
defaultDisabled = false
)

var (
Expand Down
56 changes: 56 additions & 0 deletions collector/collector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 (
"strings"

"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
)

type labelMap map[string]string

type MetricResult struct {
labels labelMap
value float64
metricType dto.MetricType
}

func readMetric(m prometheus.Metric) MetricResult {
pb := &dto.Metric{}
m.Write(pb)
labels := make(labelMap, len(pb.Label))
for _, v := range pb.Label {
labels[v.GetName()] = v.GetValue()
}
if pb.Gauge != nil {
return MetricResult{labels: labels, value: pb.GetGauge().GetValue(), metricType: dto.MetricType_GAUGE}
}
if pb.Counter != nil {
return MetricResult{labels: labels, value: pb.GetCounter().GetValue(), metricType: dto.MetricType_COUNTER}
}
if pb.Untyped != nil {
return MetricResult{labels: labels, value: pb.GetUntyped().GetValue(), metricType: dto.MetricType_UNTYPED}
}
panic("Unsupported metric type")
}

func sanitizeQuery(q string) string {
q = strings.Join(strings.Fields(q), " ")
q = strings.Replace(q, "(", "\\(", -1)
q = strings.Replace(q, ")", "\\)", -1)
q = strings.Replace(q, "*", "\\*", -1)
q = strings.Replace(q, "$", "\\$", -1)
return q
}
19 changes: 11 additions & 8 deletions collector/pg_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,15 @@ func NewPGDatabaseCollector(config collectorConfig) (Collector, error) {
}, nil
}

var pgDatabaseSizeDesc = prometheus.NewDesc(
"pg_database_size_bytes",
"Disk space used by the database",
[]string{"datname"}, nil,
var (
pgDatabaseSizeDesc = prometheus.NewDesc(
"pg_database_size_bytes",
"Disk space used by the database",
[]string{"datname"}, nil,
)

pgDatabaseQuery = "SELECT pg_database.datname FROM pg_database;"
pgDatabaseSizeQuery = "SELECT pg_database_size($1)"
)

// Update implements Collector and exposes database size.
Expand All @@ -58,9 +63,7 @@ var pgDatabaseSizeDesc = prometheus.NewDesc(
func (c PGDatabaseCollector) Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error {
// Query the list of databases
rows, err := db.QueryContext(ctx,
`SELECT pg_database.datname
FROM pg_database;
`,
pgDatabaseQuery,
)
if err != nil {
return err
Expand Down Expand Up @@ -88,7 +91,7 @@ func (c PGDatabaseCollector) Update(ctx context.Context, db *sql.DB, ch chan<- p
// Query the size of the databases
for _, datname := range databases {
var size int64
err = db.QueryRowContext(ctx, "SELECT pg_database_size($1)", datname).Scan(&size)
err = db.QueryRowContext(ctx, pgDatabaseSizeQuery, datname).Scan(&size)
if err != nil {
return err
}
Expand Down
59 changes: 59 additions & 0 deletions collector/pg_database_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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"

"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 TestPGDatabaseCollector(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Error opening a stub db connection: %s", err)
}
defer db.Close()

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(1024))

ch := make(chan prometheus.Metric)
go func() {
defer close(ch)
c := PGDatabaseCollector{}
if err := c.Update(context.Background(), db, ch); err != nil {
t.Errorf("Error calling PGDatabaseCollector.Update: %s", err)
}
}()

expected := []MetricResult{
{labels: labelMap{"datname": "postgres"}, value: 1024, 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)
}
}
58 changes: 58 additions & 0 deletions collector/pg_postmaster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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"
"database/sql"

"github.com/prometheus/client_golang/prometheus"
)

func init() {
registerCollector("postmaster", defaultEnabled, NewPGPostmasterCollector)
}

type PGPostmasterCollector struct {
}

func NewPGPostmasterCollector(collectorConfig) (Collector, error) {
return &PGPostmasterCollector{}, nil
}

var (
pgPostMasterStartTimeSeconds = prometheus.NewDesc(
"pg_postmaster_start_time_seconds",
"Time at which postmaster started",
[]string{}, nil,
)

pgPostmasterQuery = "SELECT pg_postmaster_start_time from pg_postmaster_start_time();"
)

func (c *PGPostmasterCollector) Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error {
row := db.QueryRowContext(ctx,
pgPostmasterQuery)

var startTimeSeconds float64
err := row.Scan(&startTimeSeconds)
if err != nil {
return err
}
ch <- prometheus.MustNewConstMetric(
pgPostMasterStartTimeSeconds,
prometheus.GaugeValue, startTimeSeconds,
)
return nil
}
57 changes: 57 additions & 0 deletions collector/pg_postmaster_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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"

"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 TestPgPostmasterCollector(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Error opening a stub db connection: %s", err)
}
defer db.Close()

mock.ExpectQuery(sanitizeQuery(pgPostmasterQuery)).WillReturnRows(sqlmock.NewRows([]string{"pg_postmaster_start_time"}).
AddRow(1685739904))

ch := make(chan prometheus.Metric)
go func() {
defer close(ch)
c := PGPostmasterCollector{}

if err := c.Update(context.Background(), db, ch); err != nil {
t.Errorf("Error calling PGPostmasterCollector.Update: %s", err)
}
}()

expected := []MetricResult{
{labels: labelMap{}, value: 1685739904, 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)
}
}
106 changes: 106 additions & 0 deletions collector/pg_process_idle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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"
"database/sql"

"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
)

func init() {
registerCollector("statements", defaultEnabled, NewPGProcessIdleCollector)
}

type PGProcessIdleCollector struct {
log log.Logger
}

const processIdleSubsystem = "process_idle"

func NewPGProcessIdleCollector(config collectorConfig) (Collector, error) {
return &PGProcessIdleCollector{log: config.logger}, nil
}

var pgProcessIdleSeconds = prometheus.NewDesc(
prometheus.BuildFQName(namespace, processIdleSubsystem, "seconds"),
"Idle time of server processes",
[]string{"application_name"},
prometheus.Labels{},
)

func (PGProcessIdleCollector) Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error {
row := db.QueryRowContext(ctx,
`WITH
metrics AS (
SELECT
application_name,
SUM(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - state_change))::bigint)::float AS process_idle_seconds_sum,
COUNT(*) AS process_idle_seconds_count
FROM pg_stat_activity
WHERE state = 'idle'
GROUP BY application_name
),
buckets AS (
SELECT
application_name,
le,
SUM(
CASE WHEN EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - state_change)) <= le
THEN 1
ELSE 0
END
)::bigint AS bucket
FROM
pg_stat_activity,
UNNEST(ARRAY[1, 2, 5, 15, 30, 60, 90, 120, 300]) AS le
GROUP BY application_name, le
ORDER BY application_name, le
)
SELECT
application_name,
process_idle_seconds_sum as seconds_sum,
process_idle_seconds_count as seconds_count,
ARRAY_AGG(le) AS seconds,
ARRAY_AGG(bucket) AS seconds_bucket
FROM metrics JOIN buckets USING (application_name)
GROUP BY 1, 2, 3;`)

var applicationName string
var secondsSum int64
var secondsCount uint64
var seconds []int64
var secondsBucket []uint64

err := row.Scan(&applicationName, &secondsSum, &secondsCount, &seconds, &secondsBucket)

var buckets = make(map[float64]uint64, len(seconds))
for i, second := range seconds {
if i >= len(secondsBucket) {
break
}
buckets[float64(second)] = secondsBucket[i]
}
if err != nil {
return err
}
ch <- prometheus.MustNewConstHistogram(
pgProcessIdleSeconds,
secondsCount, float64(secondsSum), buckets,
applicationName,
)
return nil
}
Loading