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

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package postgres
     4  
     5  import (
     6  	"database/sql"
     7  	_ "embed"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/netdata/go.d.plugin/agent/module"
    12  	"github.com/netdata/go.d.plugin/pkg/matcher"
    13  	"github.com/netdata/go.d.plugin/pkg/metrics"
    14  	"github.com/netdata/go.d.plugin/pkg/web"
    15  
    16  	"github.com/jackc/pgx/v4/stdlib"
    17  	_ "github.com/jackc/pgx/v4/stdlib"
    18  )
    19  
    20  //go:embed "config_schema.json"
    21  var configSchema string
    22  
    23  func init() {
    24  	module.Register("postgres", module.Creator{
    25  		JobConfigSchema: configSchema,
    26  		Create:          func() module.Module { return New() },
    27  	})
    28  }
    29  
    30  func New() *Postgres {
    31  	return &Postgres{
    32  		Config: Config{
    33  			Timeout:            web.Duration{Duration: time.Second * 2},
    34  			DSN:                "postgres://postgres:postgres@127.0.0.1:5432/postgres",
    35  			XactTimeHistogram:  []float64{.1, .5, 1, 2.5, 5, 10},
    36  			QueryTimeHistogram: []float64{.1, .5, 1, 2.5, 5, 10},
    37  			// charts: 20 x table, 4 x index.
    38  			// https://discord.com/channels/847502280503590932/1022693928874549368
    39  			MaxDBTables:  50,
    40  			MaxDBIndexes: 250,
    41  		},
    42  		charts:  baseCharts.Copy(),
    43  		dbConns: make(map[string]*dbConn),
    44  		mx: &pgMetrics{
    45  			dbs:       make(map[string]*dbMetrics),
    46  			indexes:   make(map[string]*indexMetrics),
    47  			tables:    make(map[string]*tableMetrics),
    48  			replApps:  make(map[string]*replStandbyAppMetrics),
    49  			replSlots: make(map[string]*replSlotMetrics),
    50  		},
    51  		recheckSettingsEvery:              time.Minute * 30,
    52  		doSlowEvery:                       time.Minute * 5,
    53  		addXactQueryRunningTimeChartsOnce: &sync.Once{},
    54  		addWALFilesChartsOnce:             &sync.Once{},
    55  	}
    56  }
    57  
    58  type Config struct {
    59  	DSN                string       `yaml:"dsn"`
    60  	Timeout            web.Duration `yaml:"timeout"`
    61  	DBSelector         string       `yaml:"collect_databases_matching"`
    62  	XactTimeHistogram  []float64    `yaml:"transaction_time_histogram"`
    63  	QueryTimeHistogram []float64    `yaml:"query_time_histogram"`
    64  	MaxDBTables        int64        `yaml:"max_db_tables"`
    65  	MaxDBIndexes       int64        `yaml:"max_db_indexes"`
    66  }
    67  
    68  type (
    69  	Postgres struct {
    70  		module.Base
    71  		Config `yaml:",inline"`
    72  
    73  		charts *module.Charts
    74  
    75  		db      *sql.DB
    76  		dbConns map[string]*dbConn
    77  
    78  		superUser      *bool
    79  		pgIsInRecovery *bool
    80  		pgVersion      int
    81  
    82  		addXactQueryRunningTimeChartsOnce *sync.Once
    83  		addWALFilesChartsOnce             *sync.Once
    84  
    85  		dbSr matcher.Matcher
    86  
    87  		mx *pgMetrics
    88  
    89  		recheckSettingsTime  time.Time
    90  		recheckSettingsEvery time.Duration
    91  
    92  		doSlowTime  time.Time
    93  		doSlowEvery time.Duration
    94  	}
    95  	dbConn struct {
    96  		db         *sql.DB
    97  		connStr    string
    98  		connErrors int
    99  	}
   100  )
   101  
   102  func (p *Postgres) Init() bool {
   103  	err := p.validateConfig()
   104  	if err != nil {
   105  		p.Errorf("config validation: %v", err)
   106  		return false
   107  	}
   108  
   109  	sr, err := p.initDBSelector()
   110  	if err != nil {
   111  		p.Errorf("config validation: %v", err)
   112  		return false
   113  	}
   114  	p.dbSr = sr
   115  
   116  	p.mx.xactTimeHist = metrics.NewHistogramWithRangeBuckets(p.XactTimeHistogram)
   117  	p.mx.queryTimeHist = metrics.NewHistogramWithRangeBuckets(p.QueryTimeHistogram)
   118  
   119  	return true
   120  }
   121  
   122  func (p *Postgres) Check() bool {
   123  	return len(p.Collect()) > 0
   124  }
   125  
   126  func (p *Postgres) Charts() *module.Charts {
   127  	return p.charts
   128  }
   129  
   130  func (p *Postgres) Collect() map[string]int64 {
   131  	mx, err := p.collect()
   132  	if err != nil {
   133  		p.Error(err)
   134  	}
   135  
   136  	if len(mx) == 0 {
   137  		return nil
   138  	}
   139  	return mx
   140  }
   141  
   142  func (p *Postgres) Cleanup() {
   143  	if p.db == nil {
   144  		return
   145  	}
   146  	if err := p.db.Close(); err != nil {
   147  		p.Warningf("cleanup: error on closing the Postgres database [%s]: %v", p.DSN, err)
   148  	}
   149  	p.db = nil
   150  
   151  	for dbname, conn := range p.dbConns {
   152  		delete(p.dbConns, dbname)
   153  		if conn.connStr != "" {
   154  			stdlib.UnregisterConnConfig(conn.connStr)
   155  		}
   156  		if conn.db != nil {
   157  			_ = conn.db.Close()
   158  		}
   159  	}
   160  }