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