1/*
2** Zabbix
3** Copyright (C) 2001-2021 Zabbix SIA
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation; either version 2 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18**/
19
20package postgres
21
22import (
23	"context"
24	"errors"
25	"fmt"
26	"regexp"
27	"strings"
28
29	"zabbix.com/pkg/metric"
30	"zabbix.com/pkg/plugin"
31	"zabbix.com/pkg/uri"
32)
33
34const (
35	keyArchiveSize                     = "pgsql.archive"
36	keyAutovacuum                      = "pgsql.autovacuum.count"
37	keyBgwriter                        = "pgsql.bgwriter"
38	keyCache                           = "pgsql.cache.hit"
39	keyConnections                     = "pgsql.connections"
40	keyCustomQuery                     = "pgsql.custom.query"
41	keyDBStat                          = "pgsql.dbstat"
42	keyDBStatSum                       = "pgsql.dbstat.sum"
43	keyDatabaseAge                     = "pgsql.db.age"
44	keyDatabasesBloating               = "pgsql.db.bloating_tables"
45	keyDatabasesDiscovery              = "pgsql.db.discovery"
46	keyDatabaseSize                    = "pgsql.db.size"
47	keyLocks                           = "pgsql.locks"
48	keyOldestXid                       = "pgsql.oldest.xid"
49	keyPing                            = "pgsql.ping"
50	keyReplicationCount                = "pgsql.replication.count"
51	keyReplicationLagB                 = "pgsql.replication.lag.b"
52	keyReplicationLagSec               = "pgsql.replication.lag.sec"
53	keyReplicationProcessInfo          = "pgsql.replication.process"
54	keyReplicationProcessNameDiscovery = "pgsql.replication.process.discovery"
55	keyReplicationRecoveryRole         = "pgsql.replication.recovery_role"
56	keyReplicationStatus               = "pgsql.replication.status"
57	keyUptime                          = "pgsql.uptime"
58	keyWal                             = "pgsql.wal.stat"
59)
60
61// handlerFunc defines an interface must be implemented by handlers.
62type handlerFunc func(ctx context.Context, conn PostgresClient, key string,
63	params map[string]string, extraParams ...string) (res interface{}, err error)
64
65// getHandlerFunc returns a handlerFunc related to a given key.
66func getHandlerFunc(key string) handlerFunc {
67	switch key {
68	case keyDatabasesDiscovery:
69		return databasesDiscoveryHandler
70	case keyDatabasesBloating:
71		return databasesBloatingHandler
72	case keyDatabaseSize:
73		return databaseSizeHandler
74	case keyDatabaseAge:
75		return databaseAgeHandler
76	case keyArchiveSize:
77		return archiveHandler
78	case keyPing:
79		return pingHandler
80	case keyConnections:
81		return connectionsHandler
82	case keyWal:
83		return walHandler
84	case keyAutovacuum:
85		return autovacuumHandler
86	case keyDBStat,
87		keyDBStatSum:
88		return dbStatHandler
89	case keyBgwriter:
90		return bgwriterHandler
91	case keyCustomQuery:
92		return customQueryHandler
93	case keyUptime:
94		return uptimeHandler
95	case keyCache:
96		return cacheHandler
97	case keyReplicationCount,
98		keyReplicationStatus,
99		keyReplicationLagSec,
100		keyReplicationRecoveryRole,
101		keyReplicationLagB,
102		keyReplicationProcessInfo:
103		return replicationHandler
104	case keyReplicationProcessNameDiscovery:
105		return processNameDiscoveryHandler
106	case keyLocks:
107		return locksHandler
108	case keyOldestXid:
109		return oldestXIDHandler
110
111	default:
112		return nil
113	}
114}
115
116var uriDefaults = &uri.Defaults{Scheme: "tcp", Port: "5432"}
117
118var (
119	minDBNameLen = 1
120	maxDBNameLen = 63
121	maxPassLen   = 512
122)
123
124type PostgresURIValidator struct {
125	Defaults       *uri.Defaults
126	AllowedSchemes []string
127}
128
129var reSocketPath = regexp.MustCompile(`^.*\.s\.PGSQL\.\d{1,5}$`)
130
131func (v PostgresURIValidator) Validate(value *string) error {
132	if value == nil {
133		return nil
134	}
135
136	u, err := uri.New(*value, v.Defaults)
137	if err != nil {
138		return err
139	}
140
141	isValidScheme := false
142	if v.AllowedSchemes != nil {
143		for _, s := range v.AllowedSchemes {
144			if u.Scheme() == s {
145				isValidScheme = true
146				break
147			}
148		}
149
150		if !isValidScheme {
151			return fmt.Errorf("allowed schemes: %s", strings.Join(v.AllowedSchemes, ", "))
152		}
153	}
154
155	if u.Scheme() == "unix" && !reSocketPath.MatchString(*value) {
156		return errors.New(
157			`socket file must satisfy the format: "/path/.s.PGSQL.nnnn" where nnnn is the server's port number`)
158	}
159
160	return nil
161}
162
163// Common params: [URI|Session][,User][,Password][,Database]
164var (
165	paramURI = metric.NewConnParam("URI", "URI to connect or session name.").
166			WithDefault(uriDefaults.Scheme + "://localhost:" + uriDefaults.Port).WithSession().
167			WithValidator(PostgresURIValidator{
168			Defaults:       uriDefaults,
169			AllowedSchemes: []string{"tcp", "postgresql", "unix"},
170		})
171	paramUsername = metric.NewConnParam("User", "PostgreSQL user.").WithDefault("postgres")
172	paramPassword = metric.NewConnParam("Password", "User's password.").WithDefault("").
173			WithValidator(metric.LenValidator{Max: &maxPassLen})
174	paramDatabase = metric.NewConnParam("Database", "Database name to be used for connection.").
175			WithDefault("postgres").WithValidator(metric.LenValidator{Min: &minDBNameLen, Max: &maxDBNameLen})
176	paramTLSConnect  = metric.NewSessionOnlyParam("TLSConnect", "DB connection encryption type.").WithDefault("")
177	paramTLSCaFile   = metric.NewSessionOnlyParam("TLSCAFile", "TLS ca file path.").WithDefault("")
178	paramTLSCertFile = metric.NewSessionOnlyParam("TLSCertFile", "TLS cert file path.").WithDefault("")
179	paramTLSKeyFile  = metric.NewSessionOnlyParam("TLSKeyFile", "TLS key file path.").WithDefault("")
180)
181
182var metrics = metric.MetricSet{
183	keyArchiveSize: metric.New("Returns info about size of archive files.",
184		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
185			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
186
187	keyAutovacuum: metric.New("Returns count of autovacuum workers.",
188		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
189			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
190
191	keyBgwriter: metric.New("Returns JSON for sum of each type of bgwriter statistic.",
192		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
193			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
194
195	keyCache: metric.New("Returns cache hit percent.",
196		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
197			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
198
199	keyConnections: metric.New("Returns JSON for sum of each type of connection.",
200		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
201			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
202
203	keyCustomQuery: metric.New("Returns result of a custom query.",
204		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase,
205			metric.NewParam("QueryName", "Name of a custom query "+
206				"(must be equal to a name of an SQL file without an extension).").SetRequired(),
207			paramTLSConnect, paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, true),
208
209	keyDBStat: metric.New("Returns JSON for sum of each type of statistic.",
210		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
211			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
212
213	keyDBStatSum: metric.New("Returns JSON for sum of each type of statistic for all database.",
214		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
215			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
216
217	keyDatabaseAge: metric.New("Returns age for specific database.",
218		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
219			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
220
221	keyDatabasesBloating: metric.New("Returns percent of bloating tables for each database.",
222		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
223			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
224
225	keyDatabasesDiscovery: metric.New("Returns JSON discovery rule with names of databases.",
226		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
227			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
228
229	keyDatabaseSize: metric.New("Returns size in bytes for specific database.",
230		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
231			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
232
233	keyLocks: metric.New("Returns collect all metrics from pg_locks.",
234		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
235			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
236
237	keyOldestXid: metric.New("Returns age of oldest xid.",
238		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
239			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
240
241	keyPing: metric.New("Tests if connection is alive or not.",
242		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
243			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
244
245	keyReplicationCount: metric.New("Returns number of standby servers.",
246		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
247			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
248
249	keyReplicationLagB: metric.New("Returns replication lag with Master in byte.",
250		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
251			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
252
253	keyReplicationLagSec: metric.New("Returns replication lag with Master in seconds.",
254		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
255			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
256
257	keyReplicationProcessNameDiscovery: metric.New("Returns JSON with application name from pg_stat_replication.",
258		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
259			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
260
261	keyReplicationProcessInfo: metric.New("Returns flush lag, write lag and replay lag per each sender process.",
262		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
263			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
264
265	keyReplicationRecoveryRole: metric.New("Returns postgreSQL recovery role.",
266		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
267			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
268
269	keyReplicationStatus: metric.New("Returns postgreSQL replication status.",
270		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
271			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
272
273	keyUptime: metric.New("Returns uptime.",
274		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
275			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
276
277	keyWal: metric.New("Returns JSON wal by type.",
278		[]*metric.Param{paramURI, paramUsername, paramPassword, paramDatabase, paramTLSConnect,
279			paramTLSCaFile, paramTLSCertFile, paramTLSKeyFile}, false),
280}
281
282func init() {
283	plugin.RegisterMetrics(&impl, pluginName, metrics.List()...)
284}
285