├── .gitignore ├── .goreleaser.yml ├── .travis.yml ├── Dockerfile ├── Makefile ├── README.md ├── config.yml ├── config ├── config.go ├── config_test.go └── testdata │ ├── invalid-duplicated-config.yml │ ├── invalid-empty-name-config.yml │ ├── invalid-empty-url-config.yml │ ├── invalid-yaml-config.yml │ └── valid-config.yml ├── gauges ├── backends.go ├── backends_test.go ├── basics.go ├── basics_test.go ├── bgwriter.go ├── bgwriter_test.go ├── deadtuples.go ├── deadtuples_test.go ├── gauge.go ├── gauge_test.go ├── heap.go ├── heap_test.go ├── indexes.go ├── indexes_test.go ├── locks.go ├── locks_test.go ├── logical_replication.go ├── logical_replication_test.go ├── read_throughput.go ├── read_throughput_test.go ├── replication.go ├── replication_test.go ├── slow_queries.go ├── slow_queries_test.go ├── table_rows.go ├── table_rows_test.go ├── table_sizes.go ├── table_sizes_test.go ├── tables.go ├── tables_test.go ├── transactions.go ├── transactions_test.go ├── vacuum.go ├── vacuum_test.go ├── write_throughput.go └── write_throughput_test.go ├── go.mod ├── go.sum ├── main.go └── postgres ├── version.go └── version_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | coverage.out 3 | test_config.yml 4 | postgresql_exporter 5 | dist 6 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: postgresql_exporter 2 | release: 3 | github: 4 | owner: ContaAzul 5 | name: postgresql_exporter 6 | builds: 7 | - goos: 8 | - linux 9 | - darwin 10 | goarch: 11 | - amd64 12 | env: 13 | - CGO_ENABLED=0 14 | checksum: 15 | name_template: '{{ .ProjectName }}_checksums.txt' 16 | archives: 17 | - id: postgresql_exporter 18 | name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}' 19 | files: 20 | - config.yml 21 | changelog: 22 | filters: 23 | exclude: 24 | - '^Merge (remote|branch|pull)' 25 | dockers: 26 | - image_templates: 27 | - 'caninjas/postgresql_exporter:latest' 28 | - 'caninjas/postgresql_exporter:{{ .Tag }}' 29 | - 'caninjas/postgresql_exporter:v{{ .Major }}' 30 | - 'caninjas/postgresql_exporter:v{{ .Major }}.{{ .Minor }}' 31 | extra_files: 32 | - config.yml 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: go 4 | go: "1.10" 5 | addons: 6 | postgresql: "9.6" 7 | before_script: 8 | - psql -U travis -c "ALTER SYSTEM SET wal_level TO 'logical';" 9 | - psql -U travis -c "ALTER SYSTEM SET max_replication_slots TO '10';" 10 | - sudo /etc/init.d/postgresql restart 11 | services: 12 | - docker 13 | - postgresql 14 | install: make setup 15 | script: make ci 16 | after_success: 17 | - docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" 18 | - bash <(curl -s https://codecov.io/bash) 19 | notifications: 20 | email: false 21 | deploy: 22 | - provider: script 23 | skip_cleanup: true 24 | script: curl -sL http://git.io/goreleaser | bash 25 | on: 26 | tags: true 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as build 2 | RUN apk --no-cache add ca-certificates make 3 | WORKDIR /go/src/app 4 | COPY . . 5 | 6 | FROM scratch 7 | EXPOSE 9111 8 | WORKDIR / 9 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 10 | COPY --from=build /go/src/app/postgresql_exporter / 11 | ENTRYPOINT ["./postgresql_exporter"] 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | CGO_ENABLED=0 GO111MODULES=on go build -ldflags="-s -w" -o postgresql_exporter . 3 | 4 | test: 5 | GOCACHE=off GO111MODULES=on go test -v ./... 6 | 7 | lint: 8 | gometalinter.v2 --vendor --deadline=60s ./... 9 | 10 | ci: test 11 | 12 | .DEFAULT_GOAL := build 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: Deprecated - Use https://github.com/prometheus-community/postgres_exporter :warning: 2 | 3 | 4 | # postgresql_exporter 5 | 6 | A Prometheus exporter for some postgresql metrics. 7 | 8 | ## Getting Started 9 | 10 | You can add as many database connections as you like to the 11 | `config.yml` file, and run it with: 12 | 13 | ```console 14 | ./postgresql_exporter -config=my/config.yml 15 | ``` 16 | 17 | Then you can add hostname:9111 to the prometheus scrapes config: 18 | 19 | ```yml 20 | - job_name: 'postgresql' 21 | static_configs: 22 | - targets: ['localhost:9111'] 23 | ``` 24 | 25 | And voilá, metrics should be there and you should be able to query, 26 | graph and alert on them. 27 | 28 | ## Setting up a restricted monitoring user 29 | 30 | By default some stat views like pg_stat_statements and pg_stat_activity doesn't allow viewing queries run by other users, unless you are a database superuser. Since you probably don't want monitoring to run as a superuser, you can setup a separate monitoring user like this: 31 | 32 | ```sql 33 | CREATE EXTENSION IF NOT EXISTS pg_stat_statements; 34 | CREATE EXTENSION IF NOT EXISTS pgstattuple; 35 | 36 | CREATE SCHEMA monitoring; 37 | 38 | CREATE OR REPLACE FUNCTION monitoring.pgstattuple(IN relname text, 39 | OUT table_len BIGINT, 40 | OUT tuple_count BIGINT, 41 | OUT tuple_len BIGINT, 42 | OUT tuple_percent FLOAT8, 43 | OUT dead_tuple_count BIGINT, 44 | OUT dead_tuple_len BIGINT, 45 | OUT dead_tuple_percent FLOAT8, 46 | OUT free_space BIGINT, 47 | OUT free_percent FLOAT8) AS $$ 48 | SELECT 49 | table_len, 50 | tuple_count, 51 | tuple_len, 52 | tuple_percent, 53 | dead_tuple_count, 54 | dead_tuple_len, 55 | dead_tuple_percent, 56 | free_space, 57 | free_percent 58 | FROM public.pgstattuple(relname) 59 | $$ LANGUAGE SQL VOLATILE SECURITY DEFINER; 60 | 61 | CREATE OR REPLACE FUNCTION monitoring.pgstattuple_approx(IN relname text, 62 | OUT table_len BIGINT, 63 | OUT scanned_percent FLOAT8, 64 | OUT approx_tuple_count BIGINT, 65 | OUT approx_tuple_len BIGINT, 66 | OUT approx_tuple_percent FLOAT8, 67 | OUT dead_tuple_count BIGINT, 68 | OUT dead_tuple_len BIGINT, 69 | OUT dead_tuple_percent FLOAT8, 70 | OUT approx_free_space BIGINT, 71 | OUT approx_free_percent FLOAT8) AS $$ 72 | SELECT 73 | table_len, 74 | scanned_percent, 75 | approx_tuple_count, 76 | approx_tuple_len, 77 | approx_tuple_percent, 78 | dead_tuple_count, 79 | dead_tuple_len, 80 | dead_tuple_percent, 81 | approx_free_space, 82 | approx_free_percent 83 | FROM public.pgstattuple_approx(relname) 84 | $$ LANGUAGE SQL VOLATILE SECURITY DEFINER; 85 | 86 | CREATE ROLE monitoring WITH LOGIN PASSWORD 'mypassword' 87 | CONNECTION LIMIT 5 IN ROLE pg_monitor; 88 | ALTER ROLE monitoring SET search_path = monitoring, pg_catalog, public; 89 | 90 | GRANT CONNECT ON DATABASE {{database_name}} TO monitoring; 91 | GRANT USAGE ON SCHEMA monitoring TO monitoring; 92 | GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA monitoring TO monitoring; 93 | ``` 94 | 95 | Note that these statements must be run as a superuser (to create the SECURITY DEFINER function), but from here onwards you can use the `monitoring` user instead. The exporter will automatically use the helper methods if they exist in the `monitoring` schema, otherwise data will be fetched directly. 96 | 97 | The default role `pg_monitor` only has in PostgreSQL 10 or later (See more details [here](https://www.postgresql.org/docs/10/static/default-roles.html)). If you're running Postgres 9.6 or lower you need to create some other helper methods in the `monitoring` schema: 98 | 99 | ```sql 100 | CREATE OR REPLACE FUNCTION monitoring.pg_stat_activity() RETURNS SETOF pg_stat_activity AS $$ 101 | SELECT * FROM pg_catalog.pg_stat_activity; 102 | $$ LANGUAGE sql VOLATILE SECURITY DEFINER; 103 | 104 | CREATE VIEW monitoring.pg_stat_activity AS 105 | SELECT * FROM monitoring.pg_stat_activity(); 106 | 107 | CREATE OR REPLACE FUNCTION monitoring.pg_stat_statements() RETURNS SETOF pg_stat_statements AS $$ 108 | SELECT * FROM public.pg_stat_statements; 109 | $$ LANGUAGE sql VOLATILE SECURITY DEFINER; 110 | 111 | CREATE VIEW monitoring.pg_stat_statements AS 112 | SELECT * FROM monitoring.pg_stat_statements(); 113 | 114 | CREATE OR REPLACE FUNCTION monitoring.pg_stat_replication() RETURNS SETOF pg_stat_replication AS $$ 115 | SELECT * FROM pg_catalog.pg_stat_replication; 116 | $$ LANGUAGE sql VOLATILE SECURITY DEFINER; 117 | 118 | CREATE VIEW monitoring.pg_stat_replication AS 119 | SELECT * FROM monitoring.pg_stat_replication(); 120 | 121 | CREATE OR REPLACE FUNCTION monitoring.pg_stat_progress_vacuum() RETURNS SETOF pg_stat_progress_vacuum AS $$ 122 | SELECT * FROM pg_catalog.pg_stat_progress_vacuum; 123 | $$ LANGUAGE sql VOLATILE SECURITY DEFINER; 124 | 125 | CREATE VIEW monitoring.pg_stat_progress_vacuum AS 126 | SELECT * FROM monitoring.pg_stat_progress_vacuum(); 127 | ``` 128 | 129 | ## Running it within Docker 130 | 131 | ```console 132 | docker run -p 9111 -v /path/to/my/config.yml:/config.yml caninjas/postgresql_exporter 133 | ``` -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | databases: 2 | - name: dba 3 | url: postgres://localhost:5432/dba?sslmode=disable 4 | - name: dbb 5 | url: postgres://user:pwd@mydb.foo.bar:5432/dbb 6 | - name: dbc 7 | # Used to connect into gcp sql instance using sqlproxy, when defined the URL is not necessary. 8 | # https://cloud.google.com/sql/docs/postgres/connect-admin-proxy#go 9 | sql: 10 | connection_name: gcp-project:region:instance-name 11 | database_name: dbc 12 | database_user: user 13 | database_password: pwd 14 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | 8 | "gopkg.in/yaml.v2" 9 | ) 10 | 11 | type Sql struct { 12 | ConnectionName string `yaml:"connection_name,omitempty"` 13 | DatabaseName string `yaml:"database_name,omitempty"` 14 | DatabaseUser string `yaml:"database_user,omitempty"` 15 | DatabasePassword string `yaml:"database_password,omitempty"` 16 | } 17 | 18 | type Database struct { 19 | URL string `yaml:"url,omitempty"` 20 | Name string `yaml:"name,omitempty"` 21 | Sql Sql `yaml:"sql,omitempty"` 22 | } 23 | 24 | type Config struct { 25 | Databases []Database `yaml:"databases,omitempty"` 26 | } 27 | 28 | func Parse(path string) (Config, error) { 29 | var cfg Config 30 | bts, err := ioutil.ReadFile(path) 31 | if err != nil { 32 | return cfg, fmt.Errorf("failed to read config file '%s': %s", path, err) 33 | } 34 | if err := yaml.Unmarshal(bts, &cfg); err != nil { 35 | return cfg, fmt.Errorf("failed to unmarshall config file '%s': %s", path, err) 36 | } 37 | if err := validate(cfg); err != nil { 38 | return cfg, err 39 | } 40 | return cfg, nil 41 | } 42 | 43 | func validate(config Config) error { 44 | names := make(map[string]bool) 45 | for _, conf := range config.Databases { 46 | if conf.Name == "" { 47 | return errors.New("failed to validate configuration. Database name cannot be empty") 48 | } 49 | if conf.URL == "" && conf.Sql.ConnectionName == "" { 50 | return fmt.Errorf("failed to validate configuration. URL or sql field cannot be empty in the '%s' database", conf.Name) 51 | } 52 | if names[conf.Name] { 53 | return fmt.Errorf("failed to validate configuration. A database named '%s' has already been declared", conf.Name) 54 | } 55 | names[conf.Name] = true 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestParseConfig(t *testing.T) { 11 | config, err := Parse("testdata/valid-config.yml") 12 | require.NoError(t, err) 13 | 14 | assert.Len(t, config.Databases, 2) 15 | assert.Equal(t, "dba", config.Databases[0].Name) 16 | assert.Equal(t, "postgres://localhost:5432/dba?sslmode=disable", config.Databases[0].URL) 17 | } 18 | 19 | func TestParseBadConfigs(t *testing.T) { 20 | tests := []struct { 21 | ConfigFile string 22 | ExpectedError string 23 | }{ 24 | { 25 | ConfigFile: "testdata/unknown-config.yml", 26 | ExpectedError: "failed to read config file 'testdata/unknown-config.yml': open testdata/unknown-config.yml: no such file or directory", 27 | }, 28 | { 29 | ConfigFile: "testdata/invalid-duplicated-config.yml", 30 | ExpectedError: "failed to validate configuration. A database named 'dba' has already been declared", 31 | }, 32 | { 33 | ConfigFile: "testdata/invalid-empty-name-config.yml", 34 | ExpectedError: "failed to validate configuration. Database name cannot be empty", 35 | }, 36 | { 37 | ConfigFile: "testdata/invalid-empty-url-config.yml", 38 | ExpectedError: "failed to validate configuration. URL for database 'dba' cannot be empty", 39 | }, 40 | { 41 | ConfigFile: "testdata/invalid-yaml-config.yml", 42 | ExpectedError: "failed to unmarshall config file 'testdata/invalid-yaml-config.yml': yaml: unmarshal errors:\n line 1: cannot unmarshal !!seq into config.Config", 43 | }, 44 | } 45 | 46 | for _, test := range tests { 47 | _, err := Parse(test.ConfigFile) 48 | if err == nil { 49 | t.Errorf("In case %v:\nExpected: %v\nGot: nil", test.ConfigFile, test.ExpectedError) 50 | continue 51 | } 52 | if err.Error() != test.ExpectedError { 53 | t.Errorf("In case %v:\nExpected: %v\nGot: %v", test.ConfigFile, test.ExpectedError, err.Error()) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /config/testdata/invalid-duplicated-config.yml: -------------------------------------------------------------------------------- 1 | databases: 2 | - name: dba 3 | url: postgres://localhost:5432/dba?sslmode=disable 4 | - name: dba 5 | url: postgres://user:pwd@mydb.foo.bar:5432/dbb 6 | -------------------------------------------------------------------------------- /config/testdata/invalid-empty-name-config.yml: -------------------------------------------------------------------------------- 1 | databases: 2 | - name: dba 3 | url: postgres://localhost:5432/dba?sslmode=disable 4 | - url: postgres://user:pwd@mydb.foo.bar:5432/dbb 5 | -------------------------------------------------------------------------------- /config/testdata/invalid-empty-url-config.yml: -------------------------------------------------------------------------------- 1 | databases: 2 | - name: dba 3 | - name: dbb 4 | url: postgres://user:pwd@mydb.foo.bar:5432/dbb 5 | -------------------------------------------------------------------------------- /config/testdata/invalid-yaml-config.yml: -------------------------------------------------------------------------------- 1 | - name: dba 2 | url: postgres://localhost:5432/dba?sslmode=disable 3 | -------------------------------------------------------------------------------- /config/testdata/valid-config.yml: -------------------------------------------------------------------------------- 1 | databases: 2 | - name: dba 3 | url: postgres://localhost:5432/dba?sslmode=disable 4 | - name: dbb 5 | url: postgres://user:pwd@mydb.foo.bar:5432/dbb 6 | - name: dbc 7 | sql: 8 | connection_name: gcp-project:region:instance-name 9 | database_name: dbc 10 | database_user: user 11 | database_password: pwd 12 | -------------------------------------------------------------------------------- /gauges/backends.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/ContaAzul/postgresql_exporter/postgres" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | // ConnectedBackends returns the number of backends currently connected to database 11 | func (g *Gauges) ConnectedBackends() prometheus.Gauge { 12 | return g.new( 13 | prometheus.GaugeOpts{ 14 | Name: "postgresql_backends_total", 15 | Help: "Number of backends currently connected to database", 16 | ConstLabels: g.labels, 17 | }, 18 | "SELECT numbackends FROM pg_stat_database WHERE datname = current_database()", 19 | ) 20 | } 21 | 22 | // MaxBackends returns the maximum number of concurrent connections in the database 23 | func (g *Gauges) MaxBackends() prometheus.Gauge { 24 | return g.new( 25 | prometheus.GaugeOpts{ 26 | Name: "postgresql_max_backends", 27 | Help: "Maximum number of concurrent connections in the database", 28 | ConstLabels: g.labels, 29 | }, 30 | "SELECT setting::numeric FROM pg_settings WHERE name = 'max_connections'", 31 | ) 32 | } 33 | 34 | // InstanceConnectedBackends returns the number of backends currently connected to all databases 35 | func (g *Gauges) InstanceConnectedBackends() prometheus.Gauge { 36 | return g.new( 37 | prometheus.GaugeOpts{ 38 | Name: "postgresql_instance_connected_backends", 39 | Help: "Current number of concurrent connections in all databases", 40 | ConstLabels: g.labels, 41 | }, 42 | "SELECT sum(numbackends) FROM pg_stat_database;", 43 | ) 44 | } 45 | 46 | type backendsByState struct { 47 | Total float64 `db:"total"` 48 | State string `db:"state"` 49 | } 50 | 51 | // BackendsByState returns the number of backends currently connected to database by state 52 | func (g *Gauges) BackendsByState() *prometheus.GaugeVec { 53 | var gauge = prometheus.NewGaugeVec( 54 | prometheus.GaugeOpts{ 55 | Name: "postgresql_backends_by_state_total", 56 | Help: "Number of backends currently connected to database by state", 57 | ConstLabels: g.labels, 58 | }, 59 | []string{"state"}, 60 | ) 61 | 62 | const backendsByStateQuery = ` 63 | SELECT COUNT(*) AS total, state 64 | FROM pg_stat_activity 65 | WHERE datname = current_database() 66 | GROUP BY state 67 | ` 68 | 69 | go func() { 70 | for { 71 | gauge.Reset() 72 | var backendsByState []backendsByState 73 | if err := g.query(backendsByStateQuery, &backendsByState, emptyParams); err == nil { 74 | for _, row := range backendsByState { 75 | gauge.With(prometheus.Labels{ 76 | "state": row.State, 77 | }).Set(row.Total) 78 | } 79 | } 80 | time.Sleep(g.interval) 81 | } 82 | }() 83 | return gauge 84 | } 85 | 86 | type backendsByUserAndClientAddress struct { 87 | Total float64 `db:"total"` 88 | User string `db:"usename"` 89 | ClientAddr string `db:"client_addr"` 90 | } 91 | 92 | // BackendsByUserAndClientAddress returns the number of backends currently connected 93 | // to database by user and client address 94 | func (g *Gauges) BackendsByUserAndClientAddress() *prometheus.GaugeVec { 95 | var gauge = prometheus.NewGaugeVec( 96 | prometheus.GaugeOpts{ 97 | Name: "postgresql_backends_by_user_total", 98 | Help: "Number of backends currently connected to database by user and client address", 99 | ConstLabels: g.labels, 100 | }, 101 | []string{"user", "client_addr"}, 102 | ) 103 | 104 | const backendsByUserAndClientAddressQuery = ` 105 | SELECT 106 | COUNT(*) AS total, 107 | usename, 108 | COALESCE(client_addr, '::1') AS client_addr 109 | FROM pg_stat_activity 110 | WHERE datname = current_database() 111 | GROUP BY usename, client_addr 112 | ` 113 | 114 | go func() { 115 | for { 116 | gauge.Reset() 117 | var backendsByUserAndClientAddress []backendsByUserAndClientAddress 118 | if err := g.query(backendsByUserAndClientAddressQuery, &backendsByUserAndClientAddress, emptyParams); err == nil { 119 | for _, row := range backendsByUserAndClientAddress { 120 | gauge.With(prometheus.Labels{ 121 | "user": row.User, 122 | "client_addr": row.ClientAddr, 123 | }).Set(row.Total) 124 | } 125 | } 126 | time.Sleep(g.interval) 127 | } 128 | }() 129 | return gauge 130 | } 131 | 132 | type backendsByWaitEventType struct { 133 | Total float64 `db:"total"` 134 | WaitEventType string `db:"wait_event_type"` 135 | } 136 | 137 | func (g *Gauges) backendsByWaitEventTypeQuery() string { 138 | if postgres.Version(g.version()).IsEqualOrGreaterThan96() { 139 | return ` 140 | SELECT 141 | COUNT(*) AS total, 142 | wait_event_type 143 | FROM pg_stat_activity 144 | WHERE wait_event_type IS NOT NULL 145 | AND datname = current_database() 146 | GROUP BY wait_event_type 147 | ` 148 | } 149 | return ` 150 | SELECT 151 | COUNT(*) as total, 152 | 'Lock' as wait_event_type 153 | FROM pg_stat_activity 154 | WHERE datname = current_database() 155 | AND waiting is true 156 | ` 157 | } 158 | 159 | // BackendsByWaitEventType returns the number of backends currently waiting on some event 160 | func (g *Gauges) BackendsByWaitEventType() *prometheus.GaugeVec { 161 | var gauge = prometheus.NewGaugeVec( 162 | prometheus.GaugeOpts{ 163 | Name: "postgresql_backends_by_wait_event_type_total", 164 | Help: "Number of backends currently waiting on some event", 165 | ConstLabels: g.labels, 166 | }, 167 | []string{"wait_event_type"}, 168 | ) 169 | 170 | go func() { 171 | for { 172 | gauge.Reset() 173 | var backendsByWaitEventType []backendsByWaitEventType 174 | if err := g.query(g.backendsByWaitEventTypeQuery(), 175 | &backendsByWaitEventType, emptyParams); err == nil { 176 | for _, row := range backendsByWaitEventType { 177 | gauge.With(prometheus.Labels{ 178 | "wait_event_type": row.WaitEventType, 179 | }).Set(row.Total) 180 | } 181 | } 182 | time.Sleep(g.interval) 183 | } 184 | }() 185 | return gauge 186 | } 187 | -------------------------------------------------------------------------------- /gauges/backends_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestConnectedBackends(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.ConnectedBackends()) 14 | assert.Len(metrics, 1) 15 | assertGreaterThan(t, 0, metrics[0]) 16 | assertNoErrs(t, gauges) 17 | } 18 | 19 | func TestMaxBackends(t *testing.T) { 20 | var assert = assert.New(t) 21 | _, gauges, close := prepare(t) 22 | defer close() 23 | var metrics = evaluate(t, gauges.MaxBackends()) 24 | assert.Len(metrics, 1) 25 | assertGreaterThan(t, 0, metrics[0]) 26 | assertNoErrs(t, gauges) 27 | } 28 | 29 | func TestInstanceConnectedBackends(t *testing.T) { 30 | var assert = assert.New(t) 31 | _, gauges, close := prepare(t) 32 | defer close() 33 | var metrics = evaluate(t, gauges.InstanceConnectedBackends()) 34 | assert.Len(metrics, 1) 35 | assertGreaterThan(t, 0, metrics[0]) 36 | assertNoErrs(t, gauges) 37 | } 38 | 39 | func TestBackendsByState(t *testing.T) { 40 | var assert = assert.New(t) 41 | _, gauges, close := prepare(t) 42 | defer close() 43 | var metrics = evaluate(t, gauges.BackendsByState()) 44 | assert.True(len(metrics) > 0) 45 | for _, m := range metrics { 46 | assertGreaterThan(t, 0, m) 47 | } 48 | assertNoErrs(t, gauges) 49 | } 50 | 51 | func TestBackendsByUserAndClientAddress(t *testing.T) { 52 | var assert = assert.New(t) 53 | _, gauges, close := prepare(t) 54 | defer close() 55 | var metrics = evaluate(t, gauges.BackendsByUserAndClientAddress()) 56 | assert.True(len(metrics) > 0) 57 | for _, m := range metrics { 58 | assertGreaterThan(t, 0, m) 59 | } 60 | assertNoErrs(t, gauges) 61 | } 62 | 63 | // TODO: somehow set a waiting connections to proper test this 64 | func TestBackendsByWaitEventType(t *testing.T) { 65 | var assert = assert.New(t) 66 | _, gauges, close := prepare(t) 67 | defer close() 68 | var metrics = evaluate(t, gauges.BackendsByWaitEventType()) 69 | assert.True(len(metrics) >= 0) 70 | for _, m := range metrics { 71 | assertGreaterThan(t, 0, m) 72 | } 73 | assertNoErrs(t, gauges) 74 | } 75 | -------------------------------------------------------------------------------- /gauges/basics.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | // Up returns if database is up and accepting connections 10 | func (g *Gauges) Up() prometheus.Gauge { 11 | var gauge = prometheus.NewGauge( 12 | prometheus.GaugeOpts{ 13 | Name: "postgresql_up", 14 | Help: "Database is up and accepting connections", 15 | ConstLabels: g.labels, 16 | }, 17 | ) 18 | go func() { 19 | for { 20 | var up = 1.0 21 | if err := g.db.Ping(); err != nil { 22 | up = 0.0 23 | } 24 | gauge.Set(up) 25 | time.Sleep(g.interval) 26 | } 27 | }() 28 | return gauge 29 | } 30 | 31 | // Size returns the database size in bytes 32 | func (g *Gauges) Size() prometheus.Gauge { 33 | return g.new( 34 | prometheus.GaugeOpts{ 35 | Name: "postgresql_size_bytes", 36 | Help: "Dabatase size in bytes", 37 | ConstLabels: g.labels, 38 | }, 39 | "SELECT pg_database_size(current_database())", 40 | ) 41 | } 42 | 43 | // TempSize returns the database total amount of data written to temporary files in bytes 44 | func (g *Gauges) TempSize() prometheus.Gauge { 45 | return g.new( 46 | prometheus.GaugeOpts{ 47 | Name: "postgresql_temp_bytes", 48 | Help: "Database total amount of data written to temporary files in bytes", 49 | ConstLabels: g.labels, 50 | }, 51 | "SELECT temp_bytes FROM pg_stat_database WHERE datname = current_database()", 52 | ) 53 | } 54 | 55 | // TempFiles returns the number of temporary files created by queries in this database. 56 | func (g *Gauges) TempFiles() prometheus.Gauge { 57 | return g.new( 58 | prometheus.GaugeOpts{ 59 | Name: "postgresql_temp_files", 60 | Help: "Number of temporary files created by queries in this database.", 61 | ConstLabels: g.labels, 62 | }, 63 | "SELECT temp_files FROM pg_stat_database WHERE datname = current_database()", 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /gauges/basics_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUp(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.Up()) 14 | assert.Len(metrics, 1) 15 | assert.Equal(1.0, metrics[0].Value, "%s should be 1 ", metrics[0].Name) 16 | assertNoErrs(t, gauges) 17 | } 18 | 19 | func TestSize(t *testing.T) { 20 | var assert = assert.New(t) 21 | _, gauges, close := prepare(t) 22 | defer close() 23 | var metrics = evaluate(t, gauges.Size()) 24 | assert.Len(metrics, 1) 25 | assertGreaterThan(t, 1000, metrics[0]) 26 | assertNoErrs(t, gauges) 27 | } 28 | 29 | func TestTempSize(t *testing.T) { 30 | var assert = assert.New(t) 31 | _, gauges, close := prepare(t) 32 | defer close() 33 | var metrics = evaluate(t, gauges.TempSize()) 34 | assert.Len(metrics, 1) 35 | assertGreaterThan(t, -1, metrics[0]) 36 | assertNoErrs(t, gauges) 37 | } 38 | 39 | func TestTempFiles(t *testing.T) { 40 | var assert = assert.New(t) 41 | _, gauges, close := prepare(t) 42 | defer close() 43 | var metrics = evaluate(t, gauges.TempFiles()) 44 | assert.Len(metrics, 1) 45 | assertGreaterThan(t, -1, metrics[0]) 46 | assertNoErrs(t, gauges) 47 | } 48 | -------------------------------------------------------------------------------- /gauges/bgwriter.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | // RequestedCheckpoints returns the number of requested checkpoints that have been performed 10 | func (g *Gauges) RequestedCheckpoints() prometheus.Gauge { 11 | return g.new( 12 | prometheus.GaugeOpts{ 13 | Name: "postgresql_checkpoints_requested", 14 | Help: "Number of requested checkpoints that have been performed", 15 | ConstLabels: g.labels, 16 | }, 17 | "SELECT checkpoints_req FROM pg_stat_bgwriter", 18 | ) 19 | } 20 | 21 | // ScheduledCheckpoints returns the number of scheduled checkpoints that have been performed 22 | func (g *Gauges) ScheduledCheckpoints() prometheus.Gauge { 23 | return g.new( 24 | prometheus.GaugeOpts{ 25 | Name: "postgresql_checkpoints_scheduled", 26 | Help: "Number of scheduled checkpoints that have been performed", 27 | ConstLabels: g.labels, 28 | }, 29 | "SELECT checkpoints_timed FROM pg_stat_bgwriter", 30 | ) 31 | } 32 | 33 | // BuffersMaxWrittenClean returns the number of times the background writer stopped a 34 | // cleaning scan because it had written too many buffers 35 | func (g *Gauges) BuffersMaxWrittenClean() prometheus.Gauge { 36 | return g.new( 37 | prometheus.GaugeOpts{ 38 | Name: "postgresql_buffers_maxwritten_clean", 39 | Help: "Number of times the background writer stopped a cleaning scan because it had written too many buffers", 40 | ConstLabels: g.labels, 41 | }, 42 | "SELECT maxwritten_clean FROM pg_stat_bgwriter", 43 | ) 44 | } 45 | 46 | type buffersWritten struct { 47 | Checkpoint float64 `db:"buffers_checkpoint"` 48 | BgWriter float64 `db:"buffers_clean"` 49 | Backend float64 `db:"buffers_backend"` 50 | } 51 | 52 | var buffersWrittenQuery = ` 53 | SELECT 54 | buffers_checkpoint, 55 | buffers_clean, 56 | buffers_backend 57 | FROM pg_stat_bgwriter 58 | ` 59 | 60 | // BuffersWritten returns the number of buffers written directly by a backend, 61 | // by the background writer and during checkpoints 62 | func (g *Gauges) BuffersWritten() *prometheus.GaugeVec { 63 | var gauge = prometheus.NewGaugeVec( 64 | prometheus.GaugeOpts{ 65 | Name: "postgresql_buffers_written", 66 | Help: "Number of buffers written directly by a backend, by the background writer and during checkpoints", 67 | ConstLabels: g.labels, 68 | }, 69 | []string{"written_by"}, 70 | ) 71 | 72 | go func() { 73 | for { 74 | var buffersWritten []buffersWritten 75 | if err := g.query(buffersWrittenQuery, &buffersWritten, emptyParams); err == nil { 76 | for _, writtenBy := range buffersWritten { 77 | gauge.With(prometheus.Labels{ 78 | "written_by": "checkpoint", 79 | }).Set(writtenBy.Checkpoint) 80 | gauge.With(prometheus.Labels{ 81 | "written_by": "bgwriter", 82 | }).Set(writtenBy.BgWriter) 83 | gauge.With(prometheus.Labels{ 84 | "written_by": "backend", 85 | }).Set(writtenBy.Backend) 86 | } 87 | } 88 | time.Sleep(g.interval) 89 | } 90 | }() 91 | 92 | return gauge 93 | } 94 | -------------------------------------------------------------------------------- /gauges/bgwriter_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRequestedCheckpoints(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.RequestedCheckpoints()) 14 | assert.Len(metrics, 1) 15 | assertGreaterThan(t, -1, metrics[0]) 16 | assertNoErrs(t, gauges) 17 | } 18 | 19 | func TestScheduledCheckpoints(t *testing.T) { 20 | var assert = assert.New(t) 21 | _, gauges, close := prepare(t) 22 | defer close() 23 | var metrics = evaluate(t, gauges.ScheduledCheckpoints()) 24 | assert.Len(metrics, 1) 25 | assertGreaterThan(t, -1, metrics[0]) 26 | assertNoErrs(t, gauges) 27 | } 28 | 29 | func BuffersMaxWrittenClean(t *testing.T) { 30 | var assert = assert.New(t) 31 | _, gauges, close := prepare(t) 32 | defer close() 33 | var metrics = evaluate(t, gauges.BuffersMaxWrittenClean()) 34 | assert.Len(metrics, 1) 35 | assert.Equal(float64(0), metrics[0].Value) 36 | assertNoErrs(t, gauges) 37 | } 38 | 39 | func TestBuffersWritten(t *testing.T) { 40 | var assert = assert.New(t) 41 | _, gauges, close := prepare(t) 42 | defer close() 43 | var metrics = evaluate(t, gauges.BuffersWritten()) 44 | assert.Len(metrics, 3) 45 | for _, metric := range metrics { 46 | assertGreaterThan(t, -1, metric) 47 | } 48 | assertNoErrs(t, gauges) 49 | } 50 | -------------------------------------------------------------------------------- /gauges/deadtuples.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/apex/log" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | type relation struct { 11 | Name string `db:"relname"` 12 | } 13 | 14 | // DeadTuples returns the percentage of dead tuples on the top 20 biggest tables 15 | func (g *Gauges) DeadTuples() *prometheus.GaugeVec { 16 | var gauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{ 17 | Name: "postgresql_dead_tuples_pct", 18 | Help: "percentage of dead tuples on the top 20 biggest tables", 19 | ConstLabels: g.labels, 20 | }, []string{"table"}) 21 | 22 | if !g.hasExtension("pgstattuple") { 23 | log.WithField("db", g.name). 24 | Warn("postgresql_dead_tuples_pct disabled because pgstattuple extension is not installed") 25 | return gauge 26 | } 27 | if !g.hasPermissionToExecutePgStatTuple() { 28 | log.WithField("db", g.name). 29 | Warn("postgresql_dead_tuples_pct disabled because user doesn't have permission to use pgstattuple functions") 30 | return gauge 31 | } 32 | 33 | const relationsQuery = ` 34 | SELECT relname FROM pg_stat_user_tables ORDER BY n_tup_ins + n_tup_upd desc LIMIT 20 35 | ` 36 | 37 | go func() { 38 | for { 39 | var tables []relation 40 | g.query(relationsQuery, &tables, emptyParams) 41 | for _, table := range tables { 42 | var pct []float64 43 | if err := g.queryWithTimeout( 44 | "SELECT dead_tuple_percent FROM pgstattuple_approx($1)", 45 | &pct, 46 | []interface{}{table.Name}, 47 | 1*time.Minute, 48 | ); err == nil { 49 | gauge.With(prometheus.Labels{"table": table.Name}).Set(pct[0]) 50 | } 51 | } 52 | time.Sleep(12 * time.Hour) 53 | } 54 | }() 55 | 56 | return gauge 57 | } 58 | 59 | func (g *Gauges) hasPermissionToExecutePgStatTuple() bool { 60 | if _, err := g.db.Exec("SELECT 1 FROM pgstattuple_approx('pg_class')"); err != nil { 61 | log.WithField("db", g.name).WithError(err).Error("failed to execute pgstattuple_approx function") 62 | return false 63 | } 64 | return true 65 | } 66 | -------------------------------------------------------------------------------- /gauges/deadtuples_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestDeadTuples(t *testing.T) { 11 | var assert = assert.New(t) 12 | db, gauges, close := prepare(t) 13 | defer close() 14 | dropTestTable := createTestTable(t, db) 15 | defer dropTestTable() 16 | 17 | _, err := db.Exec("CREATE EXTENSION IF NOT EXISTS pgstattuple") 18 | require.NoError(t, err) 19 | 20 | var metrics = evaluate(t, gauges.DeadTuples()) 21 | assert.True(len(metrics) > 0) 22 | for _, m := range metrics { 23 | assert.Equal(0.0, m.Value) 24 | } 25 | assertNoErrs(t, gauges) 26 | } 27 | 28 | func TestDeadTuplesWithoutPgstatTuple(t *testing.T) { 29 | var assert = assert.New(t) 30 | db, gauges, close := prepare(t) 31 | defer close() 32 | dropTestTable := createTestTable(t, db) 33 | defer dropTestTable() 34 | 35 | _, err := db.Exec("DROP EXTENSION IF EXISTS pgstattuple") 36 | require.NoError(t, err) 37 | 38 | var metrics = evaluate(t, gauges.DeadTuples()) 39 | assert.Len(metrics, 0) 40 | assertErrs(t, gauges, 0) 41 | } 42 | -------------------------------------------------------------------------------- /gauges/gauge.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "strings" 7 | "time" 8 | 9 | "github.com/apex/log" 10 | "github.com/jmoiron/sqlx" 11 | "github.com/prometheus/client_golang/prometheus" 12 | ) 13 | 14 | type Gauges struct { 15 | name string 16 | db *sqlx.DB 17 | interval time.Duration 18 | timeout time.Duration 19 | labels prometheus.Labels 20 | Errs prometheus.Gauge 21 | } 22 | 23 | func New(name string, db *sql.DB, interval, timeout time.Duration) *Gauges { 24 | var labels = prometheus.Labels{ 25 | "database_name": name, 26 | } 27 | var dbx = sqlx.NewDb(db, "postgres") 28 | return &Gauges{ 29 | name: name, 30 | db: dbx, 31 | interval: interval, 32 | timeout: timeout, 33 | labels: labels, 34 | Errs: prometheus.NewGauge( 35 | prometheus.GaugeOpts{ 36 | Name: "postgresql_query_errors", 37 | Help: "queries that failed on the monitoring databases", 38 | ConstLabels: labels, 39 | }, 40 | ), 41 | } 42 | } 43 | 44 | func (g *Gauges) hasSharedPreloadLibrary(lib string) bool { 45 | var libs []string 46 | if err := g.query("SHOW shared_preload_libraries", &libs, emptyParams); err != nil { 47 | return false 48 | } 49 | return strings.Contains(libs[0], lib) 50 | } 51 | 52 | func (g *Gauges) hasExtension(ext string) bool { 53 | var count int64 54 | ctx, cancel := context.WithDeadline( 55 | context.Background(), 56 | time.Now().Add(g.timeout), 57 | ) 58 | defer func() { 59 | <-ctx.Done() 60 | }() 61 | if err := g.db.GetContext( 62 | ctx, 63 | &count, 64 | ` 65 | SELECT count(*) 66 | FROM pg_available_extensions 67 | WHERE name = $1 68 | AND installed_version is not null 69 | `, 70 | ext, 71 | ); err != nil { 72 | log.WithError(err).Errorf("failed to determine if %s is installed", ext) 73 | } 74 | cancel() 75 | return count > 0 76 | } 77 | 78 | func paramsFix(params []string) []interface{} { 79 | iparams := make([]interface{}, len(params)) 80 | for i, v := range params { 81 | iparams[i] = v 82 | } 83 | return iparams 84 | } 85 | 86 | func (g *Gauges) new(opts prometheus.GaugeOpts, query string, params ...string) prometheus.Gauge { 87 | var gauge = prometheus.NewGauge(opts) 88 | go g.observe(gauge, query, paramsFix(params)) 89 | return gauge 90 | } 91 | 92 | func (g *Gauges) fromOnce(gauge prometheus.Gauge, query string, params ...string) { 93 | go g.observeOnce(gauge, query, paramsFix(params)) 94 | } 95 | 96 | func (g *Gauges) observeOnce(gauge prometheus.Gauge, query string, params []interface{}) { 97 | var log = log.WithField("db", g.name) 98 | log.Debugf("collecting") 99 | var result []float64 100 | if err := g.query(query, &result, params); err == nil { 101 | gauge.Set(result[0]) 102 | } 103 | } 104 | 105 | func (g *Gauges) observe(gauge prometheus.Gauge, query string, params []interface{}) { 106 | for { 107 | g.observeOnce(gauge, query, params) 108 | time.Sleep(g.interval) 109 | } 110 | } 111 | 112 | var emptyParams = []interface{}{} 113 | 114 | func (g *Gauges) query( 115 | query string, 116 | result interface{}, 117 | params []interface{}, 118 | ) error { 119 | return g.queryWithTimeout(query, result, params, g.timeout) 120 | } 121 | 122 | func (g *Gauges) queryWithTimeout( 123 | query string, 124 | result interface{}, 125 | params []interface{}, 126 | timeout time.Duration, 127 | ) error { 128 | ctx, cancel := context.WithDeadline( 129 | context.Background(), 130 | time.Now().Add(timeout), 131 | ) 132 | defer func() { 133 | <-ctx.Done() 134 | }() 135 | var err = g.db.SelectContext(ctx, result, query, params...) 136 | if err != nil { 137 | var q = strings.Join(strings.Fields(query), " ") 138 | if len(q) > 50 { 139 | q = q[:50] + "..." 140 | } 141 | g.Errs.Inc() 142 | log.WithError(err). 143 | WithField("db", g.name). 144 | WithField("query", q). 145 | WithField("params", params). 146 | Error("query failed") 147 | } 148 | cancel() 149 | return err 150 | } 151 | 152 | func (g *Gauges) version() int { 153 | var version int 154 | if err := g.db.QueryRow("show server_version_num").Scan(&version); err != nil { 155 | log.WithField("db", g.name).WithError(err).Error("failed to get postgresql version, assuming 9.6.0") 156 | return 90600 157 | } 158 | return version 159 | } 160 | -------------------------------------------------------------------------------- /gauges/gauge_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "database/sql" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | _ "github.com/lib/pq" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | type Metric struct { 16 | Value float64 17 | Name string 18 | } 19 | 20 | var labels = prometheus.Labels{ 21 | "testing": "true", 22 | } 23 | 24 | func evaluate(t *testing.T, c prometheus.Collector) (result []Metric) { 25 | var require = require.New(t) 26 | var reg = prometheus.NewRegistry() 27 | require.NoError(reg.Register(c)) 28 | time.Sleep(100 * time.Millisecond) 29 | metrics, err := reg.Gather() 30 | require.NoError(err) 31 | for _, metric := range metrics { 32 | for _, m := range metric.GetMetric() { 33 | result = append( 34 | result, 35 | Metric{ 36 | Value: m.GetGauge().GetValue(), 37 | Name: metric.GetName(), 38 | }, 39 | ) 40 | } 41 | } 42 | return 43 | } 44 | 45 | func assertNoErrs(t *testing.T, gauges *Gauges) { 46 | var assert = assert.New(t) 47 | var errs = evaluate(t, gauges.Errs) 48 | assert.Len(errs, 1) 49 | assert.Equal(0.0, errs[0].Value) 50 | } 51 | 52 | func assertErrs(t *testing.T, gauges *Gauges, errors int) { 53 | var assert = assert.New(t) 54 | var errs = evaluate(t, gauges.Errs) 55 | assert.Len(errs, 1) 56 | assert.Equal(float64(errors), errs[0].Value) 57 | } 58 | 59 | func assertGreaterThan(t *testing.T, expected float64, m Metric) { 60 | var assert = assert.New(t) 61 | assert.True( 62 | m.Value > expected, 63 | "%s should be > %v: %v", m.Name, expected, m.Value, 64 | ) 65 | } 66 | 67 | func assertEqual(t *testing.T, expected float64, m Metric) { 68 | var assert = assert.New(t) 69 | assert.Equal( 70 | expected, 71 | m.Value, 72 | "%s should be equal to %v: %v", m.Name, expected, m.Value, 73 | ) 74 | } 75 | 76 | func prepare(t *testing.T) (*sql.DB, *Gauges, func()) { 77 | var db = connect(t) 78 | var gauges = New("test", db, 1*time.Minute, 1*time.Second) 79 | return db, gauges, func() { 80 | assert.NoError(t, db.Close()) 81 | } 82 | } 83 | 84 | func connect(t *testing.T) *sql.DB { 85 | var require = require.New(t) 86 | var url = os.Getenv("TEST_DATABASE_URL") 87 | if url == "" { 88 | url = "postgres://localhost:5432/postgres?sslmode=disable" 89 | } 90 | db, err := sql.Open("postgres", url) 91 | require.NoError(err, "failed to open connection to the database") 92 | require.NoError(db.Ping(), "failed to ping database") 93 | db.SetMaxOpenConns(1) 94 | return db 95 | } 96 | 97 | func createTestTable(t *testing.T, db *sql.DB) func() { 98 | _, err := db.Exec("CREATE TABLE IF NOT EXISTS testtable(id bigint PRIMARY KEY)") 99 | require.NoError(t, err) 100 | return func() { 101 | _, err := db.Exec("DROP TABLE IF EXISTS testtable") 102 | assert.New(t).NoError(err) 103 | } 104 | } 105 | 106 | func TestVersion(t *testing.T) { 107 | var assert = assert.New(t) 108 | _, gauges, close := prepare(t) 109 | defer close() 110 | assert.NotEmpty(gauges.version()) 111 | assertNoErrs(t, gauges) 112 | } 113 | -------------------------------------------------------------------------------- /gauges/heap.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | func (g *Gauges) HeapBlocksRead() prometheus.Gauge { 6 | return g.new( 7 | prometheus.GaugeOpts{ 8 | Name: "postgresql_heap_blks_read_sum", 9 | Help: "Sum of the number of disk blocks read from all tables", 10 | ConstLabels: g.labels, 11 | }, 12 | "SELECT coalesce(sum(heap_blks_read), 0) FROM pg_statio_user_tables", 13 | ) 14 | } 15 | 16 | func (g *Gauges) HeapBlocksHit() prometheus.Gauge { 17 | return g.new( 18 | prometheus.GaugeOpts{ 19 | Name: "postgresql_heap_blks_hit_sum", 20 | Help: "Sum of the number of buffer hits on all tables", 21 | ConstLabels: g.labels, 22 | }, 23 | "SELECT coalesce(sum(heap_blks_hit), 0) FROM pg_statio_user_tables", 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /gauges/heap_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestHeapBlocksRead(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.HeapBlocksRead()) 14 | assert.Len(metrics, 1) 15 | assertGreaterThan(t, -1, metrics[0]) 16 | assertNoErrs(t, gauges) 17 | } 18 | 19 | func TestHeapBlocksHit(t *testing.T) { 20 | var assert = assert.New(t) 21 | _, gauges, close := prepare(t) 22 | defer close() 23 | var metrics = evaluate(t, gauges.HeapBlocksHit()) 24 | assert.Len(metrics, 1) 25 | assertGreaterThan(t, -1, metrics[0]) 26 | assertNoErrs(t, gauges) 27 | } 28 | -------------------------------------------------------------------------------- /gauges/indexes.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | type indexScans struct { 10 | Table string `db:"relname"` 11 | Index string `db:"indexrelname"` 12 | IdxScan float64 `db:"idx_scan"` 13 | } 14 | 15 | // IndexScans returns the number of index scans initiated on a index 16 | func (g *Gauges) IndexScans() *prometheus.GaugeVec { 17 | var gauge = prometheus.NewGaugeVec( 18 | prometheus.GaugeOpts{ 19 | Name: "postgresql_index_scans_total", 20 | Help: "Number of index scans initiated on a index", 21 | ConstLabels: g.labels, 22 | }, 23 | []string{"table", "index"}, 24 | ) 25 | 26 | const indexScansQuery = "SELECT relname, indexrelname, idx_scan FROM pg_stat_user_indexes" 27 | 28 | go func() { 29 | for { 30 | var indexes []indexScans 31 | if err := g.query(indexScansQuery, &indexes, emptyParams); err == nil { 32 | for _, index := range indexes { 33 | gauge.With(prometheus.Labels{ 34 | "table": index.Table, 35 | "index": index.Index, 36 | }).Set(index.IdxScan) 37 | } 38 | } 39 | time.Sleep(g.interval) 40 | } 41 | }() 42 | 43 | return gauge 44 | } 45 | 46 | // UnusedIndexes returns the count of unused indexes in the database 47 | func (g *Gauges) UnusedIndexes() prometheus.Gauge { 48 | return g.new( 49 | prometheus.GaugeOpts{ 50 | Name: "postgresql_unused_indexes", 51 | Help: "Dabatase unused indexes count", 52 | ConstLabels: g.labels, 53 | }, 54 | ` 55 | SELECT COUNT(*) 56 | FROM pg_stat_user_indexes ui 57 | JOIN pg_index i ON ui.indexrelid = i.indexrelid 58 | WHERE NOT i.indisunique 59 | AND ui.idx_scan < 100 60 | `, 61 | ) 62 | } 63 | 64 | type schemaIndexBlocksRead struct { 65 | Name string `db:"schemaname"` 66 | IndexBlocksRead float64 `db:"idx_blks_read"` 67 | } 68 | 69 | // IndexBlocksReadBySchema returns the sum of the number of disk blocks read from all user indexes by schema 70 | func (g *Gauges) IndexBlocksReadBySchema() *prometheus.GaugeVec { 71 | var gauge = prometheus.NewGaugeVec( 72 | prometheus.GaugeOpts{ 73 | Name: "postgresql_index_blocks_read_sum", 74 | Help: "Sum of the number of disk blocks read from all user indexes", 75 | ConstLabels: g.labels, 76 | }, 77 | []string{"schema"}, 78 | ) 79 | 80 | const schemaIndexBlocksReadQuery = ` 81 | SELECT 82 | schemaname, 83 | coalesce(sum(idx_blks_read), 0) AS idx_blks_read 84 | FROM pg_statio_user_indexes 85 | WHERE schemaname NOT IN ('pg_catalog','information_schema','monitoring') 86 | GROUP BY schemaname; 87 | ` 88 | 89 | go func() { 90 | for { 91 | var schemas []schemaIndexBlocksRead 92 | if err := g.query(schemaIndexBlocksReadQuery, &schemas, emptyParams); err == nil { 93 | for _, schema := range schemas { 94 | gauge.With(prometheus.Labels{ 95 | "schema": schema.Name, 96 | }).Set(schema.IndexBlocksRead) 97 | } 98 | } 99 | time.Sleep(g.interval) 100 | } 101 | }() 102 | 103 | return gauge 104 | } 105 | 106 | type schemaIndexBlocksHit struct { 107 | Name string `db:"schemaname"` 108 | IndexBlocksHit float64 `db:"idx_blks_hit"` 109 | } 110 | 111 | // IndexBlocksHitBySchema returns the sum of the number of buffer hits on all user indexes by schema 112 | func (g *Gauges) IndexBlocksHitBySchema() *prometheus.GaugeVec { 113 | var gauge = prometheus.NewGaugeVec( 114 | prometheus.GaugeOpts{ 115 | Name: "postgresql_index_blocks_hit_sum", 116 | Help: "Sum of the number of buffer hits on all user indexes", 117 | ConstLabels: g.labels, 118 | }, 119 | []string{"schema"}, 120 | ) 121 | 122 | const schemaIndexBlocksHitQuery = ` 123 | SELECT 124 | schemaname, 125 | coalesce(sum(idx_blks_hit), 0) AS idx_blks_hit 126 | FROM pg_statio_user_indexes 127 | WHERE schemaname NOT IN ('pg_catalog','information_schema','monitoring') 128 | GROUP BY schemaname; 129 | ` 130 | 131 | go func() { 132 | for { 133 | var schemas []schemaIndexBlocksHit 134 | if err := g.query(schemaIndexBlocksHitQuery, &schemas, emptyParams); err == nil { 135 | for _, schema := range schemas { 136 | gauge.With(prometheus.Labels{ 137 | "schema": schema.Name, 138 | }).Set(schema.IndexBlocksHit) 139 | } 140 | } 141 | time.Sleep(g.interval) 142 | } 143 | }() 144 | 145 | return gauge 146 | } 147 | 148 | const indexBloatQuery = ` 149 | WITH btree_index_atts AS ( 150 | SELECT nspname, 151 | indexclass.relname as index_name, 152 | indexclass.reltuples, 153 | indexclass.relpages, 154 | indrelid, indexrelid, 155 | indexclass.relam, 156 | tableclass.relname as tablename, 157 | regexp_split_to_table(indkey::text, ' ')::smallint AS attnum, 158 | indexrelid as index_oid 159 | FROM pg_index 160 | JOIN pg_class AS indexclass ON pg_index.indexrelid = indexclass.oid 161 | JOIN pg_class AS tableclass ON pg_index.indrelid = tableclass.oid 162 | JOIN pg_namespace ON pg_namespace.oid = indexclass.relnamespace 163 | JOIN pg_am ON indexclass.relam = pg_am.oid 164 | WHERE pg_am.amname = 'btree' and indexclass.relpages > 0 165 | AND nspname NOT IN ('pg_catalog','information_schema') 166 | ), 167 | index_item_sizes AS ( 168 | SELECT 169 | ind_atts.nspname, ind_atts.index_name, 170 | ind_atts.reltuples, ind_atts.relpages, ind_atts.relam, 171 | indrelid AS table_oid, index_oid, 172 | current_setting('block_size')::numeric AS bs, 173 | 8 AS maxalign, 174 | 24 AS pagehdr, 175 | CASE WHEN max(coalesce(pg_stats.null_frac,0)) = 0 176 | THEN 2 177 | ELSE 6 178 | END AS index_tuple_hdr, 179 | sum( (1-coalesce(pg_stats.null_frac, 0)) * coalesce(pg_stats.avg_width, 1024) ) AS nulldatawidth 180 | FROM pg_attribute 181 | JOIN btree_index_atts AS ind_atts ON pg_attribute.attrelid = ind_atts.indexrelid AND pg_attribute.attnum = ind_atts.attnum 182 | JOIN pg_stats ON pg_stats.schemaname = ind_atts.nspname 183 | -- stats for regular index columns 184 | AND ( (pg_stats.tablename = ind_atts.tablename AND pg_stats.attname = pg_catalog.pg_get_indexdef(pg_attribute.attrelid, pg_attribute.attnum, TRUE)) 185 | -- stats for functional indexes 186 | OR (pg_stats.tablename = ind_atts.index_name AND pg_stats.attname = pg_attribute.attname)) 187 | WHERE pg_attribute.attnum > 0 188 | GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9 189 | ), 190 | index_aligned_est AS ( 191 | SELECT maxalign, bs, nspname, index_name, reltuples, 192 | relpages, relam, table_oid, index_oid, 193 | coalesce ( 194 | ceil ( 195 | reltuples * ( 6 196 | + maxalign 197 | - CASE 198 | WHEN index_tuple_hdr%maxalign = 0 THEN maxalign 199 | ELSE index_tuple_hdr%maxalign 200 | END 201 | + nulldatawidth 202 | + maxalign 203 | - CASE /* Add padding to the data to align on MAXALIGN */ 204 | WHEN nulldatawidth::integer%maxalign = 0 THEN maxalign 205 | ELSE nulldatawidth::integer%maxalign 206 | END 207 | )::numeric 208 | / ( bs - pagehdr::NUMERIC ) 209 | +1 ) 210 | , 0 ) 211 | as expected 212 | FROM index_item_sizes 213 | ), 214 | raw_bloat AS ( 215 | SELECT current_database() as dbname, nspname, pg_class.relname AS table_name, index_name, 216 | bs*(index_aligned_est.relpages)::bigint AS totalbytes, expected, 217 | CASE 218 | WHEN index_aligned_est.relpages <= expected 219 | THEN 0 220 | ELSE bs*(index_aligned_est.relpages-expected)::bigint 221 | END AS wastedbytes, 222 | CASE 223 | WHEN index_aligned_est.relpages <= expected 224 | THEN 0 225 | ELSE bs*(index_aligned_est.relpages-expected)::bigint * 100 / (bs*(index_aligned_est.relpages)::bigint) 226 | END AS realbloat, 227 | pg_relation_size(index_aligned_est.table_oid) as table_bytes, 228 | stat.idx_scan as index_scans 229 | FROM index_aligned_est 230 | JOIN pg_class ON pg_class.oid=index_aligned_est.table_oid 231 | JOIN pg_stat_user_indexes AS stat ON index_aligned_est.index_oid = stat.indexrelid 232 | ), 233 | format_bloat AS ( 234 | SELECT dbname as database_name, nspname as schema_name, table_name, index_name, 235 | round(realbloat) as bloat_pct, round(wastedbytes/(1024^2)::NUMERIC) as bloat_mb, 236 | round(totalbytes/(1024^2)::NUMERIC,3) as index_mb, 237 | round(table_bytes/(1024^2)::NUMERIC,3) as table_mb, 238 | index_scans 239 | FROM raw_bloat 240 | ) 241 | SELECT table_name, index_name, COALESCE(bloat_pct,0) as bloat_pct 242 | FROM format_bloat 243 | WHERE database_name = current_database() 244 | --AND bloat_pct > 50 245 | --AND bloat_mb > 10 246 | ORDER BY bloat_mb DESC 247 | ` 248 | 249 | type indexBloat struct { 250 | Table string `db:"table_name"` 251 | Name string `db:"index_name"` 252 | Pct float64 `db:"bloat_pct"` 253 | } 254 | 255 | // IndexBloat returns bloat percentage of an index reporting only for indexes 256 | // with size greater than 10mb and bloat greater than 50% 257 | func (g *Gauges) IndexBloat() *prometheus.GaugeVec { 258 | var gauge = prometheus.NewGaugeVec( 259 | prometheus.GaugeOpts{ 260 | Name: "postgresql_index_bloat_pct", 261 | Help: "Bloat percentage of an index. This metric reports only indexes > 10mb and > 50% bloat", 262 | ConstLabels: g.labels, 263 | }, 264 | []string{"index", "table"}, 265 | ) 266 | go func() { 267 | for { 268 | gauge.Reset() 269 | var indexes []indexBloat 270 | if err := g.query(indexBloatQuery, &indexes, emptyParams); err == nil { 271 | for _, idx := range indexes { 272 | gauge.With(prometheus.Labels{ 273 | "table": idx.Table, 274 | "index": idx.Name, 275 | }).Set(idx.Pct) 276 | } 277 | } 278 | time.Sleep(1 * time.Hour) 279 | } 280 | }() 281 | return gauge 282 | } 283 | -------------------------------------------------------------------------------- /gauges/indexes_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIndexScans(t *testing.T) { 10 | var assert = assert.New(t) 11 | db, gauges, close := prepare(t) 12 | defer close() 13 | dropTestTable := createTestTable(t, db) 14 | defer dropTestTable() 15 | 16 | var metrics = evaluate(t, gauges.IndexScans()) 17 | assert.Len(metrics, 1) 18 | assertEqual(t, 0, metrics[0]) 19 | assertNoErrs(t, gauges) 20 | } 21 | 22 | func TestUnusedIndexes(t *testing.T) { 23 | var assert = assert.New(t) 24 | _, gauges, close := prepare(t) 25 | defer close() 26 | 27 | var metrics = evaluate(t, gauges.UnusedIndexes()) 28 | assert.Len(metrics, 1) 29 | assertGreaterThan(t, -1, metrics[0]) 30 | assertNoErrs(t, gauges) 31 | } 32 | 33 | func TestIndexBlocksReadBySchema(t *testing.T) { 34 | var assert = assert.New(t) 35 | db, gauges, close := prepare(t) 36 | defer close() 37 | dropTestTable := createTestTable(t, db) 38 | defer dropTestTable() 39 | 40 | var metrics = evaluate(t, gauges.IndexBlocksReadBySchema()) 41 | assert.Len(metrics, 1) 42 | assertGreaterThan(t, -1, metrics[0]) 43 | assertNoErrs(t, gauges) 44 | } 45 | 46 | func TestIndexBlocksReadBySchemaWithoutIndexes(t *testing.T) { 47 | var assert = assert.New(t) 48 | _, gauges, close := prepare(t) 49 | defer close() 50 | 51 | var metrics = evaluate(t, gauges.IndexBlocksReadBySchema()) 52 | assert.Len(metrics, 0) 53 | assertNoErrs(t, gauges) 54 | } 55 | 56 | func TestIndexBlocksHitBySchema(t *testing.T) { 57 | var assert = assert.New(t) 58 | db, gauges, close := prepare(t) 59 | defer close() 60 | dropTestTable := createTestTable(t, db) 61 | defer dropTestTable() 62 | 63 | var metrics = evaluate(t, gauges.IndexBlocksHitBySchema()) 64 | assert.Len(metrics, 1) 65 | assertGreaterThan(t, -1, metrics[0]) 66 | assertNoErrs(t, gauges) 67 | } 68 | 69 | func TestIndexBlocksHitBySchemaWithoutIndexes(t *testing.T) { 70 | var assert = assert.New(t) 71 | _, gauges, close := prepare(t) 72 | defer close() 73 | 74 | var metrics = evaluate(t, gauges.IndexBlocksHitBySchema()) 75 | assert.Len(metrics, 0) 76 | assertNoErrs(t, gauges) 77 | } 78 | 79 | // TODO: somehow create some bloated index to proper test this 80 | func TestIndexBloat(t *testing.T) { 81 | var assert = assert.New(t) 82 | _, gauges, close := prepare(t) 83 | defer close() 84 | 85 | var metrics = evaluate(t, gauges.IndexBloat()) 86 | assert.Len(metrics, 0) 87 | assertNoErrs(t, gauges) 88 | } 89 | -------------------------------------------------------------------------------- /gauges/locks.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | type locks struct { 10 | Mode string `db:"mode"` 11 | Type string `db:"locktype"` 12 | Count float64 `db:"count"` 13 | } 14 | 15 | // Locks returns the number of active locks on the database by locktype and mode 16 | func (g *Gauges) Locks() *prometheus.GaugeVec { 17 | var gauge = prometheus.NewGaugeVec( 18 | prometheus.GaugeOpts{ 19 | Name: "postgresql_locks", 20 | Help: "Number of active locks on the database by locktype and mode", 21 | ConstLabels: g.labels, 22 | }, 23 | []string{"locktype", "mode"}, 24 | ) 25 | go func() { 26 | for { 27 | gauge.Reset() 28 | var locks []locks 29 | if err := g.query( 30 | ` 31 | SELECT locktype, mode, count(*) as count 32 | FROM pg_locks 33 | WHERE database = ( 34 | SELECT datid 35 | FROM pg_stat_database 36 | WHERE datname = current_database() 37 | ) GROUP BY locktype, mode; 38 | `, 39 | &locks, 40 | emptyParams, 41 | ); err == nil { 42 | for _, lock := range locks { 43 | gauge.With(prometheus.Labels{ 44 | "locktype": lock.Type, 45 | "mode": lock.Mode, 46 | }).Set(lock.Count) 47 | } 48 | } 49 | time.Sleep(g.interval) 50 | } 51 | }() 52 | return gauge 53 | } 54 | 55 | // NotGrantedLocks returns the number of not granted locks on the database 56 | func (g *Gauges) NotGrantedLocks() prometheus.Gauge { 57 | return g.new( 58 | prometheus.GaugeOpts{ 59 | Name: "postgresql_not_granted_locks", 60 | Help: "Number of not granted locks on the database", 61 | ConstLabels: g.labels, 62 | }, 63 | ` 64 | SELECT count(*) as count 65 | FROM pg_locks 66 | WHERE NOT granted 67 | AND database = ( 68 | SELECT datid 69 | FROM pg_stat_database 70 | WHERE datname = current_database() 71 | ); 72 | `, 73 | ) 74 | } 75 | 76 | // DeadLocks returns the number of deadlocks detected on the database 77 | func (g *Gauges) DeadLocks() prometheus.Gauge { 78 | return g.new( 79 | prometheus.GaugeOpts{ 80 | Name: "postgresql_deadlocks", 81 | Help: "Number of deadlocks detected on the database", 82 | ConstLabels: g.labels, 83 | }, 84 | "SELECT deadlocks FROM pg_stat_database WHERE datname = current_database()", 85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /gauges/locks_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestLocks(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.Locks()) 14 | assert.Len(metrics, 1) 15 | assertGreaterThan(t, -1, metrics[0]) 16 | assertNoErrs(t, gauges) 17 | } 18 | 19 | func TestNotGrantedLocks(t *testing.T) { 20 | var assert = assert.New(t) 21 | _, gauges, close := prepare(t) 22 | defer close() 23 | var metrics = evaluate(t, gauges.NotGrantedLocks()) 24 | assert.Len(metrics, 1) 25 | assertGreaterThan(t, -1, metrics[0]) 26 | assertNoErrs(t, gauges) 27 | } 28 | 29 | func TestDeadLocks(t *testing.T) { 30 | var assert = assert.New(t) 31 | _, gauges, close := prepare(t) 32 | defer close() 33 | var metrics = evaluate(t, gauges.DeadLocks()) 34 | assert.Len(metrics, 1) 35 | assertGreaterThan(t, -1, metrics[0]) 36 | assertNoErrs(t, gauges) 37 | } 38 | -------------------------------------------------------------------------------- /gauges/logical_replication.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/ContaAzul/postgresql_exporter/postgres" 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | type slots struct { 12 | Name string `db:"slot_name"` 13 | Active float64 `db:"active"` 14 | TotalLag float64 `db:"total_lag"` 15 | } 16 | 17 | // ReplicationSlotStatus returns the state of the replication slots 18 | func (g *Gauges) ReplicationSlotStatus() *prometheus.GaugeVec { 19 | var gauge = prometheus.NewGaugeVec( 20 | prometheus.GaugeOpts{ 21 | Name: "postgresql_replication_slot_status", 22 | Help: "Returns 1 if the slot is currently actively being used", 23 | ConstLabels: g.labels, 24 | }, 25 | []string{"slot_name"}, 26 | ) 27 | go func() { 28 | for { 29 | gauge.Reset() 30 | var slots []slots 31 | if err := g.query( 32 | ` 33 | SELECT 34 | slot_name, 35 | active::int 36 | FROM pg_replication_slots 37 | WHERE slot_type = 'logical' 38 | AND "database" = current_database(); 39 | `, 40 | &slots, 41 | emptyParams, 42 | ); err == nil { 43 | for _, slot := range slots { 44 | gauge.With(prometheus.Labels{ 45 | "slot_name": slot.Name, 46 | }).Set(slot.Active) 47 | } 48 | } 49 | time.Sleep(g.interval) 50 | } 51 | }() 52 | return gauge 53 | } 54 | 55 | // ReplicationSlotLagInBytes returns the total lag in bytes from the replication slots 56 | func (g *Gauges) ReplicationSlotLagInBytes() *prometheus.GaugeVec { 57 | var gauge = prometheus.NewGaugeVec( 58 | prometheus.GaugeOpts{ 59 | Name: "postgresql_replication_lag_bytes", 60 | Help: "Total lag of the replication slots in bytes", 61 | ConstLabels: g.labels, 62 | }, 63 | []string{"slot_name"}, 64 | ) 65 | go func() { 66 | for { 67 | gauge.Reset() 68 | var slots []slots 69 | if err := g.query( 70 | fmt.Sprintf( 71 | ` 72 | SELECT 73 | slot_name, 74 | %s(%s(), confirmed_flush_lsn) AS total_lag 75 | FROM pg_replication_slots 76 | WHERE slot_type = 'logical' 77 | AND "database" = current_database(); 78 | `, 79 | postgres.Version(g.version()).WalLsnDiffFunctionName(), 80 | postgres.Version(g.version()).CurrentWalLsnFunctionName(), 81 | ), 82 | &slots, 83 | emptyParams, 84 | ); err == nil { 85 | for _, slot := range slots { 86 | gauge.With(prometheus.Labels{ 87 | "slot_name": slot.Name, 88 | }).Set(slot.TotalLag) 89 | } 90 | } 91 | time.Sleep(g.interval) 92 | } 93 | }() 94 | return gauge 95 | } 96 | -------------------------------------------------------------------------------- /gauges/logical_replication_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestReplicationSlotStatus(t *testing.T) { 13 | var assert = assert.New(t) 14 | db, gauges, close := prepare(t) 15 | defer close() 16 | dropTestLogicalReplicationSlot := createTestLogicalReplicationSlot("test_status", t, db) 17 | defer dropTestLogicalReplicationSlot() 18 | var metrics = evaluate(t, gauges.ReplicationSlotStatus()) 19 | assert.Len(metrics, 1) 20 | assertEqual(t, 0, metrics[0]) 21 | assertNoErrs(t, gauges) 22 | } 23 | 24 | func TestReplicationSlotLagInMegabytes(t *testing.T) { 25 | var assert = assert.New(t) 26 | db, gauges, close := prepare(t) 27 | defer close() 28 | dropTestLogicalReplicationSlot := createTestLogicalReplicationSlot("test_lag", t, db) 29 | defer dropTestLogicalReplicationSlot() 30 | var metrics = evaluate(t, gauges.ReplicationDelayInBytes()) 31 | assert.Len(metrics, 1) 32 | assertEqual(t, 0, metrics[0]) 33 | assertNoErrs(t, gauges) 34 | } 35 | 36 | func createTestLogicalReplicationSlot(slotName string, t *testing.T, db *sql.DB) func() { 37 | _, err := db.Exec(fmt.Sprintf("SELECT * FROM pg_create_logical_replication_slot('%s', 'test_decoding');", slotName)) 38 | require.NoError(t, err) 39 | return func() { 40 | _, err := db.Exec(fmt.Sprintf("SELECT pg_drop_replication_slot('%s');", slotName)) 41 | assert.New(t).NoError(err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /gauges/read_throughput.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | var databaseReadingUsageQuery = ` 10 | SELECT coalesce(tup_returned, 0) as tup_returned 11 | , coalesce(tup_fetched, 0) as tup_fetched 12 | FROM pg_stat_database 13 | WHERE datname = current_database() 14 | ` 15 | 16 | type readingUsage struct { 17 | TuplesRedurned float64 `db:"tup_returned"` 18 | TuplesFetched float64 `db:"tup_fetched"` 19 | } 20 | 21 | func (g *Gauges) DatabaseReadingUsage() *prometheus.GaugeVec { 22 | var gauge = prometheus.NewGaugeVec( 23 | prometheus.GaugeOpts{ 24 | Name: "postgresql_database_reading_usage", 25 | Help: "Database reading usage statistics", 26 | ConstLabels: g.labels, 27 | }, 28 | []string{"stat"}, 29 | ) 30 | go func() { 31 | for { 32 | var readingUsage []readingUsage 33 | if err := g.query(databaseReadingUsageQuery, &readingUsage, emptyParams); err == nil { 34 | for _, database := range readingUsage { 35 | gauge.With(prometheus.Labels{ 36 | "stat": "tup_fetched", 37 | }).Set(database.TuplesFetched) 38 | gauge.With(prometheus.Labels{ 39 | "stat": "tup_returned", 40 | }).Set(database.TuplesRedurned) 41 | } 42 | } 43 | time.Sleep(g.interval) 44 | } 45 | }() 46 | return gauge 47 | } 48 | -------------------------------------------------------------------------------- /gauges/read_throughput_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDatabaseReadingUsage(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.DatabaseReadingUsage()) 14 | assert.True(len(metrics) > 0) 15 | assertNoErrs(t, gauges) 16 | } 17 | -------------------------------------------------------------------------------- /gauges/replication.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ContaAzul/postgresql_exporter/postgres" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | // ReplicationStatus returns a prometheus gauge for the PostgreSQL 11 | // replication status 12 | func (g *Gauges) ReplicationStatus() prometheus.Gauge { 13 | return g.new( 14 | prometheus.GaugeOpts{ 15 | Name: "postgresql_replication_status", 16 | Help: "Returns 1 if in recovery and replay is paused, 0 if OK and -1 if not in recovery", 17 | ConstLabels: g.labels, 18 | }, 19 | fmt.Sprintf(` 20 | SELECT 21 | CASE 22 | WHEN pg_is_in_recovery() is true 23 | THEN 24 | CASE 25 | WHEN %s() is true 26 | THEN 1 27 | ELSE 0 28 | END 29 | ELSE 30 | -1 31 | END`, 32 | postgres.Version(g.version()).IsWalReplayPausedFunctionName(), 33 | ), 34 | ) 35 | } 36 | 37 | // StreamingWALs returns a prometheus gauge for the count of WALs 38 | // in streaming state 39 | func (g *Gauges) StreamingWALs() prometheus.Gauge { 40 | return g.new( 41 | prometheus.GaugeOpts{ 42 | Name: "postgresql_streaming_wals", 43 | Help: "Returns the count of WALs in streaming state", 44 | ConstLabels: g.labels, 45 | }, 46 | ` 47 | SELECT count(state) 48 | FROM pg_stat_replication 49 | WHERE state='streaming' 50 | `, 51 | ) 52 | } 53 | 54 | // ReplicationDelayInSeconds returns a prometheus gauge for the database replication 55 | // lag in seconds 56 | func (g *Gauges) ReplicationDelayInSeconds() prometheus.Gauge { 57 | return g.new( 58 | prometheus.GaugeOpts{ 59 | Name: "postgresql_replication_delay_seconds", 60 | Help: "Dabatase replication delay in seconds", 61 | ConstLabels: g.labels, 62 | }, 63 | ` 64 | SELECT CASE WHEN pg_is_in_recovery() is true 65 | THEN COALESCE(EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()), 0) 66 | ELSE 0 67 | END AS replication_delay 68 | `, 69 | ) 70 | } 71 | 72 | // ReplicationDelayInBytes returns a prometheus gauge for the database replication 73 | // lag in bytes 74 | func (g *Gauges) ReplicationDelayInBytes() prometheus.Gauge { 75 | version := postgres.Version(g.version()) 76 | 77 | return g.new( 78 | prometheus.GaugeOpts{ 79 | Name: "postgresql_replication_delay_bytes", 80 | Help: "Dabatase replication delay in bytes", 81 | ConstLabels: g.labels, 82 | }, 83 | fmt.Sprintf(` 84 | SELECT COALESCE(ABS(%s(%s(), %s())), 0) AS replication_delay_bytes`, 85 | version.WalLsnDiffFunctionName(), 86 | version.LastWalReceivedLsnFunctionName(), 87 | version.LastWalReplayedLsnFunctionName(), 88 | ), 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /gauges/replication_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestReplicationStatus(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.ReplicationStatus()) 14 | assert.Len(metrics, 1) 15 | assertGreaterThan(t, -2, metrics[0]) 16 | assertNoErrs(t, gauges) 17 | } 18 | 19 | func TestReplicationDelayInSeconds(t *testing.T) { 20 | var assert = assert.New(t) 21 | _, gauges, close := prepare(t) 22 | defer close() 23 | var metrics = evaluate(t, gauges.ReplicationDelayInSeconds()) 24 | assert.Len(metrics, 1) 25 | assertGreaterThan(t, -1, metrics[0]) 26 | assertNoErrs(t, gauges) 27 | } 28 | 29 | func TestReplicationDelayInBytes(t *testing.T) { 30 | var assert = assert.New(t) 31 | _, gauges, close := prepare(t) 32 | defer close() 33 | var metrics = evaluate(t, gauges.ReplicationDelayInBytes()) 34 | assert.Len(metrics, 1) 35 | assertGreaterThan(t, -1, metrics[0]) 36 | assertNoErrs(t, gauges) 37 | } 38 | 39 | func TestStreamingWALs(t *testing.T) { 40 | var assert = assert.New(t) 41 | _, gauges, close := prepare(t) 42 | defer close() 43 | var metrics = evaluate(t, gauges.StreamingWALs()) 44 | assert.Len(metrics, 1) 45 | assertGreaterThan(t, -1, metrics[0]) 46 | assertNoErrs(t, gauges) 47 | } 48 | -------------------------------------------------------------------------------- /gauges/slow_queries.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/ContaAzul/postgresql_exporter/postgres" 9 | "github.com/apex/log" 10 | "github.com/prometheus/client_golang/prometheus" 11 | ) 12 | 13 | type slowQuery struct { 14 | Query string `db:"query"` 15 | Time float64 `db:"total_time"` 16 | } 17 | 18 | func (g *Gauges) SlowestQueries() *prometheus.GaugeVec { 19 | var gauge = prometheus.NewGaugeVec( 20 | prometheus.GaugeOpts{ 21 | Name: "postgresql_slowest_queries", 22 | Help: "top 10 slowest queries by accumulated time", 23 | ConstLabels: g.labels, 24 | }, 25 | []string{"query"}, 26 | ) 27 | if !g.hasExtension("pg_stat_statements") { 28 | log.WithField("db", g.name). 29 | Warn("postgresql_slowest_queries disabled because pg_stat_statements extension is not installed") 30 | return gauge 31 | } 32 | if !g.hasSharedPreloadLibrary("pg_stat_statements") { 33 | log.WithField("db", g.name). 34 | Warn("postgresql_slowest_queries disabled because pg_stat_statements is not on shared_preload_libraries") 35 | return gauge 36 | } 37 | go func() { 38 | for { 39 | var queries []slowQuery 40 | if err := g.query( 41 | fmt.Sprintf(` 42 | SELECT %[1]s as total_time, query 43 | FROM pg_stat_statements 44 | WHERE dbid = (SELECT datid FROM pg_stat_database WHERE datname = current_database()) 45 | ORDER BY %[1]s desc limit 10`, 46 | postgres.Version(g.version()).PgStatStatementsTotalTimeColumn(), 47 | ), 48 | &queries, 49 | emptyParams, 50 | ); err == nil { 51 | for _, query := range queries { 52 | gauge.With(prometheus.Labels{ 53 | "query": strings.Join(strings.Fields(query.Query), " "), 54 | }).Set(query.Time) 55 | } 56 | } 57 | time.Sleep(g.interval) 58 | } 59 | }() 60 | return gauge 61 | } 62 | -------------------------------------------------------------------------------- /gauges/slow_queries_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestSlowestQueries(t *testing.T) { 11 | var assert = assert.New(t) 12 | db, gauges, close := prepare(t) 13 | defer close() 14 | if !gauges.hasSharedPreloadLibrary("pg_stat_statements") { 15 | t.Skip("pg_stat_statements not in shared_preload_libraries") 16 | return 17 | } 18 | _, err := db.Exec("CREATE EXTENSION IF NOT EXISTS pg_stat_statements") 19 | require.NoError(t, err) 20 | _, err = db.Exec("CREATE TABLE IF NOT EXISTS slowest_queries AS SELECT generate_series(1, 200) AS id, md5(random()::text) AS desc") 21 | require.NoError(t, err) 22 | defer func() { 23 | _, err := db.Exec("DROP TABLE IF EXISTS slowest_queries") 24 | assert.NoError(err) 25 | }() 26 | 27 | for i := 0; i < 20; i++ { 28 | var id int64 29 | db.QueryRow("select id from slowest_queries where id = $1", i).Scan(&id) 30 | } 31 | 32 | var metrics = evaluate(t, gauges.SlowestQueries()) 33 | assert.Len(metrics, 10) 34 | assertNoErrs(t, gauges) 35 | } 36 | 37 | func TestSlowestQueriesExtensionNotInstalled(t *testing.T) { 38 | var assert = assert.New(t) 39 | db, gauges, close := prepare(t) 40 | defer close() 41 | if !gauges.hasSharedPreloadLibrary("pg_stat_statements") { 42 | t.Skip("pg_stat_statements not in shared_preload_libraries") 43 | return 44 | } 45 | _, err := db.Exec("DROP EXTENSION IF EXISTS pg_stat_statements") 46 | require.NoError(t, err) 47 | 48 | var metrics = evaluate(t, gauges.SlowestQueries()) 49 | assert.Len(metrics, 0) 50 | assertNoErrs(t, gauges) 51 | } 52 | -------------------------------------------------------------------------------- /gauges/table_rows.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | type tableDeadRows struct { 10 | Table string `db:"relname"` 11 | DeadTuples float64 `db:"n_dead_tup"` 12 | } 13 | 14 | // TableDeadRows returns the estimated number of dead rows of a given table 15 | func (g *Gauges) TableDeadRows() *prometheus.GaugeVec { 16 | var gauge = prometheus.NewGaugeVec( 17 | prometheus.GaugeOpts{ 18 | Name: "postgresql_table_dead_rows", 19 | Help: "Estimated number of dead rows in a table", 20 | ConstLabels: g.labels, 21 | }, 22 | []string{"table"}, 23 | ) 24 | 25 | const tableDeadRowsQuery = ` 26 | SELECT relname, coalesce(n_dead_tup, 0) as n_dead_tup FROM pg_stat_user_tables 27 | ` 28 | 29 | go func() { 30 | for { 31 | var tableDeadRows []tableDeadRows 32 | if err := g.query(tableDeadRowsQuery, &tableDeadRows, emptyParams); err == nil { 33 | for _, table := range tableDeadRows { 34 | gauge.With(prometheus.Labels{ 35 | "table": table.Table, 36 | }).Set(table.DeadTuples) 37 | } 38 | } 39 | time.Sleep(g.interval) 40 | } 41 | }() 42 | return gauge 43 | } 44 | 45 | // DatabaseDeadRows returns the sum of estimated number of dead rows of all tables in a database 46 | func (g *Gauges) DatabaseDeadRows() prometheus.Gauge { 47 | return g.new( 48 | prometheus.GaugeOpts{ 49 | Name: "postgresql_database_dead_rows", 50 | Help: "Estimated number of dead rows in a database", 51 | ConstLabels: g.labels, 52 | }, 53 | "SELECT coalesce(sum(n_dead_tup), 0) as n_dead_tup FROM pg_stat_user_tables", 54 | ) 55 | } 56 | 57 | type tableLiveRows struct { 58 | Table string `db:"relname"` 59 | LiveTuples float64 `db:"n_live_tup"` 60 | } 61 | 62 | // TableLiveRows returns the estimated number of live rows of a given table 63 | func (g *Gauges) TableLiveRows() *prometheus.GaugeVec { 64 | var gauge = prometheus.NewGaugeVec( 65 | prometheus.GaugeOpts{ 66 | Name: "postgresql_table_live_rows", 67 | Help: "Estimated number of live rows in a table", 68 | ConstLabels: g.labels, 69 | }, 70 | []string{"table"}, 71 | ) 72 | 73 | const tableLiveRowsQuery = ` 74 | SELECT relname, coalesce(n_live_tup, 0) as n_live_tup FROM pg_stat_user_tables 75 | ` 76 | 77 | go func() { 78 | for { 79 | var tableLiveRows []tableLiveRows 80 | if err := g.query(tableLiveRowsQuery, &tableLiveRows, emptyParams); err == nil { 81 | for _, table := range tableLiveRows { 82 | gauge.With(prometheus.Labels{ 83 | "table": table.Table, 84 | }).Set(table.LiveTuples) 85 | } 86 | } 87 | time.Sleep(g.interval) 88 | } 89 | }() 90 | return gauge 91 | } 92 | 93 | // DatabaseLiveRows returns the sum of estimated number of live rows of all tables in a database 94 | func (g *Gauges) DatabaseLiveRows() prometheus.Gauge { 95 | return g.new( 96 | prometheus.GaugeOpts{ 97 | Name: "postgresql_database_live_rows", 98 | Help: "Estimated number of live rows in a database", 99 | ConstLabels: g.labels, 100 | }, 101 | "SELECT coalesce(sum(n_live_tup), 0) as n_live_tup FROM pg_stat_user_tables", 102 | ) 103 | } 104 | -------------------------------------------------------------------------------- /gauges/table_rows_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestTableDeadRows(t *testing.T) { 10 | var assert = assert.New(t) 11 | db, gauges, close := prepare(t) 12 | defer close() 13 | dropTestTable := createTestTable(t, db) 14 | defer dropTestTable() 15 | 16 | var metrics = evaluate(t, gauges.TableDeadRows()) 17 | assert.True(len(metrics) > 0) 18 | assertNoErrs(t, gauges) 19 | } 20 | 21 | func TestDatabaseDeadRows(t *testing.T) { 22 | var assert = assert.New(t) 23 | db, gauges, close := prepare(t) 24 | defer close() 25 | dropTestTable := createTestTable(t, db) 26 | defer dropTestTable() 27 | 28 | var metrics = evaluate(t, gauges.DatabaseDeadRows()) 29 | assert.True(len(metrics) > 0) 30 | assertNoErrs(t, gauges) 31 | } 32 | 33 | func TestTableLiveRows(t *testing.T) { 34 | var assert = assert.New(t) 35 | db, gauges, close := prepare(t) 36 | defer close() 37 | dropTestTable := createTestTable(t, db) 38 | defer dropTestTable() 39 | 40 | var metrics = evaluate(t, gauges.TableLiveRows()) 41 | assert.True(len(metrics) > 0) 42 | assertNoErrs(t, gauges) 43 | } 44 | 45 | func TestDatabaseLiveRows(t *testing.T) { 46 | var assert = assert.New(t) 47 | db, gauges, close := prepare(t) 48 | defer close() 49 | dropTestTable := createTestTable(t, db) 50 | defer dropTestTable() 51 | 52 | var metrics = evaluate(t, gauges.DatabaseLiveRows()) 53 | assert.True(len(metrics) > 0) 54 | assertNoErrs(t, gauges) 55 | } 56 | -------------------------------------------------------------------------------- /gauges/table_sizes.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | const tableSizesQuery = ` 10 | SELECT 11 | ut.relname AS table_name, 12 | pg_indexes_size(c.oid) AS index_size, 13 | COALESCE(pg_total_relation_size(c.reltoastrelid), 0) AS toast_size, 14 | pg_total_relation_size(c.oid) 15 | - pg_indexes_size(c.oid) 16 | - COALESCE(pg_total_relation_size(c.reltoastrelid), 0) AS table_size 17 | FROM 18 | pg_stat_user_tables ut 19 | JOIN pg_class c on ut.relname = c.relname 20 | ` 21 | 22 | type tableSizes struct { 23 | Name string `db:"table_name"` 24 | IndexSize float64 `db:"index_size"` 25 | ToastSize float64 `db:"toast_size"` 26 | TableSize float64 `db:"table_size"` 27 | } 28 | 29 | // TableSizes returns the total disk space in bytes used by the a table, 30 | // including all indexes and TOAST data 31 | func (g *Gauges) TableSizes() *prometheus.GaugeVec { 32 | var gauge = prometheus.NewGaugeVec( 33 | prometheus.GaugeOpts{ 34 | Name: "postgresql_table_size_bytes", 35 | Help: "Total disk space in bytes used by the a table, including all indexes and TOAST data", 36 | ConstLabels: g.labels, 37 | }, 38 | []string{"table", "type"}, 39 | ) 40 | 41 | go func() { 42 | for { 43 | var tables []tableSizes 44 | if err := g.query(tableSizesQuery, &tables, emptyParams); err == nil { 45 | for _, table := range tables { 46 | gauge.With(prometheus.Labels{ 47 | "table": table.Name, 48 | "type": "index", 49 | }).Set(table.IndexSize) 50 | gauge.With(prometheus.Labels{ 51 | "table": table.Name, 52 | "type": "toast", 53 | }).Set(table.ToastSize) 54 | gauge.With(prometheus.Labels{ 55 | "table": table.Name, 56 | "type": "table", 57 | }).Set(table.TableSize) 58 | } 59 | } 60 | time.Sleep(g.interval) 61 | } 62 | }() 63 | 64 | return gauge 65 | } 66 | -------------------------------------------------------------------------------- /gauges/table_sizes_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestTableSizes(t *testing.T) { 10 | var assert = assert.New(t) 11 | db, gauges, close := prepare(t) 12 | defer close() 13 | dropTestTable := createTestTable(t, db) 14 | defer dropTestTable() 15 | 16 | var metrics = evaluate(t, gauges.TableSizes()) 17 | assert.Len(metrics, 3) 18 | for _, metric := range metrics { 19 | assertGreaterThan(t, -1, metric) 20 | } 21 | assertNoErrs(t, gauges) 22 | } 23 | -------------------------------------------------------------------------------- /gauges/tables.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | const tableBloatQuery = ` 10 | WITH constants AS ( 11 | SELECT current_setting('block_size')::numeric AS bs, 23 AS hdr, 8 AS ma 12 | ), 13 | no_stats AS ( 14 | SELECT table_schema, table_name, 15 | n_live_tup::numeric as est_rows, 16 | pg_table_size(relid)::numeric as table_size 17 | FROM information_schema.columns 18 | JOIN pg_stat_user_tables as psut 19 | ON table_schema = psut.schemaname 20 | AND table_name = psut.relname 21 | LEFT OUTER JOIN pg_stats 22 | ON table_schema = pg_stats.schemaname 23 | AND table_name = pg_stats.tablename 24 | AND column_name = attname 25 | WHERE attname IS NULL 26 | AND table_schema NOT IN ('pg_catalog', 'information_schema') 27 | GROUP BY table_schema, table_name, relid, n_live_tup 28 | ), 29 | null_headers AS ( 30 | -- calculate null header sizes 31 | -- omitting tables which dont have complete stats 32 | -- and attributes which arent visible 33 | SELECT 34 | hdr+1+(sum(case when null_frac <> 0 THEN 1 else 0 END)/8) as nullhdr, 35 | SUM((1-null_frac)*avg_width) as datawidth, 36 | MAX(null_frac) as maxfracsum, 37 | schemaname, 38 | tablename, 39 | hdr, ma, bs 40 | FROM pg_stats CROSS JOIN constants 41 | LEFT OUTER JOIN no_stats 42 | ON schemaname = no_stats.table_schema 43 | AND tablename = no_stats.table_name 44 | WHERE schemaname NOT IN ('pg_catalog', 'information_schema') 45 | AND no_stats.table_name IS NULL 46 | AND EXISTS ( SELECT 1 47 | FROM information_schema.columns 48 | WHERE schemaname = columns.table_schema 49 | AND tablename = columns.table_name ) 50 | GROUP BY schemaname, tablename, hdr, ma, bs 51 | ), 52 | data_headers AS ( 53 | -- estimate header and row size 54 | SELECT 55 | ma, bs, hdr, schemaname, tablename, 56 | (datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr, 57 | (maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2 58 | FROM null_headers 59 | ), 60 | table_estimates AS ( 61 | -- make estimates of how large the table should be 62 | -- based on row and page size 63 | SELECT schemaname, tablename, bs, 64 | reltuples::numeric as est_rows, relpages * bs as table_bytes, 65 | CEIL((reltuples* 66 | (datahdr + nullhdr2 + 4 + ma - 67 | (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END) 68 | )/(bs-20))) * bs AS expected_bytes, 69 | reltoastrelid 70 | FROM data_headers 71 | JOIN pg_class ON tablename = relname 72 | JOIN pg_namespace ON relnamespace = pg_namespace.oid 73 | AND schemaname = nspname 74 | WHERE pg_class.relkind = 'r' 75 | ), 76 | estimates_with_toast AS ( 77 | SELECT schemaname, tablename, 78 | TRUE as can_estimate, 79 | est_rows, 80 | table_bytes + ( coalesce(toast.relpages, 0) * bs ) as table_bytes, 81 | expected_bytes + ( ceil( coalesce(toast.reltuples, 0) / 4 ) * bs ) as expected_bytes 82 | FROM table_estimates LEFT OUTER JOIN pg_class as toast 83 | ON table_estimates.reltoastrelid = toast.oid 84 | AND toast.relkind = 't' 85 | ), 86 | table_estimates_plus AS ( 87 | SELECT current_database() as databasename, 88 | schemaname, tablename, can_estimate, 89 | est_rows, 90 | CASE WHEN table_bytes > 0 91 | THEN table_bytes::NUMERIC 92 | ELSE NULL::NUMERIC END 93 | AS table_bytes, 94 | CASE WHEN expected_bytes > 0 95 | THEN expected_bytes::NUMERIC 96 | ELSE NULL::NUMERIC END 97 | AS expected_bytes, 98 | CASE WHEN expected_bytes > 0 AND table_bytes > 0 99 | AND expected_bytes <= table_bytes 100 | THEN (table_bytes - expected_bytes)::NUMERIC 101 | ELSE 0::NUMERIC END AS bloat_bytes 102 | FROM estimates_with_toast 103 | UNION ALL 104 | SELECT current_database() as databasename, 105 | table_schema, table_name, FALSE, 106 | est_rows, table_size, 107 | NULL::NUMERIC, NULL::NUMERIC 108 | FROM no_stats 109 | ), 110 | bloat_data AS ( 111 | select current_database() as databasename, 112 | schemaname, tablename, can_estimate, 113 | table_bytes, round(table_bytes/(1024^2)::NUMERIC,3) as table_mb, 114 | expected_bytes, round(expected_bytes/(1024^2)::NUMERIC,3) as expected_mb, 115 | round(bloat_bytes*100/table_bytes) as pct_bloat, 116 | round(bloat_bytes/(1024::NUMERIC^2),2) as mb_bloat, 117 | table_bytes, expected_bytes, est_rows 118 | FROM table_estimates_plus 119 | ) 120 | SELECT tablename, 121 | COALESCE(pct_bloat,0) as pct_bloat 122 | FROM bloat_data 123 | -- WHERE ( pct_bloat >= 30 AND mb_bloat >= 10 ) 124 | -- OR ( pct_bloat >= 20 AND mb_bloat >= 1000 ) 125 | ORDER BY pct_bloat DESC 126 | ` 127 | 128 | type tableBloat struct { 129 | Name string `db:"tablename"` 130 | Pct float64 `db:"pct_bloat"` 131 | } 132 | 133 | // TableBloat returns the bloat percentage of a table reporting only for tables 134 | // with bloat percentange greater than 30% or greater than 1000mb 135 | func (g *Gauges) TableBloat() *prometheus.GaugeVec { 136 | var gauge = prometheus.NewGaugeVec( 137 | prometheus.GaugeOpts{ 138 | Name: "postgresql_table_bloat_pct", 139 | Help: "bloat percentage of a table. Reports only for tables with a lot of bloat", 140 | ConstLabels: g.labels, 141 | }, 142 | []string{"table"}, 143 | ) 144 | go func() { 145 | for { 146 | gauge.Reset() 147 | var tables []tableBloat 148 | if err := g.query(tableBloatQuery, &tables, emptyParams); err == nil { 149 | for _, table := range tables { 150 | gauge.With(prometheus.Labels{ 151 | "table": table.Name, 152 | }).Set(table.Pct) 153 | } 154 | } 155 | time.Sleep(1 * time.Hour) 156 | } 157 | }() 158 | return gauge 159 | } 160 | 161 | var tableUsageQuery = ` 162 | SELECT s.relname, 163 | coalesce(s.seq_tup_read, 0) as seq_tup_read, 164 | coalesce(s.idx_tup_fetch, 0) as idx_tup_fetch, 165 | coalesce(s.n_tup_ins, 0) as n_tup_ins, 166 | coalesce(s.n_tup_upd, 0) as n_tup_upd, 167 | coalesce(s.n_tup_del, 0) as n_tup_del 168 | FROM pg_stat_user_tables s 169 | ORDER BY 2 desc 170 | ` 171 | 172 | type tableUsage struct { 173 | Name string `db:"relname"` 174 | SeqReads float64 `db:"seq_tup_read"` 175 | IdxFetchs float64 `db:"idx_tup_fetch"` 176 | Inserts float64 `db:"n_tup_ins"` 177 | Updates float64 `db:"n_tup_upd"` 178 | Deletes float64 `db:"n_tup_del"` 179 | } 180 | 181 | func (g *Gauges) TableUsage() *prometheus.GaugeVec { 182 | var gauge = prometheus.NewGaugeVec( 183 | prometheus.GaugeOpts{ 184 | Name: "postgresql_table_usage", 185 | Help: "table usage statistics", 186 | ConstLabels: g.labels, 187 | }, 188 | []string{"table", "stat"}, 189 | ) 190 | go func() { 191 | for { 192 | var tables []tableUsage 193 | if err := g.query(tableUsageQuery, &tables, emptyParams); err == nil { 194 | for _, table := range tables { 195 | gauge.With(prometheus.Labels{ 196 | "table": table.Name, 197 | "stat": "seq_tup_read", 198 | }).Set(table.SeqReads) 199 | gauge.With(prometheus.Labels{ 200 | "table": table.Name, 201 | "stat": "idx_tup_fetch", 202 | }).Set(table.IdxFetchs) 203 | gauge.With(prometheus.Labels{ 204 | "table": table.Name, 205 | "stat": "n_tup_ins", 206 | }).Set(table.Inserts) 207 | gauge.With(prometheus.Labels{ 208 | "table": table.Name, 209 | "stat": "n_tup_upd", 210 | }).Set(table.Updates) 211 | gauge.With(prometheus.Labels{ 212 | "table": table.Name, 213 | "stat": "n_tup_del", 214 | }).Set(table.Deletes) 215 | } 216 | } 217 | time.Sleep(g.interval) 218 | } 219 | }() 220 | return gauge 221 | } 222 | 223 | var tableSecScansQuery = ` 224 | select relname, 225 | coalesce(seq_scan, 0) as seq_scan, 226 | coalesce(idx_scan, 0) as idx_scan 227 | from pg_stat_user_tables 228 | ` 229 | 230 | type tableScans struct { 231 | Name string `db:"relname"` 232 | SecScan float64 `db:"seq_scan"` 233 | IdxScan float64 `db:"idx_scan"` 234 | } 235 | 236 | func (g *Gauges) TableScans() *prometheus.GaugeVec { 237 | var gauge = prometheus.NewGaugeVec( 238 | prometheus.GaugeOpts{ 239 | Name: "postgresql_table_scans", 240 | Help: "table scans statistics", 241 | ConstLabels: g.labels, 242 | }, 243 | []string{"table", "scan"}, 244 | ) 245 | go func() { 246 | for { 247 | var tables []tableScans 248 | if err := g.query(tableSecScansQuery, &tables, emptyParams); err == nil { 249 | for _, table := range tables { 250 | gauge.With(prometheus.Labels{ 251 | "table": table.Name, 252 | "scan": "seq_scan", 253 | }).Set(table.SecScan) 254 | gauge.With(prometheus.Labels{ 255 | "table": table.Name, 256 | "scan": "idx_scan", 257 | }).Set(table.IdxScan) 258 | } 259 | } 260 | time.Sleep(g.interval) 261 | } 262 | }() 263 | 264 | return gauge 265 | } 266 | 267 | var hotUpdatesQuery = ` 268 | SELECT relname 269 | , coalesce(n_tup_hot_upd, 0) as n_tup_hot_upd 270 | FROM pg_stat_user_tables 271 | ` 272 | 273 | type tableHotUpdates struct { 274 | Name string `db:"relname"` 275 | HotUpdates float64 `db:"n_tup_hot_upd"` 276 | } 277 | 278 | func (g *Gauges) HOTUpdates() *prometheus.GaugeVec { 279 | var gauge = prometheus.NewGaugeVec( 280 | prometheus.GaugeOpts{ 281 | Name: "postgresql_hot_updates", 282 | Help: "Number of rows Heap-Only tuple updated", 283 | ConstLabels: g.labels, 284 | }, 285 | []string{"table"}, 286 | ) 287 | go func() { 288 | for { 289 | var tables []tableHotUpdates 290 | if err := g.query(hotUpdatesQuery, &tables, emptyParams); err == nil { 291 | for _, table := range tables { 292 | gauge.With(prometheus.Labels{ 293 | "table": table.Name, 294 | }).Set(table.HotUpdates) 295 | } 296 | } 297 | time.Sleep(g.interval) 298 | } 299 | }() 300 | 301 | return gauge 302 | } 303 | -------------------------------------------------------------------------------- /gauges/tables_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | // TODO: somehow create some bloated table to proper test this 10 | func TestTableBloat(t *testing.T) { 11 | var assert = assert.New(t) 12 | db, gauges, close := prepare(t) 13 | defer close() 14 | dropTestTable := createTestTable(t, db) 15 | defer dropTestTable() 16 | 17 | var metrics = evaluate(t, gauges.TableBloat()) 18 | assert.Len(metrics, 0) 19 | assertNoErrs(t, gauges) 20 | } 21 | 22 | func TestTableUsage(t *testing.T) { 23 | var assert = assert.New(t) 24 | db, gauges, close := prepare(t) 25 | defer close() 26 | dropTestTable := createTestTable(t, db) 27 | defer dropTestTable() 28 | 29 | var metrics = evaluate(t, gauges.TableUsage()) 30 | assert.True(len(metrics) > 0) 31 | assertNoErrs(t, gauges) 32 | } 33 | 34 | func TestTableScans(t *testing.T) { 35 | var assert = assert.New(t) 36 | db, gauges, close := prepare(t) 37 | defer close() 38 | dropTestTable := createTestTable(t, db) 39 | defer dropTestTable() 40 | 41 | var metrics = evaluate(t, gauges.TableScans()) 42 | assert.True(len(metrics) > 0) 43 | assertNoErrs(t, gauges) 44 | } 45 | 46 | func TestHOTUpdates(t *testing.T) { 47 | var assert = assert.New(t) 48 | db, gauges, close := prepare(t) 49 | defer close() 50 | dropTestTable := createTestTable(t, db) 51 | defer dropTestTable() 52 | 53 | var metrics = evaluate(t, gauges.HOTUpdates()) 54 | assert.True(len(metrics) > 0) 55 | assertNoErrs(t, gauges) 56 | } 57 | -------------------------------------------------------------------------------- /gauges/transactions.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | func (g *Gauges) TransactionsSum() prometheus.Gauge { 6 | return g.new( 7 | prometheus.GaugeOpts{ 8 | Name: "postgresql_transactions_sum", 9 | Help: "Sum of all transactions in the database", 10 | ConstLabels: g.labels, 11 | }, 12 | ` 13 | SELECT xact_commit + xact_rollback 14 | FROM pg_stat_database 15 | WHERE datname = current_database() 16 | `, 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /gauges/transactions_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestTransactionsSum(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.TransactionsSum()) 14 | assert.Len(metrics, 1) 15 | assertGreaterThan(t, 1, metrics[0]) 16 | assertNoErrs(t, gauges) 17 | } 18 | -------------------------------------------------------------------------------- /gauges/vacuum.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/ContaAzul/postgresql_exporter/postgres" 7 | "github.com/apex/log" 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | // UnvacuumedTransactions returns the number of unvacuumed transactions 12 | func (g *Gauges) UnvacuumedTransactions() prometheus.Gauge { 13 | return g.new( 14 | prometheus.GaugeOpts{ 15 | Name: "postgresql_unvacuumed_transactions_total", 16 | Help: "Number of unvacuumed transactions", 17 | ConstLabels: g.labels, 18 | }, 19 | "SELECT age(datfrozenxid) FROM pg_database WHERE datname = current_database()", 20 | ) 21 | } 22 | 23 | type tableVacuum struct { 24 | Name string `db:"relname"` 25 | LastVacuumTime float64 `db:"last_vacuum_time"` 26 | } 27 | 28 | // LastTimeVacuumRan returns the last time in seconds at which a table 29 | // was manually vacuumed (not counting VACUUM FULL) 30 | func (g *Gauges) LastTimeVacuumRan() *prometheus.GaugeVec { 31 | var gauge = prometheus.NewGaugeVec( 32 | prometheus.GaugeOpts{ 33 | Name: "postgresql_last_vacuum_seconds", 34 | Help: "Last time in seconds at which a table was manually vacuumed (not counting VACUUM FULL)", 35 | ConstLabels: g.labels, 36 | }, 37 | []string{"table"}, 38 | ) 39 | 40 | const lastVacuumQuery = ` 41 | SELECT 42 | relname, 43 | COALESCE(EXTRACT(EPOCH FROM last_vacuum), 0) as last_vacuum_time 44 | FROM pg_stat_user_tables 45 | ` 46 | 47 | go func() { 48 | for { 49 | var tables []tableVacuum 50 | if err := g.query(lastVacuumQuery, &tables, emptyParams); err == nil { 51 | for _, table := range tables { 52 | gauge.With(prometheus.Labels{ 53 | "table": table.Name, 54 | }).Set(table.LastVacuumTime) 55 | } 56 | } 57 | time.Sleep(g.interval) 58 | } 59 | }() 60 | 61 | return gauge 62 | } 63 | 64 | // LastTimeAutoVacuumRan returns the last time in seconds at which a table 65 | // was vacuumed by the autovacuum daemon 66 | func (g *Gauges) LastTimeAutoVacuumRan() *prometheus.GaugeVec { 67 | var gauge = prometheus.NewGaugeVec( 68 | prometheus.GaugeOpts{ 69 | Name: "postgresql_last_autovacuum_seconds", 70 | Help: "Last time in seconds at which a table was vacuumed by the autovacuum daemon", 71 | ConstLabels: g.labels, 72 | }, 73 | []string{"table"}, 74 | ) 75 | 76 | const lastAutoVacuumQuery = ` 77 | SELECT 78 | relname, 79 | COALESCE(EXTRACT(EPOCH FROM last_autovacuum), 0) as last_vacuum_time 80 | FROM pg_stat_user_tables 81 | ` 82 | 83 | go func() { 84 | for { 85 | var tables []tableVacuum 86 | if err := g.query(lastAutoVacuumQuery, &tables, emptyParams); err == nil { 87 | for _, table := range tables { 88 | gauge.With(prometheus.Labels{ 89 | "table": table.Name, 90 | }).Set(table.LastVacuumTime) 91 | } 92 | } 93 | time.Sleep(g.interval) 94 | } 95 | }() 96 | 97 | return gauge 98 | } 99 | 100 | // VacuumRunningTotal returns the number of backends (including autovacuum worker processes) 101 | // that are currently running a vacuuming (not including VACUUM FULL). 102 | // 103 | // This metric is only supported for PostgreSQL 9.6 or newer versions 104 | func (g *Gauges) VacuumRunningTotal() prometheus.Gauge { 105 | var gaugeOpts = prometheus.GaugeOpts{ 106 | Name: "postgresql_vacuum_running_total", 107 | Help: "Number of backends (including autovacuum worker processes) currently vacuuming", 108 | ConstLabels: g.labels, 109 | } 110 | 111 | const vacuumRunningQuery = ` 112 | SELECT COUNT(*) FROM pg_stat_progress_vacuum WHERE datname = current_database() 113 | ` 114 | 115 | if !postgres.Version(g.version()).IsEqualOrGreaterThan96() { 116 | log.WithField("db", g.name). 117 | Warn("postgresql_vacuum_running_total disabled because it's only supported for PostgreSQL 9.6 or newer versions") 118 | return prometheus.NewGauge(gaugeOpts) 119 | } 120 | return g.new(gaugeOpts, vacuumRunningQuery) 121 | } 122 | -------------------------------------------------------------------------------- /gauges/vacuum_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUnvacuumedTransactions(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.UnvacuumedTransactions()) 14 | assert.Len(metrics, 1) 15 | assertGreaterThan(t, 0, metrics[0]) 16 | assertNoErrs(t, gauges) 17 | } 18 | 19 | func TestLastTimeVacuumRan(t *testing.T) { 20 | var assert = assert.New(t) 21 | db, gauges, close := prepare(t) 22 | defer close() 23 | dropTestTable := createTestTable(t, db) 24 | defer dropTestTable() 25 | 26 | var metrics = evaluate(t, gauges.LastTimeVacuumRan()) 27 | assert.Len(metrics, 1) 28 | assertEqual(t, 0, metrics[0]) 29 | assertNoErrs(t, gauges) 30 | } 31 | 32 | func TestLastTimeAutoVacuumRan(t *testing.T) { 33 | var assert = assert.New(t) 34 | db, gauges, close := prepare(t) 35 | defer close() 36 | dropTestTable := createTestTable(t, db) 37 | defer dropTestTable() 38 | 39 | var metrics = evaluate(t, gauges.LastTimeAutoVacuumRan()) 40 | assert.Len(metrics, 1) 41 | assertEqual(t, 0, metrics[0]) 42 | assertNoErrs(t, gauges) 43 | } 44 | 45 | func TestVacuumRunningTotal(t *testing.T) { 46 | var assert = assert.New(t) 47 | _, gauges, close := prepare(t) 48 | defer close() 49 | 50 | var metrics = evaluate(t, gauges.VacuumRunningTotal()) 51 | assert.Len(metrics, 1) 52 | assertEqual(t, 0, metrics[0]) 53 | assertNoErrs(t, gauges) 54 | } 55 | -------------------------------------------------------------------------------- /gauges/write_throughput.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | var databaseWritingUsageQuery = ` 10 | SELECT coalesce(tup_inserted, 0) as tup_inserted 11 | , coalesce(tup_updated, 0) as tup_updated 12 | , coalesce(tup_deleted, 0) as tup_deleted 13 | FROM pg_stat_database 14 | WHERE datname = current_database() 15 | ` 16 | 17 | type writingUsage struct { 18 | TuplesInserted float64 `db:"tup_inserted"` 19 | TuplesUpdated float64 `db:"tup_updated"` 20 | TuplesDeleted float64 `db:"tup_deleted"` 21 | } 22 | 23 | func (g *Gauges) DatabaseWritingUsage() *prometheus.GaugeVec { 24 | var gauge = prometheus.NewGaugeVec( 25 | prometheus.GaugeOpts{ 26 | Name: "postgresql_database_writing_usage", 27 | Help: "Number of inserted, updated and deleted rows per database", 28 | ConstLabels: g.labels, 29 | }, 30 | []string{"stat"}, 31 | ) 32 | go func() { 33 | for { 34 | var writingUsage []writingUsage 35 | if err := g.query(databaseWritingUsageQuery, &writingUsage, emptyParams); err == nil { 36 | for _, database := range writingUsage { 37 | gauge.With(prometheus.Labels{ 38 | "stat": "tup_inserted", 39 | }).Set(database.TuplesInserted) 40 | gauge.With(prometheus.Labels{ 41 | "stat": "tup_updated", 42 | }).Set(database.TuplesUpdated) 43 | gauge.With(prometheus.Labels{ 44 | "stat": "tup_deleted", 45 | }).Set(database.TuplesDeleted) 46 | } 47 | } 48 | time.Sleep(g.interval) 49 | } 50 | }() 51 | return gauge 52 | } 53 | -------------------------------------------------------------------------------- /gauges/write_throughput_test.go: -------------------------------------------------------------------------------- 1 | package gauges 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDatabaseWritingUsage(t *testing.T) { 10 | var assert = assert.New(t) 11 | _, gauges, close := prepare(t) 12 | defer close() 13 | var metrics = evaluate(t, gauges.DatabaseWritingUsage()) 14 | assert.True(len(metrics) > 0) 15 | assertNoErrs(t, gauges) 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ContaAzul/postgresql_exporter 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/GoogleCloudPlatform/cloudsql-proxy v1.19.1 7 | github.com/apex/httplog v1.0.0 8 | github.com/apex/log v1.9.0 9 | github.com/jmoiron/sqlx v1.2.0 10 | github.com/lib/pq v1.9.0 11 | github.com/prometheus/client_golang v1.9.0 12 | github.com/stretchr/testify v1.6.1 13 | gopkg.in/yaml.v2 v2.4.0 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= 2 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 4 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 5 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 6 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0 h1:eWRCuwubtDrCJG0oSUMgnsbD4CmPFQF2ei4OFbXvwww= 18 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 19 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 20 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 21 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 22 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 23 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 24 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 25 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 26 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 27 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 28 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 29 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 30 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 31 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 32 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 33 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 34 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 35 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 36 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 37 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 38 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 39 | github.com/GoogleCloudPlatform/cloudsql-proxy v1.19.1 h1:eAKkTWSG0jXkdQ6V2qO+ovXMLcSCcqDqCK+A8xeVYN0= 40 | github.com/GoogleCloudPlatform/cloudsql-proxy v1.19.1/go.mod h1:+yYmuKqcBVkgRePGpUhTA9OEg0XsnFE96eZ6nJ2yCQM= 41 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 42 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 43 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 44 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= 45 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= 46 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 47 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 48 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 49 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 50 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 51 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 52 | github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 53 | github.com/apex/httplog v1.0.0 h1:5uJFk6Ga4rRGG3Xt+ldofR5/RCgSzRiQn1WRXe2TXt0= 54 | github.com/apex/httplog v1.0.0/go.mod h1:cjjeMniS2rpajsvqBd2X521ua0Tmwtt4y0avzGRIG9M= 55 | github.com/apex/log v1.1.2/go.mod h1:SyfRweFO+TlkIJ3DVizTSeI1xk7jOIIqOnUPZQTTsww= 56 | github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0= 57 | github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA= 58 | github.com/apex/logs v0.0.3/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= 59 | github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= 60 | github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= 61 | github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= 62 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 63 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 64 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 65 | github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= 66 | github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= 67 | github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 68 | github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 69 | github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= 70 | github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= 71 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 72 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 73 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 74 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 75 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 76 | github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= 77 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 78 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 79 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 80 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 81 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 82 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 83 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 84 | github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= 85 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 86 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 87 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= 88 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= 89 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 90 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 91 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 92 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 93 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 94 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 95 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 96 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 97 | github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 98 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 99 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 100 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 101 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 102 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 103 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 104 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 105 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 106 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 107 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 108 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 109 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 110 | github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= 111 | github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= 112 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 113 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 114 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 115 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 116 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 117 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 118 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 119 | github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= 120 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 121 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 122 | github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= 123 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 124 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 125 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 126 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 127 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= 128 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 129 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 130 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 131 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 132 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 133 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 134 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 135 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 136 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= 137 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 138 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 139 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 140 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 141 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 142 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 143 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 144 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 145 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 146 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 147 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 148 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 149 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 150 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 151 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 152 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 153 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 154 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 155 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 156 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 157 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 158 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= 159 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 160 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 161 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 162 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 163 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 164 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 165 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 166 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 167 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 168 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 169 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 170 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 171 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 172 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 173 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 174 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 175 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 176 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 177 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 178 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 179 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 180 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 181 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 182 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 183 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 184 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 185 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 186 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 187 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 188 | github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= 189 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 190 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 191 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 192 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 193 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 194 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 195 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 196 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 197 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 198 | github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= 199 | github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 200 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 201 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 202 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 203 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 204 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 205 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 206 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 207 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 208 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 209 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 210 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 211 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 212 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 213 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 214 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 215 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 216 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 217 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 218 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 219 | github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= 220 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 221 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 222 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 223 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= 224 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 225 | github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= 226 | github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= 227 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 228 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= 229 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 230 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 231 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 232 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 233 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 234 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 235 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 236 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 237 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 238 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 239 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 240 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 241 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 242 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 243 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 244 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 245 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 246 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 247 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 248 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 249 | github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= 250 | github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 251 | github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= 252 | github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= 253 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= 254 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 255 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 256 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 257 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 258 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 259 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 260 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 261 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 262 | github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 263 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 264 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 265 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 266 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 267 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 268 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 269 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 270 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 271 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 272 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 273 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 274 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 275 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 276 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 277 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 278 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 279 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 280 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= 281 | github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= 282 | github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= 283 | github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= 284 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= 285 | github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= 286 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 287 | github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= 288 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 289 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 290 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 291 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 292 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 293 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 294 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= 295 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= 296 | github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= 297 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 298 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 299 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= 300 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 301 | github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 302 | github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 303 | github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= 304 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 305 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 306 | github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= 307 | github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= 308 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 309 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 310 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 311 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 312 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 313 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 314 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 315 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 316 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 317 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 318 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= 319 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 320 | github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= 321 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 322 | github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU= 323 | github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= 324 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 325 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 326 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 327 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 328 | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 329 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 330 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 331 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 332 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 333 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= 334 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 335 | github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= 336 | github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= 337 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 338 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 339 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 340 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 341 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 342 | github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= 343 | github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 344 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 345 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 346 | github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 347 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 348 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 349 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 350 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= 351 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 352 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 353 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 354 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 355 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 356 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 357 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 358 | github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= 359 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= 360 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 361 | github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= 362 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 363 | github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= 364 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 365 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 366 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 367 | github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 368 | github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= 369 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 370 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 371 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 372 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 373 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 374 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 375 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 376 | github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= 377 | github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= 378 | github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj52Uc= 379 | github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= 380 | github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= 381 | github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= 382 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 383 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 384 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 385 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 386 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 387 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 388 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 389 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 390 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 391 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= 392 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 393 | go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 394 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 395 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 396 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 397 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 398 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 399 | go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= 400 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 401 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 402 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 403 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 404 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 405 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 406 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 407 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 408 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 409 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 410 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 411 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 412 | golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 413 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 414 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 415 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 416 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 417 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 418 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 419 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 420 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 421 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 422 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 423 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 424 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 425 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 426 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 427 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 428 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 429 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 430 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 431 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 432 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 433 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 434 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 435 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 436 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 437 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 438 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 439 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 440 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 441 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 442 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 443 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 444 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 445 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 446 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 447 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 448 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 449 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 450 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 451 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 452 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 453 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 454 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 455 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 456 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 457 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 458 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 459 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 460 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 461 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 462 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 463 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 464 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 465 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 466 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 467 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 468 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 469 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 470 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 471 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 472 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 473 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 474 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 475 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 476 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 477 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 478 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 479 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 480 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 481 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 482 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 483 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 484 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= 485 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 486 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 487 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 488 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 489 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 490 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 491 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 492 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 h1:Mj83v+wSRNEar42a/MQgxk9X42TdEmrOl9i+y8WbxLo= 493 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 494 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 495 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 496 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 497 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 498 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 499 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 500 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 501 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 502 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 503 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 504 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 505 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 506 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 507 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 508 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 509 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 510 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 511 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 512 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 513 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 514 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 515 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 516 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 517 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 518 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 519 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 520 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 521 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 522 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 523 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 524 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 525 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 526 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 527 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 528 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 529 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 530 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 531 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 532 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 533 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 534 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 535 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 536 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 537 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 538 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 539 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 540 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 541 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 542 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 543 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 544 | golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= 545 | golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 546 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 547 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 548 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 549 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 550 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 551 | golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= 552 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 553 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 554 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 555 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 556 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 557 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 558 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 559 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 560 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 561 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 562 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 563 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 564 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 565 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 566 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 567 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 568 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 569 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 570 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 571 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 572 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 573 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 574 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 575 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 576 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 577 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 578 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 579 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 580 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 581 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 582 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 583 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 584 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 585 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 586 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 587 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 588 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 589 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 590 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 591 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 592 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 593 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 594 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 595 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 596 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 597 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 598 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 599 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 600 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 601 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 602 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 603 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 604 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 605 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 606 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 607 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 608 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 609 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 610 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 611 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 612 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 613 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 614 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 615 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 616 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 617 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 618 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 619 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 620 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 621 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 622 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 623 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 624 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 625 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 626 | google.golang.org/api v0.35.0 h1:TBCmTTxUrRDA1iTctnK/fIeitxIZ+TQuaf0j29fmCGo= 627 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 628 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 629 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 630 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 631 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 632 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 633 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 634 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= 635 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 636 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 637 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 638 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 639 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 640 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 641 | google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 642 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 643 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 644 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 645 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 646 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 647 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 648 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 649 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 650 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 651 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 652 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 653 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 654 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 655 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 656 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 657 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 658 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 659 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 660 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 661 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 662 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 663 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 664 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 665 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 666 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 667 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb h1:MoNcrN5yaH+35Ge8RUwFbL7ekwq9ED2fiDpgWKrR29w= 668 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 669 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 670 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 671 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= 672 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 673 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 674 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 675 | google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 676 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 677 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 678 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 679 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 680 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 681 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 682 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 683 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 684 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 685 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 686 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 687 | google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= 688 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 689 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 690 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 691 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 692 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 693 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 694 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 695 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 696 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 697 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 698 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= 699 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 700 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 701 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 702 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 703 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 704 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 705 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 706 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 707 | gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= 708 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 709 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 710 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 711 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 712 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 713 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 714 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 715 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 716 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 717 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 718 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 719 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 720 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= 721 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 722 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 723 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 724 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 725 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 726 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 727 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 728 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 729 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 730 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 731 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 732 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 733 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 734 | sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= 735 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "flag" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/ContaAzul/postgresql_exporter/config" 11 | "github.com/ContaAzul/postgresql_exporter/gauges" 12 | _ "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/postgres" 13 | "github.com/apex/httplog" 14 | "github.com/apex/log" 15 | "github.com/apex/log/handlers/logfmt" 16 | _ "github.com/lib/pq" 17 | "github.com/prometheus/client_golang/prometheus" 18 | "github.com/prometheus/client_golang/prometheus/promhttp" 19 | ) 20 | 21 | var ( 22 | addr = flag.String("listen-address", ":9111", "The address to listen on for HTTP requests.") 23 | configFile = flag.String("config", "config.yml", "The path to the config file.") 24 | interval = flag.Duration("interval", 30*time.Second, "interval between gathering metrics") 25 | timeout = flag.Duration("timeout", 15*time.Second, "query timeout") 26 | maxDBConns = flag.Int("max-db-connections", 5, "max connections to open to each database") 27 | debug = flag.Bool("debug", false, "Enable debug mode") 28 | ) 29 | 30 | func main() { 31 | log.SetHandler(logfmt.Default) 32 | flag.Parse() 33 | var server = &http.Server{ 34 | Addr: *addr, 35 | WriteTimeout: 1 * time.Second, 36 | ReadTimeout: 1 * time.Second, 37 | } 38 | if *debug { 39 | log.SetLevel(log.DebugLevel) 40 | server.Handler = httplog.New(http.DefaultServeMux) 41 | } 42 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 43 | w.Write([]byte( 44 | ` 45 |