Skip to content

Refactor exporter so that it can be used as a library. #157

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

Closed
wants to merge 2 commits into from
Closed
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ fmt: tools
postgres_exporter_integration_test: $(GO_SRC)
CGO_ENABLED=0 go test -c -tags integration \
-a -ldflags "-extldflags '-static' -X main.Version=$(VERSION)" \
-o postgres_exporter_integration_test -cover -covermode count .
-o postgres_exporter_integration_test -cover -covermode count ./collector/...

test: tools
@mkdir -p $(COVERDIR)
Expand Down
135 changes: 135 additions & 0 deletions collector/pg_setting.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package collector

import (
"database/sql"
"errors"
"fmt"
"math"
"strconv"
"strings"

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

// Query the pg_settings view containing runtime variables
func querySettings(ch chan<- prometheus.Metric, db *sql.DB) error {
log.Debugln("Querying pg_setting view")

// pg_settings docs: https://www.postgresql.org/docs/current/static/view-pg-settings.html
//
// NOTE: If you add more vartypes here, you must update the supported
// types in normaliseUnit() below
query := "SELECT name, setting, COALESCE(unit, ''), short_desc, vartype FROM pg_settings WHERE vartype IN ('bool', 'integer', 'real');"

rows, err := db.Query(query)
if err != nil {
return errors.New(fmt.Sprintln("Error running query on database: ", namespace, err))
}
defer rows.Close() // nolint: errcheck

for rows.Next() {
s := &pgSetting{}
err = rows.Scan(&s.name, &s.setting, &s.unit, &s.shortDesc, &s.vartype)
if err != nil {
return errors.New(fmt.Sprintln("Error retrieving rows:", namespace, err))
}

ch <- s.metric()
}

return nil
}

// pgSetting is represents a PostgreSQL runtime variable as returned by the
// pg_settings view.
type pgSetting struct {
name, setting, unit, shortDesc, vartype string
}

func (s *pgSetting) metric() prometheus.Metric {
var (
err error
name = strings.Replace(s.name, ".", "_", -1)
unit = s.unit // nolint: ineffassign
shortDesc = s.shortDesc
subsystem = "settings"
val float64
)

switch s.vartype {
case "bool":
if s.setting == "on" {
val = 1
}
case "integer", "real":
if val, unit, err = s.normaliseUnit(); err != nil {
// Panic, since we should recognise all units
// and don't want to silently exlude metrics
panic(err)
}

if len(unit) > 0 {
name = fmt.Sprintf("%s_%s", name, unit)
shortDesc = fmt.Sprintf("%s [Units converted to %s.]", shortDesc, unit)
}
default:
// Panic because we got a type we didn't ask for
panic(fmt.Sprintf("Unsupported vartype %q", s.vartype))
}

desc := newDesc(subsystem, name, shortDesc)
return prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, val)
}

func (s *pgSetting) normaliseUnit() (val float64, unit string, err error) {
val, err = strconv.ParseFloat(s.setting, 64)
if err != nil {
return val, unit, fmt.Errorf("Error converting setting %q value %q to float: %s", s.name, s.setting, err)
}

// Units defined in: https://www.postgresql.org/docs/current/static/config-setting.html
switch s.unit {
case "":
return
case "ms", "s", "min", "h", "d":
unit = "seconds"
case "kB", "MB", "GB", "TB", "8kB", "16kB", "16MB":
unit = "bytes"
default:
err = fmt.Errorf("Unknown unit for runtime variable: %q", s.unit)
return
}

// -1 is special, don't modify the value
if val == -1 {
return
}

switch s.unit {
case "ms":
val /= 1000
case "min":
val *= 60
case "h":
val *= 60 * 60
case "d":
val *= 60 * 60 * 24
case "kB":
val *= math.Pow(2, 10)
case "MB":
val *= math.Pow(2, 20)
case "GB":
val *= math.Pow(2, 30)
case "TB":
val *= math.Pow(2, 40)
case "8kB":
val *= math.Pow(2, 13)
case "16kB":
val *= math.Pow(2, 14)
case "16MB":
val *= math.Pow(2, 24)
}

return
}
223 changes: 223 additions & 0 deletions collector/pg_setting_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// +build !integration

package collector

import (
dto "github.com/prometheus/client_model/go"
. "gopkg.in/check.v1"
)

type PgSettingSuite struct{}

var _ = Suite(&PgSettingSuite{})

