github.com/netdata/go.d.plugin@v0.58.1/modules/postgres/collect.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package postgres
     4  
     5  import (
     6  	"context"
     7  	"database/sql"
     8  	"fmt"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/jackc/pgx/v4"
    13  	"github.com/jackc/pgx/v4/stdlib"
    14  )
    15  
    16  const (
    17  	pgVersion94 = 9_04_00
    18  	pgVersion10 = 10_00_00
    19  	pgVersion11 = 11_00_00
    20  )
    21  
    22  func (p *Postgres) collect() (map[string]int64, error) {
    23  	if p.db == nil {
    24  		db, err := p.openPrimaryConnection()
    25  		if err != nil {
    26  			return nil, err
    27  		}
    28  		p.db = db
    29  	}
    30  
    31  	if p.pgVersion == 0 {
    32  		ver, err := p.doQueryServerVersion()
    33  		if err != nil {
    34  			return nil, fmt.Errorf("querying server version error: %v", err)
    35  		}
    36  		p.pgVersion = ver
    37  		p.Debugf("connected to PostgreSQL v%d", p.pgVersion)
    38  	}
    39  
    40  	if p.superUser == nil {
    41  		v, err := p.doQueryIsSuperUser()
    42  		if err != nil {
    43  			return nil, fmt.Errorf("querying is super user error: %v", err)
    44  		}
    45  		p.superUser = &v
    46  		p.Debugf("connected as super user: %v", *p.superUser)
    47  	}
    48  
    49  	if p.pgIsInRecovery == nil {
    50  		v, err := p.doQueryPGIsInRecovery()
    51  		if err != nil {
    52  			return nil, fmt.Errorf("querying recovery status error: %v", err)
    53  		}
    54  		p.pgIsInRecovery = &v
    55  		p.Debugf("the instance is in recovery mode: %v", *p.pgIsInRecovery)
    56  	}
    57  
    58  	now := time.Now()
    59  
    60  	if now.Sub(p.recheckSettingsTime) > p.recheckSettingsEvery {
    61  		p.recheckSettingsTime = now
    62  		maxConn, err := p.doQuerySettingsMaxConnections()
    63  		if err != nil {
    64  			return nil, fmt.Errorf("querying settings max connections error: %v", err)
    65  		}
    66  		p.mx.maxConnections = maxConn
    67  
    68  		maxLocks, err := p.doQuerySettingsMaxLocksHeld()
    69  		if err != nil {
    70  			return nil, fmt.Errorf("querying settings max locks held error: %v", err)
    71  		}
    72  		p.mx.maxLocksHeld = maxLocks
    73  	}
    74  
    75  	p.resetMetrics()
    76  
    77  	if p.pgVersion >= pgVersion10 {
    78  		// need 'backend_type' in pg_stat_activity
    79  		p.addXactQueryRunningTimeChartsOnce.Do(func() {
    80  			p.addTransactionsRunTimeHistogramChart()
    81  			p.addQueriesRunTimeHistogramChart()
    82  		})
    83  	}
    84  	if p.isSuperUser() {
    85  		p.addWALFilesChartsOnce.Do(p.addWALFilesCharts)
    86  	}
    87  
    88  	if err := p.doQueryGlobalMetrics(); err != nil {
    89  		return nil, err
    90  	}
    91  	if err := p.doQueryReplicationMetrics(); err != nil {
    92  		return nil, err
    93  	}
    94  	if err := p.doQueryDatabasesMetrics(); err != nil {
    95  		return nil, err
    96  	}
    97  	if p.dbSr != nil {
    98  		if err := p.doQueryQueryableDatabases(); err != nil {
    99  			return nil, err
   100  		}
   101  	}
   102  	if err := p.doQueryTablesMetrics(); err != nil {
   103  		return nil, err
   104  	}
   105  	if err := p.doQueryIndexesMetrics(); err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	if now.Sub(p.doSlowTime) > p.doSlowEvery {
   110  		p.doSlowTime = now
   111  		if err := p.doQueryBloat(); err != nil {
   112  			return nil, err
   113  		}
   114  		if err := p.doQueryColumns(); err != nil {
   115  			return nil, err
   116  		}
   117  	}
   118  
   119  	mx := make(map[string]int64)
   120  	p.collectMetrics(mx)
   121  
   122  	return mx, nil
   123  }
   124  
   125  func (p *Postgres) openPrimaryConnection() (*sql.DB, error) {
   126  	db, err := sql.Open("pgx", p.DSN)
   127  	if err != nil {
   128  		return nil, fmt.Errorf("error on opening a connection with the Postgres database [%s]: %v", p.DSN, err)
   129  	}
   130  
   131  	db.SetMaxOpenConns(1)
   132  	db.SetMaxIdleConns(1)
   133  	db.SetConnMaxLifetime(10 * time.Minute)
   134  
   135  	ctx, cancel := context.WithTimeout(context.Background(), p.Timeout.Duration)
   136  	defer cancel()
   137  
   138  	if err := db.PingContext(ctx); err != nil {
   139  		_ = db.Close()
   140  		return nil, fmt.Errorf("error on pinging the Postgres database [%s]: %v", p.DSN, err)
   141  	}
   142  
   143  	return db, nil
   144  }
   145  
   146  func (p *Postgres) openSecondaryConnection(dbname string) (*sql.DB, string, error) {
   147  	cfg, err := pgx.ParseConfig(p.DSN)
   148  	if err != nil {
   149  		return nil, "", fmt.Errorf("error on parsing DSN [%s]: %v", p.DSN, err)
   150  	}
   151  
   152  	cfg.Database = dbname
   153  	connStr := stdlib.RegisterConnConfig(cfg)
   154  
   155  	db, err := sql.Open("pgx", connStr)
   156  	if err != nil {
   157  		stdlib.UnregisterConnConfig(connStr)
   158  		return nil, "", fmt.Errorf("error on opening a secondary connection with the Postgres database [%s]: %v", dbname, err)
   159  	}
   160  
   161  	db.SetMaxOpenConns(1)
   162  	db.SetMaxIdleConns(1)
   163  	db.SetConnMaxLifetime(10 * time.Minute)
   164  
   165  	ctx, cancel := context.WithTimeout(context.Background(), p.Timeout.Duration)
   166  	defer cancel()
   167  
   168  	if err := db.PingContext(ctx); err != nil {
   169  		stdlib.UnregisterConnConfig(connStr)
   170  		_ = db.Close()
   171  		return nil, "", fmt.Errorf("error on pinging the secondary Postgres database [%s]: %v", dbname, err)
   172  	}
   173  
   174  	return db, connStr, nil
   175  }
   176  
   177  func (p *Postgres) isSuperUser() bool { return p.superUser != nil && *p.superUser }
   178  
   179  func (p *Postgres) isPGInRecovery() bool { return p.pgIsInRecovery != nil && *p.pgIsInRecovery }
   180  
   181  func (p *Postgres) getDBMetrics(name string) *dbMetrics {
   182  	db, ok := p.mx.dbs[name]
   183  	if !ok {
   184  		db = &dbMetrics{name: name}
   185  		p.mx.dbs[name] = db
   186  	}
   187  	return db
   188  }
   189  
   190  func (p *Postgres) getTableMetrics(name, db, schema string) *tableMetrics {
   191  	key := name + "_" + db + "_" + schema
   192  	m, ok := p.mx.tables[key]
   193  	if !ok {
   194  		m = &tableMetrics{db: db, schema: schema, name: name}
   195  		p.mx.tables[key] = m
   196  	}
   197  	return m
   198  }
   199  
   200  func (p *Postgres) hasTableMetrics(name, db, schema string) bool {
   201  	key := name + "_" + db + "_" + schema
   202  	_, ok := p.mx.tables[key]
   203  	return ok
   204  }
   205  
   206  func (p *Postgres) getIndexMetrics(name, table, db, schema string) *indexMetrics {
   207  	key := name + "_" + table + "_" + db + "_" + schema
   208  	m, ok := p.mx.indexes[key]
   209  	if !ok {
   210  		m = &indexMetrics{name: name, db: db, schema: schema, table: table}
   211  		p.mx.indexes[key] = m
   212  	}
   213  	return m
   214  }
   215  
   216  func (p *Postgres) hasIndexMetrics(name, table, db, schema string) bool {
   217  	key := name + "_" + table + "_" + db + "_" + schema
   218  	_, ok := p.mx.indexes[key]
   219  	return ok
   220  }
   221  
   222  func (p *Postgres) getReplAppMetrics(name string) *replStandbyAppMetrics {
   223  	app, ok := p.mx.replApps[name]
   224  	if !ok {
   225  		app = &replStandbyAppMetrics{name: name}
   226  		p.mx.replApps[name] = app
   227  	}
   228  	return app
   229  }
   230  
   231  func (p *Postgres) getReplSlotMetrics(name string) *replSlotMetrics {
   232  	slot, ok := p.mx.replSlots[name]
   233  	if !ok {
   234  		slot = &replSlotMetrics{name: name}
   235  		p.mx.replSlots[name] = slot
   236  	}
   237  	return slot
   238  }
   239  
   240  func parseInt(s string) int64 {
   241  	v, _ := strconv.ParseInt(s, 10, 64)
   242  	return v
   243  }
   244  
   245  func parseFloat(s string) int64 {
   246  	v, _ := strconv.ParseFloat(s, 64)
   247  	return int64(v)
   248  }
   249  
   250  func newInt(v int64) *int64 {
   251  	return &v
   252  }
   253  
   254  func calcPercentage(value, total int64) (v int64) {
   255  	if total == 0 {
   256  		return 0
   257  	}
   258  	if v = value * 100 / total; v < 0 {
   259  		v = -v
   260  	}
   261  	return v
   262  }
   263  
   264  func calcDeltaPercentage(a, b incDelta) int64 {
   265  	return calcPercentage(a.delta(), a.delta()+b.delta())
   266  }