var fixtures = []fixture{
{
p: pgSetting{
name: "seconds_fixture_metric",
setting: "5",
unit: "s",
shortDesc: "Foo foo foo",
vartype: "integer",
},
n: normalised{
val: 5,
unit: "seconds",
err: "",
},
d: "Desc{fqName: \"pg_settings_seconds_fixture_metric_seconds\", help: \"Foo foo foo [Units converted to seconds.]\", constLabels: {}, variableLabels: []}",
v: 5,
},
{
p: pgSetting{
name: "milliseconds_fixture_metric",
setting: "5000",
unit: "ms",
shortDesc: "Foo foo foo",
vartype: "integer",
},
n: normalised{
val: 5,
unit: "seconds",
err: "",
},
d: "Desc{fqName: \"pg_settings_milliseconds_fixture_metric_seconds\", help: \"Foo foo foo [Units converted to seconds.]\", constLabels: {}, variableLabels: []}",
v: 5,
},
{
p: pgSetting{
name: "eight_kb_fixture_metric",
setting: "17",
unit: "8kB",
shortDesc: "Foo foo foo",
vartype: "integer",
},
n: normalised{
val: 139264,
unit: "bytes",
err: "",
},
d: "Desc{fqName: \"pg_settings_eight_kb_fixture_metric_bytes\", help: \"Foo foo foo [Units converted to bytes.]\", constLabels: {}, variableLabels: []}",
v: 139264,
},
{
p: pgSetting{
name: "16_kb_real_fixture_metric",
setting: "3.0",
unit: "16kB",
shortDesc: "Foo foo foo",
vartype: "real",
},
n: normalised{
val: 49152,
unit: "bytes",
err: "",
},
d: "Desc{fqName: \"pg_settings_16_kb_real_fixture_metric_bytes\", help: \"Foo foo foo [Units converted to bytes.]\", constLabels: {}, variableLabels: []}",
v: 49152,
},
{
p: pgSetting{
name: "16_mb_real_fixture_metric",
setting: "3.0",
unit: "16MB",
shortDesc: "Foo foo foo",
vartype: "real",
},
n: normalised{
val: 5.0331648e+07,
unit: "bytes",
err: "",
},
d: "Desc{fqName: \"pg_settings_16_mb_real_fixture_metric_bytes\", help: \"Foo foo foo [Units converted to bytes.]\", constLabels: {}, variableLabels: []}",
v: 5.0331648e+07,
},
{
p: pgSetting{
name: "bool_on_fixture_metric",
setting: "on",
unit: "",
shortDesc: "Foo foo foo",
vartype: "bool",
},
n: normalised{
val: 1,
unit: "",
err: "",
},
d: "Desc{fqName: \"pg_settings_bool_on_fixture_metric\", help: \"Foo foo foo\", constLabels: {}, variableLabels: []}",
v: 1,
},
{
p: pgSetting{
name: "bool_off_fixture_metric",
setting: "off",
unit: "",
shortDesc: "Foo foo foo",
vartype: "bool",
},
n: normalised{
val: 0,
unit: "",
err: "",
},
d: "Desc{fqName: \"pg_settings_bool_off_fixture_metric\", help: \"Foo foo foo\", constLabels: {}, variableLabels: []}",
v: 0,
},
{
p: pgSetting{
name: "special_minus_one_value",
setting: "-1",
unit: "d",
shortDesc: "foo foo foo",
vartype: "integer",
},
n: normalised{
val: -1,
unit: "seconds",
err: "",
},
d: "Desc{fqName: \"pg_settings_special_minus_one_value_seconds\", help: \"foo foo foo [Units converted to seconds.]\", constLabels: {}, variableLabels: []}",
v: -1,
},
{
p: pgSetting{
name: "rds.rds_superuser_reserved_connections",
setting: "2",
unit: "",
shortDesc: "Sets the number of connection slots reserved for rds_superusers.",
vartype: "integer",
},
n: normalised{
val: 2,
unit: "",
err: "",
},
d: "Desc{fqName: \"pg_settings_rds_rds_superuser_reserved_connections\", help: \"Sets the number of connection slots reserved for rds_superusers.\", constLabels: {}, variableLabels: []}",
v: 2,
},
{
p: pgSetting{
name: "unknown_unit",
setting: "10",
unit: "nonexistent",
shortDesc: "foo foo foo",
vartype: "integer",
},
n: normalised{
val: 10,
unit: "",
err: `Unknown unit for runtime variable: "nonexistent"`,
},
},
}

func (s *PgSettingSuite) TestNormaliseUnit(c *C) {
for _, f := range fixtures {
switch f.p.vartype {
case "integer", "real":
val, unit, err := f.p.normaliseUnit()

c.Check(val, Equals, f.n.val)
c.Check(unit, Equals, f.n.unit)

if err == nil {
c.Check("", Equals, f.n.err)
} else {
c.Check(err.Error(), Equals, f.n.err)
}
}
}
}

func (s *PgSettingSuite) TestMetric(c *C) {
defer func() {
if r := recover(); r != nil {
if r.(error).Error() != `Unknown unit for runtime variable: "nonexistent"` {
panic(r)
}
}
}()

for _, f := range fixtures {
d := &dto.Metric{}
m := f.p.metric()
m.Write(d) // nolint: errcheck

c.Check(m.Desc().String(), Equals, f.d)
c.Check(d.GetGauge().GetValue(), Equals, f.v)
}
}

type normalised struct {
val float64
unit string
err string
}

type fixture struct {
p pgSetting
n normalised
d string
v float64
}
Loading