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

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package postgres
     4  
     5  import (
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/netdata/go.d.plugin/agent/module"
    11  )
    12  
    13  const (
    14  	prioConnectionsUtilization = module.Priority + iota
    15  	prioConnectionsUsage
    16  	prioConnectionsStateCount
    17  	prioDBConnectionsUtilization
    18  	prioDBConnectionsCount
    19  
    20  	prioTransactionsDuration
    21  	prioDBTransactionsRatio
    22  	prioDBTransactionsRate
    23  
    24  	prioQueriesDuration
    25  
    26  	prioDBOpsFetchedRowsRatio
    27  	prioDBOpsReadRowsRate
    28  	prioDBOpsWriteRowsRate
    29  	prioDBTempFilesCreatedRate
    30  	prioDBTempFilesIORate
    31  	prioTableOpsRowsRate
    32  	prioTableOpsRowsHOTRatio
    33  	prioTableOpsRowsHOTRate
    34  	prioTableScansRate
    35  	prioTableScansRowsRate
    36  
    37  	prioDBCacheIORatio
    38  	prioDBIORate
    39  	prioTableCacheIORatio
    40  	prioTableIORate
    41  	prioTableIndexCacheIORatio
    42  	prioTableIndexIORate
    43  	prioTableToastCacheIORatio
    44  	prioTableToastIORate
    45  	prioTableToastIndexCacheIORatio
    46  	prioTableToastIndexIORate
    47  
    48  	prioDBSize
    49  	prioTableTotalSize
    50  	prioIndexSize
    51  
    52  	prioTableBloatSizePerc
    53  	prioTableBloatSize
    54  	prioIndexBloatSizePerc
    55  	prioIndexBloatSize
    56  
    57  	prioLocksUtilization
    58  	prioDBLocksHeldCount
    59  	prioDBLocksAwaitedCount
    60  	prioDBDeadlocksRate
    61  
    62  	prioAutovacuumWorkersCount
    63  	prioTableAutovacuumSinceTime
    64  	prioTableVacuumSinceTime
    65  	prioTableAutoAnalyzeSinceTime
    66  	prioTableLastAnalyzeAgo
    67  
    68  	prioCheckpointsRate
    69  	prioCheckpointsTime
    70  	prioBGWriterHaltsRate
    71  	prioBuffersIORate
    72  	prioBuffersBackendFsyncRate
    73  	prioBuffersAllocRate
    74  	prioTXIDExhaustionTowardsAutovacuumPerc
    75  	prioTXIDExhaustionPerc
    76  	prioTXIDExhaustionOldestTXIDNum
    77  	prioTableRowsDeadRatio
    78  	prioTableRowsCount
    79  	prioTableNullColumns
    80  	prioIndexUsageStatus
    81  
    82  	prioReplicationAppWALLagSize
    83  	prioReplicationAppWALLagTime
    84  	prioReplicationSlotFilesCount
    85  	prioDBConflictsRate
    86  	prioDBConflictsReasonRate
    87  
    88  	prioWALIORate
    89  	prioWALFilesCount
    90  	prioWALArchivingFilesCount
    91  
    92  	prioDatabasesCount
    93  	prioCatalogRelationsCount
    94  	prioCatalogRelationsSize
    95  
    96  	prioUptime
    97  )
    98  
    99  var baseCharts = module.Charts{
   100  	serverConnectionsUtilizationChart.Copy(),
   101  	serverConnectionsUsageChart.Copy(),
   102  	serverConnectionsStateCount.Copy(),
   103  	locksUtilization.Copy(),
   104  	checkpointsChart.Copy(),
   105  	checkpointWriteChart.Copy(),
   106  	buffersIORateChart.Copy(),
   107  	buffersAllocRateChart.Copy(),
   108  	bgWriterHaltsRateChart.Copy(),
   109  	buffersBackendFsyncRateChart.Copy(),
   110  	walIORateChart.Copy(),
   111  	autovacuumWorkersCountChart.Copy(),
   112  	txidExhaustionTowardsAutovacuumPercChart.Copy(),
   113  	txidExhaustionPercChart.Copy(),
   114  	txidExhaustionOldestTXIDNumChart.Copy(),
   115  
   116  	catalogRelationSCountChart.Copy(),
   117  	catalogRelationsSizeChart.Copy(),
   118  	serverUptimeChart.Copy(),
   119  	databasesCountChart.Copy(),
   120  }
   121  
   122  var walFilesCharts = module.Charts{
   123  	walFilesCountChart.Copy(),
   124  	walArchivingFilesCountChart.Copy(),
   125  }
   126  
   127  func (p *Postgres) addWALFilesCharts() {
   128  	charts := walFilesCharts.Copy()
   129  
   130  	if err := p.Charts().Add(*charts...); err != nil {
   131  		p.Warning(err)
   132  	}
   133  }
   134  
   135  var (
   136  	serverConnectionsUtilizationChart = module.Chart{
   137  		ID:       "connections_utilization",
   138  		Title:    "Connections utilization",
   139  		Units:    "percentage",
   140  		Fam:      "connections",
   141  		Ctx:      "postgres.connections_utilization",
   142  		Priority: prioConnectionsUtilization,
   143  		Dims: module.Dims{
   144  			{ID: "server_connections_utilization", Name: "used"},
   145  		},
   146  	}
   147  	serverConnectionsUsageChart = module.Chart{
   148  		ID:       "connections_usage",
   149  		Title:    "Connections usage",
   150  		Units:    "connections",
   151  		Fam:      "connections",
   152  		Ctx:      "postgres.connections_usage",
   153  		Priority: prioConnectionsUsage,
   154  		Type:     module.Stacked,
   155  		Dims: module.Dims{
   156  			{ID: "server_connections_available", Name: "available"},
   157  			{ID: "server_connections_used", Name: "used"},
   158  		},
   159  	}
   160  	serverConnectionsStateCount = module.Chart{
   161  		ID:       "connections_state",
   162  		Title:    "Connections in each state",
   163  		Units:    "connections",
   164  		Fam:      "connections",
   165  		Ctx:      "postgres.connections_state_count",
   166  		Priority: prioConnectionsStateCount,
   167  		Type:     module.Stacked,
   168  		Dims: module.Dims{
   169  			{ID: "server_connections_state_active", Name: "active"},
   170  			{ID: "server_connections_state_idle", Name: "idle"},
   171  			{ID: "server_connections_state_idle_in_transaction", Name: "idle_in_transaction"},
   172  			{ID: "server_connections_state_idle_in_transaction_aborted", Name: "idle_in_transaction_aborted"},
   173  			{ID: "server_connections_state_fastpath_function_call", Name: "fastpath_function_call"},
   174  			{ID: "server_connections_state_disabled", Name: "disabled"},
   175  		},
   176  	}
   177  
   178  	locksUtilization = module.Chart{
   179  		ID:       "locks_utilization",
   180  		Title:    "Acquired locks utilization",
   181  		Units:    "percentage",
   182  		Fam:      "locks",
   183  		Ctx:      "postgres.locks_utilization",
   184  		Priority: prioLocksUtilization,
   185  		Dims: module.Dims{
   186  			{ID: "locks_utilization", Name: "used"},
   187  		},
   188  	}
   189  
   190  	checkpointsChart = module.Chart{
   191  		ID:       "checkpoints_rate",
   192  		Title:    "Checkpoints",
   193  		Units:    "checkpoints/s",
   194  		Fam:      "maintenance",
   195  		Ctx:      "postgres.checkpoints_rate",
   196  		Priority: prioCheckpointsRate,
   197  		Type:     module.Stacked,
   198  		Dims: module.Dims{
   199  			{ID: "checkpoints_timed", Name: "scheduled", Algo: module.Incremental},
   200  			{ID: "checkpoints_req", Name: "requested", Algo: module.Incremental},
   201  		},
   202  	}
   203  	// TODO: should be seconds, also it is units/s when using incremental...
   204  	checkpointWriteChart = module.Chart{
   205  		ID:       "checkpoints_time",
   206  		Title:    "Checkpoint time",
   207  		Units:    "milliseconds",
   208  		Fam:      "maintenance",
   209  		Ctx:      "postgres.checkpoints_time",
   210  		Priority: prioCheckpointsTime,
   211  		Type:     module.Stacked,
   212  		Dims: module.Dims{
   213  			{ID: "checkpoint_write_time", Name: "write", Algo: module.Incremental},
   214  			{ID: "checkpoint_sync_time", Name: "sync", Algo: module.Incremental},
   215  		},
   216  	}
   217  	bgWriterHaltsRateChart = module.Chart{
   218  		ID:       "bgwriter_halts_rate",
   219  		Title:    "Background writer scan halts",
   220  		Units:    "halts/s",
   221  		Fam:      "maintenance",
   222  		Ctx:      "postgres.bgwriter_halts_rate",
   223  		Priority: prioBGWriterHaltsRate,
   224  		Dims: module.Dims{
   225  			{ID: "maxwritten_clean", Name: "maxwritten", Algo: module.Incremental},
   226  		},
   227  	}
   228  
   229  	buffersIORateChart = module.Chart{
   230  		ID:       "buffers_io_rate",
   231  		Title:    "Buffers written rate",
   232  		Units:    "B/s",
   233  		Fam:      "maintenance",
   234  		Ctx:      "postgres.buffers_io_rate",
   235  		Priority: prioBuffersIORate,
   236  		Type:     module.Area,
   237  		Dims: module.Dims{
   238  			{ID: "buffers_checkpoint", Name: "checkpoint", Algo: module.Incremental},
   239  			{ID: "buffers_backend", Name: "backend", Algo: module.Incremental},
   240  			{ID: "buffers_clean", Name: "bgwriter", Algo: module.Incremental},
   241  		},
   242  	}
   243  	buffersBackendFsyncRateChart = module.Chart{
   244  		ID:       "buffers_backend_fsync_rate",
   245  		Title:    "Backend fsync calls",
   246  		Units:    "calls/s",
   247  		Fam:      "maintenance",
   248  		Ctx:      "postgres.buffers_backend_fsync_rate",
   249  		Priority: prioBuffersBackendFsyncRate,
   250  		Dims: module.Dims{
   251  			{ID: "buffers_backend_fsync", Name: "fsync", Algo: module.Incremental},
   252  		},
   253  	}
   254  	buffersAllocRateChart = module.Chart{
   255  		ID:       "buffers_alloc_rate",
   256  		Title:    "Buffers allocated",
   257  		Units:    "B/s",
   258  		Fam:      "maintenance",
   259  		Ctx:      "postgres.buffers_allocated_rate",
   260  		Priority: prioBuffersAllocRate,
   261  		Dims: module.Dims{
   262  			{ID: "buffers_alloc", Name: "allocated", Algo: module.Incremental},
   263  		},
   264  	}
   265  
   266  	walIORateChart = module.Chart{
   267  		ID:       "wal_io_rate",
   268  		Title:    "Write-Ahead Log writes",
   269  		Units:    "B/s",
   270  		Fam:      "wal",
   271  		Ctx:      "postgres.wal_io_rate",
   272  		Priority: prioWALIORate,
   273  		Dims: module.Dims{
   274  			{ID: "wal_writes", Name: "written", Algo: module.Incremental},
   275  		},
   276  	}
   277  	walFilesCountChart = module.Chart{
   278  		ID:       "wal_files_count",
   279  		Title:    "Write-Ahead Log files",
   280  		Units:    "files",
   281  		Fam:      "wal",
   282  		Ctx:      "postgres.wal_files_count",
   283  		Priority: prioWALFilesCount,
   284  		Type:     module.Stacked,
   285  		Dims: module.Dims{
   286  			{ID: "wal_written_files", Name: "written"},
   287  			{ID: "wal_recycled_files", Name: "recycled"},
   288  		},
   289  	}
   290  
   291  	walArchivingFilesCountChart = module.Chart{
   292  		ID:       "wal_archiving_files_count",
   293  		Title:    "Write-Ahead Log archived files",
   294  		Units:    "files/s",
   295  		Fam:      "wal",
   296  		Ctx:      "postgres.wal_archiving_files_count",
   297  		Priority: prioWALArchivingFilesCount,
   298  		Type:     module.Stacked,
   299  		Dims: module.Dims{
   300  			{ID: "wal_archive_files_ready_count", Name: "ready"},
   301  			{ID: "wal_archive_files_done_count", Name: "done"},
   302  		},
   303  	}
   304  
   305  	autovacuumWorkersCountChart = module.Chart{
   306  		ID:       "autovacuum_workers_count",
   307  		Title:    "Autovacuum workers",
   308  		Units:    "workers",
   309  		Fam:      "vacuum and analyze",
   310  		Ctx:      "postgres.autovacuum_workers_count",
   311  		Priority: prioAutovacuumWorkersCount,
   312  		Dims: module.Dims{
   313  			{ID: "autovacuum_analyze", Name: "analyze"},
   314  			{ID: "autovacuum_vacuum_analyze", Name: "vacuum_analyze"},
   315  			{ID: "autovacuum_vacuum", Name: "vacuum"},
   316  			{ID: "autovacuum_vacuum_freeze", Name: "vacuum_freeze"},
   317  			{ID: "autovacuum_brin_summarize", Name: "brin_summarize"},
   318  		},
   319  	}
   320  
   321  	txidExhaustionTowardsAutovacuumPercChart = module.Chart{
   322  		ID:       "txid_exhaustion_towards_autovacuum_perc",
   323  		Title:    "Percent towards emergency autovacuum",
   324  		Units:    "percentage",
   325  		Fam:      "maintenance",
   326  		Ctx:      "postgres.txid_exhaustion_towards_autovacuum_perc",
   327  		Priority: prioTXIDExhaustionTowardsAutovacuumPerc,
   328  		Dims: module.Dims{
   329  			{ID: "percent_towards_emergency_autovacuum", Name: "emergency_autovacuum"},
   330  		},
   331  	}
   332  	txidExhaustionPercChart = module.Chart{
   333  		ID:       "txid_exhaustion_perc",
   334  		Title:    "Percent towards transaction ID wraparound",
   335  		Units:    "percentage",
   336  		Fam:      "maintenance",
   337  		Ctx:      "postgres.txid_exhaustion_perc",
   338  		Priority: prioTXIDExhaustionPerc,
   339  		Dims: module.Dims{
   340  			{ID: "percent_towards_wraparound", Name: "txid_exhaustion"},
   341  		},
   342  	}
   343  	txidExhaustionOldestTXIDNumChart = module.Chart{
   344  		ID:       "txid_exhaustion_oldest_txid_num",
   345  		Title:    "Oldest transaction XID",
   346  		Units:    "xid",
   347  		Fam:      "maintenance",
   348  		Ctx:      "postgres.txid_exhaustion_oldest_txid_num",
   349  		Priority: prioTXIDExhaustionOldestTXIDNum,
   350  		Dims: module.Dims{
   351  			{ID: "oldest_current_xid", Name: "xid"},
   352  		},
   353  	}
   354  
   355  	catalogRelationSCountChart = module.Chart{
   356  		ID:       "catalog_relations_count",
   357  		Title:    "Relation count",
   358  		Units:    "relations",
   359  		Fam:      "catalog",
   360  		Ctx:      "postgres.catalog_relations_count",
   361  		Priority: prioCatalogRelationsCount,
   362  		Type:     module.Stacked,
   363  		Dims: module.Dims{
   364  			{ID: "catalog_relkind_r_count", Name: "ordinary_table"},
   365  			{ID: "catalog_relkind_i_count", Name: "index"},
   366  			{ID: "catalog_relkind_S_count", Name: "sequence"},
   367  			{ID: "catalog_relkind_t_count", Name: "toast_table"},
   368  			{ID: "catalog_relkind_v_count", Name: "view"},
   369  			{ID: "catalog_relkind_m_count", Name: "materialized_view"},
   370  			{ID: "catalog_relkind_c_count", Name: "composite_type"},
   371  			{ID: "catalog_relkind_f_count", Name: "foreign_table"},
   372  			{ID: "catalog_relkind_p_count", Name: "partitioned_table"},
   373  			{ID: "catalog_relkind_I_count", Name: "partitioned_index"},
   374  		},
   375  	}
   376  	catalogRelationsSizeChart = module.Chart{
   377  		ID:       "catalog_relations_size",
   378  		Title:    "Relation size",
   379  		Units:    "B",
   380  		Fam:      "catalog",
   381  		Ctx:      "postgres.catalog_relations_size",
   382  		Priority: prioCatalogRelationsSize,
   383  		Type:     module.Stacked,
   384  		Dims: module.Dims{
   385  			{ID: "catalog_relkind_r_size", Name: "ordinary_table"},
   386  			{ID: "catalog_relkind_i_size", Name: "index"},
   387  			{ID: "catalog_relkind_S_size", Name: "sequence"},
   388  			{ID: "catalog_relkind_t_size", Name: "toast_table"},
   389  			{ID: "catalog_relkind_v_size", Name: "view"},
   390  			{ID: "catalog_relkind_m_size", Name: "materialized_view"},
   391  			{ID: "catalog_relkind_c_size", Name: "composite_type"},
   392  			{ID: "catalog_relkind_f_size", Name: "foreign_table"},
   393  			{ID: "catalog_relkind_p_size", Name: "partitioned_table"},
   394  			{ID: "catalog_relkind_I_size", Name: "partitioned_index"},
   395  		},
   396  	}
   397  
   398  	serverUptimeChart = module.Chart{
   399  		ID:       "server_uptime",
   400  		Title:    "Uptime",
   401  		Units:    "seconds",
   402  		Fam:      "uptime",
   403  		Ctx:      "postgres.uptime",
   404  		Priority: prioUptime,
   405  		Dims: module.Dims{
   406  			{ID: "server_uptime", Name: "uptime"},
   407  		},
   408  	}
   409  
   410  	databasesCountChart = module.Chart{
   411  		ID:       "databases_count",
   412  		Title:    "Number of databases",
   413  		Units:    "databases",
   414  		Fam:      "catalog",
   415  		Ctx:      "postgres.databases_count",
   416  		Priority: prioDatabasesCount,
   417  		Dims: module.Dims{
   418  			{ID: "databases_count", Name: "databases"},
   419  		},
   420  	}
   421  
   422  	transactionsDurationChartTmpl = module.Chart{
   423  		ID:       "transactions_duration",
   424  		Title:    "Observed transactions time",
   425  		Units:    "transactions/s",
   426  		Fam:      "transactions",
   427  		Ctx:      "postgres.transactions_duration",
   428  		Priority: prioTransactionsDuration,
   429  		Type:     module.Stacked,
   430  	}
   431  	queriesDurationChartTmpl = module.Chart{
   432  		ID:       "queries_duration",
   433  		Title:    "Observed active queries time",
   434  		Units:    "queries/s",
   435  		Fam:      "queries",
   436  		Ctx:      "postgres.queries_duration",
   437  		Priority: prioQueriesDuration,
   438  		Type:     module.Stacked,
   439  	}
   440  )
   441  
   442  func newRunningTimeHistogramChart(tmpl module.Chart, prefix string, buckets []float64) (*module.Chart, error) {
   443  	chart := tmpl.Copy()
   444  
   445  	for i, v := range buckets {
   446  		dim := &module.Dim{
   447  			ID:   fmt.Sprintf("%s_hist_bucket_%d", prefix, i+1),
   448  			Name: time.Duration(v * float64(time.Second)).String(),
   449  			Algo: module.Incremental,
   450  		}
   451  		if err := chart.AddDim(dim); err != nil {
   452  			return nil, err
   453  		}
   454  	}
   455  
   456  	dim := &module.Dim{
   457  		ID:   fmt.Sprintf("%s_hist_bucket_inf", prefix),
   458  		Name: "+Inf",
   459  		Algo: module.Incremental,
   460  	}
   461  	if err := chart.AddDim(dim); err != nil {
   462  		return nil, err
   463  	}
   464  
   465  	return chart, nil
   466  }
   467  
   468  func (p *Postgres) addTransactionsRunTimeHistogramChart() {
   469  	chart, err := newRunningTimeHistogramChart(
   470  		transactionsDurationChartTmpl,
   471  		"transaction_running_time",
   472  		p.XactTimeHistogram,
   473  	)
   474  	if err != nil {
   475  		p.Warning(err)
   476  		return
   477  	}
   478  	if err := p.Charts().Add(chart); err != nil {
   479  		p.Warning(err)
   480  	}
   481  }
   482  
   483  func (p *Postgres) addQueriesRunTimeHistogramChart() {
   484  	chart, err := newRunningTimeHistogramChart(
   485  		queriesDurationChartTmpl,
   486  		"query_running_time",
   487  		p.QueryTimeHistogram,
   488  	)
   489  	if err != nil {
   490  		p.Warning(err)
   491  		return
   492  	}
   493  	if err := p.Charts().Add(chart); err != nil {
   494  		p.Warning(err)
   495  	}
   496  }
   497  
   498  var (
   499  	replicationStandbyAppCharts = module.Charts{
   500  		replicationAppWALLagSizeChartTmpl.Copy(),
   501  		replicationAppWALLagTimeChartTmpl.Copy(),
   502  	}
   503  	replicationAppWALLagSizeChartTmpl = module.Chart{
   504  		ID:       "replication_app_%s_wal_lag_size",
   505  		Title:    "Standby application WAL lag size",
   506  		Units:    "B",
   507  		Fam:      "replication",
   508  		Ctx:      "postgres.replication_app_wal_lag_size",
   509  		Priority: prioReplicationAppWALLagSize,
   510  		Dims: module.Dims{
   511  			{ID: "repl_standby_app_%s_wal_sent_lag_size", Name: "sent_lag"},
   512  			{ID: "repl_standby_app_%s_wal_write_lag_size", Name: "write_lag"},
   513  			{ID: "repl_standby_app_%s_wal_flush_lag_size", Name: "flush_lag"},
   514  			{ID: "repl_standby_app_%s_wal_replay_lag_size", Name: "replay_lag"},
   515  		},
   516  	}
   517  	replicationAppWALLagTimeChartTmpl = module.Chart{
   518  		ID:       "replication_app_%s_wal_lag_time",
   519  		Title:    "Standby application WAL lag time",
   520  		Units:    "seconds",
   521  		Fam:      "replication",
   522  		Ctx:      "postgres.replication_app_wal_lag_time",
   523  		Priority: prioReplicationAppWALLagTime,
   524  		Dims: module.Dims{
   525  			{ID: "repl_standby_app_%s_wal_write_lag_time", Name: "write_lag"},
   526  			{ID: "repl_standby_app_%s_wal_flush_lag_time", Name: "flush_lag"},
   527  			{ID: "repl_standby_app_%s_wal_replay_lag_time", Name: "replay_lag"},
   528  		},
   529  	}
   530  )
   531  
   532  func newReplicationStandbyAppCharts(app string) *module.Charts {
   533  	charts := replicationStandbyAppCharts.Copy()
   534  	for _, c := range *charts {
   535  		c.ID = fmt.Sprintf(c.ID, app)
   536  		c.Labels = []module.Label{
   537  			{Key: "application", Value: app},
   538  		}
   539  		for _, d := range c.Dims {
   540  			d.ID = fmt.Sprintf(d.ID, app)
   541  		}
   542  	}
   543  	return charts
   544  }
   545  
   546  func (p *Postgres) addNewReplicationStandbyAppCharts(app string) {
   547  	charts := newReplicationStandbyAppCharts(app)
   548  	if err := p.Charts().Add(*charts...); err != nil {
   549  		p.Warning(err)
   550  	}
   551  }
   552  
   553  func (p *Postgres) removeReplicationStandbyAppCharts(app string) {
   554  	prefix := fmt.Sprintf("replication_standby_app_%s_", app)
   555  	for _, c := range *p.Charts() {
   556  		if strings.HasPrefix(c.ID, prefix) {
   557  			c.MarkRemove()
   558  			c.MarkNotCreated()
   559  		}
   560  	}
   561  }
   562  
   563  var (
   564  	replicationSlotCharts = module.Charts{
   565  		replicationSlotFilesCountChartTmpl.Copy(),
   566  	}
   567  	replicationSlotFilesCountChartTmpl = module.Chart{
   568  		ID:       "replication_slot_%s_files_count",
   569  		Title:    "Replication slot files",
   570  		Units:    "files",
   571  		Fam:      "replication",
   572  		Ctx:      "postgres.replication_slot_files_count",
   573  		Priority: prioReplicationSlotFilesCount,
   574  		Dims: module.Dims{
   575  			{ID: "repl_slot_%s_replslot_wal_keep", Name: "wal_keep"},
   576  			{ID: "repl_slot_%s_replslot_files", Name: "pg_replslot_files"},
   577  		},
   578  	}
   579  )
   580  
   581  func newReplicationSlotCharts(slot string) *module.Charts {
   582  	charts := replicationSlotCharts.Copy()
   583  	for _, c := range *charts {
   584  		c.ID = fmt.Sprintf(c.ID, slot)
   585  		c.Labels = []module.Label{
   586  			{Key: "slot", Value: slot},
   587  		}
   588  		for _, d := range c.Dims {
   589  			d.ID = fmt.Sprintf(d.ID, slot)
   590  		}
   591  	}
   592  	return charts
   593  }
   594  
   595  func (p *Postgres) addNewReplicationSlotCharts(slot string) {
   596  	charts := newReplicationSlotCharts(slot)
   597  	if err := p.Charts().Add(*charts...); err != nil {
   598  		p.Warning(err)
   599  	}
   600  }
   601  
   602  func (p *Postgres) removeReplicationSlotCharts(slot string) {
   603  	prefix := fmt.Sprintf("replication_slot_%s_", slot)
   604  	for _, c := range *p.Charts() {
   605  		if strings.HasPrefix(c.ID, prefix) {
   606  			c.MarkRemove()
   607  			c.MarkNotCreated()
   608  		}
   609  	}
   610  }
   611  
   612  var (
   613  	dbChartsTmpl = module.Charts{
   614  		dbTransactionsRatioChartTmpl.Copy(),
   615  		dbTransactionsRateChartTmpl.Copy(),
   616  		dbConnectionsUtilizationChartTmpl.Copy(),
   617  		dbConnectionsCountChartTmpl.Copy(),
   618  		dbCacheIORatioChartTmpl.Copy(),
   619  		dbIORateChartTmpl.Copy(),
   620  		dbOpsFetchedRowsRatioChartTmpl.Copy(),
   621  		dbOpsReadRowsRateChartTmpl.Copy(),
   622  		dbOpsWriteRowsRateChartTmpl.Copy(),
   623  		dbDeadlocksRateChartTmpl.Copy(),
   624  		dbLocksHeldCountChartTmpl.Copy(),
   625  		dbLocksAwaitedCountChartTmpl.Copy(),
   626  		dbTempFilesCreatedRateChartTmpl.Copy(),
   627  		dbTempFilesIORateChartTmpl.Copy(),
   628  		dbSizeChartTmpl.Copy(),
   629  	}
   630  	dbTransactionsRatioChartTmpl = module.Chart{
   631  		ID:       "db_%s_transactions_ratio",
   632  		Title:    "Database transactions ratio",
   633  		Units:    "percentage",
   634  		Fam:      "transactions",
   635  		Ctx:      "postgres.db_transactions_ratio",
   636  		Priority: prioDBTransactionsRatio,
   637  		Type:     module.Stacked,
   638  		Dims: module.Dims{
   639  			{ID: "db_%s_xact_commit", Name: "committed", Algo: module.PercentOfIncremental},
   640  			{ID: "db_%s_xact_rollback", Name: "rollback", Algo: module.PercentOfIncremental},
   641  		},
   642  	}
   643  	dbTransactionsRateChartTmpl = module.Chart{
   644  		ID:       "db_%s_transactions_rate",
   645  		Title:    "Database transactions",
   646  		Units:    "transactions/s",
   647  		Fam:      "transactions",
   648  		Ctx:      "postgres.db_transactions_rate",
   649  		Priority: prioDBTransactionsRate,
   650  		Dims: module.Dims{
   651  			{ID: "db_%s_xact_commit", Name: "committed", Algo: module.Incremental},
   652  			{ID: "db_%s_xact_rollback", Name: "rollback", Algo: module.Incremental},
   653  		},
   654  	}
   655  	dbConnectionsUtilizationChartTmpl = module.Chart{
   656  		ID:       "db_%s_connections_utilization",
   657  		Title:    "Database connections utilization",
   658  		Units:    "percentage",
   659  		Fam:      "connections",
   660  		Ctx:      "postgres.db_connections_utilization",
   661  		Priority: prioDBConnectionsUtilization,
   662  		Dims: module.Dims{
   663  			{ID: "db_%s_numbackends_utilization", Name: "used"},
   664  		},
   665  	}
   666  	dbConnectionsCountChartTmpl = module.Chart{
   667  		ID:       "db_%s_connections",
   668  		Title:    "Database connections",
   669  		Units:    "connections",
   670  		Fam:      "connections",
   671  		Ctx:      "postgres.db_connections_count",
   672  		Priority: prioDBConnectionsCount,
   673  		Dims: module.Dims{
   674  			{ID: "db_%s_numbackends", Name: "connections"},
   675  		},
   676  	}
   677  	dbCacheIORatioChartTmpl = module.Chart{
   678  		ID:       "db_%s_cache_io_ratio",
   679  		Title:    "Database buffer cache miss ratio",
   680  		Units:    "percentage",
   681  		Fam:      "cache",
   682  		Ctx:      "postgres.db_cache_io_ratio",
   683  		Priority: prioDBCacheIORatio,
   684  		Dims: module.Dims{
   685  			{ID: "db_%s_blks_read_perc", Name: "miss"},
   686  		},
   687  	}
   688  	dbIORateChartTmpl = module.Chart{
   689  		ID:       "db_%s_io_rate",
   690  		Title:    "Database reads",
   691  		Units:    "B/s",
   692  		Fam:      "cache",
   693  		Ctx:      "postgres.db_io_rate",
   694  		Priority: prioDBIORate,
   695  		Type:     module.Area,
   696  		Dims: module.Dims{
   697  			{ID: "db_%s_blks_hit", Name: "memory", Algo: module.Incremental},
   698  			{ID: "db_%s_blks_read", Name: "disk", Algo: module.Incremental},
   699  		},
   700  	}
   701  	dbOpsFetchedRowsRatioChartTmpl = module.Chart{
   702  		ID:       "db_%s_db_ops_fetched_rows_ratio",
   703  		Title:    "Database rows fetched ratio",
   704  		Units:    "percentage",
   705  		Fam:      "throughput",
   706  		Ctx:      "postgres.db_ops_fetched_rows_ratio",
   707  		Priority: prioDBOpsFetchedRowsRatio,
   708  		Dims: module.Dims{
   709  			{ID: "db_%s_tup_fetched_perc", Name: "fetched"},
   710  		},
   711  	}
   712  	dbOpsReadRowsRateChartTmpl = module.Chart{
   713  		ID:       "db_%s_ops_read_rows_rate",
   714  		Title:    "Database rows read",
   715  		Units:    "rows/s",
   716  		Fam:      "throughput",
   717  		Ctx:      "postgres.db_ops_read_rows_rate",
   718  		Priority: prioDBOpsReadRowsRate,
   719  		Dims: module.Dims{
   720  			{ID: "db_%s_tup_returned", Name: "returned", Algo: module.Incremental},
   721  			{ID: "db_%s_tup_fetched", Name: "fetched", Algo: module.Incremental},
   722  		},
   723  	}
   724  	dbOpsWriteRowsRateChartTmpl = module.Chart{
   725  		ID:       "db_%s_ops_write_rows_rate",
   726  		Title:    "Database rows written",
   727  		Units:    "rows/s",
   728  		Fam:      "throughput",
   729  		Ctx:      "postgres.db_ops_write_rows_rate",
   730  		Priority: prioDBOpsWriteRowsRate,
   731  		Dims: module.Dims{
   732  			{ID: "db_%s_tup_inserted", Name: "inserted", Algo: module.Incremental},
   733  			{ID: "db_%s_tup_deleted", Name: "deleted", Algo: module.Incremental},
   734  			{ID: "db_%s_tup_updated", Name: "updated", Algo: module.Incremental},
   735  		},
   736  	}
   737  	dbConflictsRateChartTmpl = module.Chart{
   738  		ID:       "db_%s_conflicts_rate",
   739  		Title:    "Database canceled queries",
   740  		Units:    "queries/s",
   741  		Fam:      "replication",
   742  		Ctx:      "postgres.db_conflicts_rate",
   743  		Priority: prioDBConflictsRate,
   744  		Dims: module.Dims{
   745  			{ID: "db_%s_conflicts", Name: "conflicts", Algo: module.Incremental},
   746  		},
   747  	}
   748  	dbConflictsReasonRateChartTmpl = module.Chart{
   749  		ID:       "db_%s_conflicts_reason_rate",
   750  		Title:    "Database canceled queries by reason",
   751  		Units:    "queries/s",
   752  		Fam:      "replication",
   753  		Ctx:      "postgres.db_conflicts_reason_rate",
   754  		Priority: prioDBConflictsReasonRate,
   755  		Dims: module.Dims{
   756  			{ID: "db_%s_confl_tablespace", Name: "tablespace", Algo: module.Incremental},
   757  			{ID: "db_%s_confl_lock", Name: "lock", Algo: module.Incremental},
   758  			{ID: "db_%s_confl_snapshot", Name: "snapshot", Algo: module.Incremental},
   759  			{ID: "db_%s_confl_bufferpin", Name: "bufferpin", Algo: module.Incremental},
   760  			{ID: "db_%s_confl_deadlock", Name: "deadlock", Algo: module.Incremental},
   761  		},
   762  	}
   763  	dbDeadlocksRateChartTmpl = module.Chart{
   764  		ID:       "db_%s_deadlocks_rate",
   765  		Title:    "Database deadlocks",
   766  		Units:    "deadlocks/s",
   767  		Fam:      "locks",
   768  		Ctx:      "postgres.db_deadlocks_rate",
   769  		Priority: prioDBDeadlocksRate,
   770  		Dims: module.Dims{
   771  			{ID: "db_%s_deadlocks", Name: "deadlocks", Algo: module.Incremental},
   772  		},
   773  	}
   774  	dbLocksHeldCountChartTmpl = module.Chart{
   775  		ID:       "db_%s_locks_held",
   776  		Title:    "Database locks held",
   777  		Units:    "locks",
   778  		Fam:      "locks",
   779  		Ctx:      "postgres.db_locks_held_count",
   780  		Priority: prioDBLocksHeldCount,
   781  		Type:     module.Stacked,
   782  		Dims: module.Dims{
   783  			{ID: "db_%s_lock_mode_AccessShareLock_held", Name: "access_share"},
   784  			{ID: "db_%s_lock_mode_RowShareLock_held", Name: "row_share"},
   785  			{ID: "db_%s_lock_mode_RowExclusiveLock_held", Name: "row_exclusive"},
   786  			{ID: "db_%s_lock_mode_ShareUpdateExclusiveLock_held", Name: "share_update"},
   787  			{ID: "db_%s_lock_mode_ShareLock_held", Name: "share"},
   788  			{ID: "db_%s_lock_mode_ShareRowExclusiveLock_held", Name: "share_row_exclusive"},
   789  			{ID: "db_%s_lock_mode_ExclusiveLock_held", Name: "exclusive"},
   790  			{ID: "db_%s_lock_mode_AccessExclusiveLock_held", Name: "access_exclusive"},
   791  		},
   792  	}
   793  	dbLocksAwaitedCountChartTmpl = module.Chart{
   794  		ID:       "db_%s_locks_awaited_count",
   795  		Title:    "Database locks awaited",
   796  		Units:    "locks",
   797  		Fam:      "locks",
   798  		Ctx:      "postgres.db_locks_awaited_count",
   799  		Priority: prioDBLocksAwaitedCount,
   800  		Type:     module.Stacked,
   801  		Dims: module.Dims{
   802  			{ID: "db_%s_lock_mode_AccessShareLock_awaited", Name: "access_share"},
   803  			{ID: "db_%s_lock_mode_RowShareLock_awaited", Name: "row_share"},
   804  			{ID: "db_%s_lock_mode_RowExclusiveLock_awaited", Name: "row_exclusive"},
   805  			{ID: "db_%s_lock_mode_ShareUpdateExclusiveLock_awaited", Name: "share_update"},
   806  			{ID: "db_%s_lock_mode_ShareLock_awaited", Name: "share"},
   807  			{ID: "db_%s_lock_mode_ShareRowExclusiveLock_awaited", Name: "share_row_exclusive"},
   808  			{ID: "db_%s_lock_mode_ExclusiveLock_awaited", Name: "exclusive"},
   809  			{ID: "db_%s_lock_mode_AccessExclusiveLock_awaited", Name: "access_exclusive"},
   810  		},
   811  	}
   812  	dbTempFilesCreatedRateChartTmpl = module.Chart{
   813  		ID:       "db_%s_temp_files_files_created_rate",
   814  		Title:    "Database created temporary files",
   815  		Units:    "files/s",
   816  		Fam:      "throughput",
   817  		Ctx:      "postgres.db_temp_files_created_rate",
   818  		Priority: prioDBTempFilesCreatedRate,
   819  		Dims: module.Dims{
   820  			{ID: "db_%s_temp_files", Name: "created", Algo: module.Incremental},
   821  		},
   822  	}
   823  	dbTempFilesIORateChartTmpl = module.Chart{
   824  		ID:       "db_%s_temp_files_io_rate",
   825  		Title:    "Database temporary files data written to disk",
   826  		Units:    "B/s",
   827  		Fam:      "throughput",
   828  		Ctx:      "postgres.db_temp_files_io_rate",
   829  		Priority: prioDBTempFilesIORate,
   830  		Dims: module.Dims{
   831  			{ID: "db_%s_temp_bytes", Name: "written", Algo: module.Incremental},
   832  		},
   833  	}
   834  	dbSizeChartTmpl = module.Chart{
   835  		ID:       "db_%s_size",
   836  		Title:    "Database size",
   837  		Units:    "B",
   838  		Fam:      "size",
   839  		Ctx:      "postgres.db_size",
   840  		Priority: prioDBSize,
   841  		Dims: module.Dims{
   842  			{ID: "db_%s_size", Name: "size"},
   843  		},
   844  	}
   845  )
   846  
   847  func (p *Postgres) addDBConflictsCharts(db *dbMetrics) {
   848  	tmpl := module.Charts{
   849  		dbConflictsRateChartTmpl.Copy(),
   850  		dbConflictsReasonRateChartTmpl.Copy(),
   851  	}
   852  	charts := newDatabaseCharts(tmpl.Copy(), db)
   853  
   854  	if err := p.Charts().Add(*charts...); err != nil {
   855  		p.Warning(err)
   856  	}
   857  }
   858  
   859  func newDatabaseCharts(tmpl *module.Charts, db *dbMetrics) *module.Charts {
   860  	charts := tmpl.Copy()
   861  	for _, c := range *charts {
   862  		c.ID = fmt.Sprintf(c.ID, db.name)
   863  		c.Labels = []module.Label{
   864  			{Key: "database", Value: db.name},
   865  		}
   866  		for _, d := range c.Dims {
   867  			d.ID = fmt.Sprintf(d.ID, db.name)
   868  		}
   869  	}
   870  	return charts
   871  }
   872  
   873  func (p *Postgres) addNewDatabaseCharts(db *dbMetrics) {
   874  	charts := newDatabaseCharts(dbChartsTmpl.Copy(), db)
   875  
   876  	if db.size == nil {
   877  		_ = charts.Remove(fmt.Sprintf(dbSizeChartTmpl.ID, db.name))
   878  	}
   879  
   880  	if err := p.Charts().Add(*charts...); err != nil {
   881  		p.Warning(err)
   882  	}
   883  }
   884  
   885  func (p *Postgres) removeDatabaseCharts(db *dbMetrics) {
   886  	prefix := fmt.Sprintf("db_%s_", db.name)
   887  	for _, c := range *p.Charts() {
   888  		if strings.HasPrefix(c.ID, prefix) {
   889  			c.MarkRemove()
   890  			c.MarkNotCreated()
   891  		}
   892  	}
   893  }
   894  
   895  var (
   896  	tableChartsTmpl = module.Charts{
   897  		tableRowsCountChartTmpl.Copy(),
   898  		tableDeadRowsDeadRatioChartTmpl.Copy(),
   899  		tableOpsRowsRateChartTmpl.Copy(),
   900  		tableOpsRowsHOTRatioChartTmpl.Copy(),
   901  		tableOpsRowsHOTRateChartTmpl.Copy(),
   902  		tableScansRateChartTmpl.Copy(),
   903  		tableScansRowsRateChartTmpl.Copy(),
   904  		tableNullColumnsCountChartTmpl.Copy(),
   905  		tableTotalSizeChartTmpl.Copy(),
   906  		tableBloatSizePercChartTmpl.Copy(),
   907  		tableBloatSizeChartTmpl.Copy(),
   908  	}
   909  
   910  	tableDeadRowsDeadRatioChartTmpl = module.Chart{
   911  		ID:       "table_%s_db_%s_schema_%s_rows_dead_ratio",
   912  		Title:    "Table dead rows",
   913  		Units:    "%",
   914  		Fam:      "maintenance",
   915  		Ctx:      "postgres.table_rows_dead_ratio",
   916  		Priority: prioTableRowsDeadRatio,
   917  		Dims: module.Dims{
   918  			{ID: "table_%s_db_%s_schema_%s_n_dead_tup_perc", Name: "dead"},
   919  		},
   920  	}
   921  	tableRowsCountChartTmpl = module.Chart{
   922  		ID:       "table_%s_db_%s_schema_%s_rows_count",
   923  		Title:    "Table total rows",
   924  		Units:    "rows",
   925  		Fam:      "maintenance",
   926  		Ctx:      "postgres.table_rows_count",
   927  		Priority: prioTableRowsCount,
   928  		Type:     module.Stacked,
   929  		Dims: module.Dims{
   930  			{ID: "table_%s_db_%s_schema_%s_n_live_tup", Name: "live"},
   931  			{ID: "table_%s_db_%s_schema_%s_n_dead_tup", Name: "dead"},
   932  		},
   933  	}
   934  	tableOpsRowsRateChartTmpl = module.Chart{
   935  		ID:       "table_%s_db_%s_schema_%s_ops_rows_rate",
   936  		Title:    "Table throughput",
   937  		Units:    "rows/s",
   938  		Fam:      "throughput",
   939  		Ctx:      "postgres.table_ops_rows_rate",
   940  		Priority: prioTableOpsRowsRate,
   941  		Type:     module.Stacked,
   942  		Dims: module.Dims{
   943  			{ID: "table_%s_db_%s_schema_%s_n_tup_ins", Name: "inserted", Algo: module.Incremental},
   944  			{ID: "table_%s_db_%s_schema_%s_n_tup_del", Name: "deleted", Algo: module.Incremental},
   945  			{ID: "table_%s_db_%s_schema_%s_n_tup_upd", Name: "updated", Algo: module.Incremental},
   946  		},
   947  	}
   948  	tableOpsRowsHOTRatioChartTmpl = module.Chart{
   949  		ID:       "table_%s_db_%s_schema_%s_ops_rows_hot_ratio",
   950  		Title:    "Table HOT updates ratio",
   951  		Units:    "percentage",
   952  		Fam:      "throughput",
   953  		Ctx:      "postgres.table_ops_rows_hot_ratio",
   954  		Priority: prioTableOpsRowsHOTRatio,
   955  		Dims: module.Dims{
   956  			{ID: "table_%s_db_%s_schema_%s_n_tup_hot_upd_perc", Name: "hot"},
   957  		},
   958  	}
   959  	tableOpsRowsHOTRateChartTmpl = module.Chart{
   960  		ID:       "table_%s_db_%s_schema_%s_ops_rows_hot_rate",
   961  		Title:    "Table HOT updates",
   962  		Units:    "rows/s",
   963  		Fam:      "throughput",
   964  		Ctx:      "postgres.table_ops_rows_hot_rate",
   965  		Priority: prioTableOpsRowsHOTRate,
   966  		Dims: module.Dims{
   967  			{ID: "table_%s_db_%s_schema_%s_n_tup_hot_upd", Name: "hot", Algo: module.Incremental},
   968  		},
   969  	}
   970  	tableCacheIORatioChartTmpl = module.Chart{
   971  		ID:       "table_%s_db_%s_schema_%s_cache_io_ratio",
   972  		Title:    "Table I/O cache miss ratio",
   973  		Units:    "percentage",
   974  		Fam:      "cache",
   975  		Ctx:      "postgres.table_cache_io_ratio",
   976  		Priority: prioTableCacheIORatio,
   977  		Dims: module.Dims{
   978  			{ID: "table_%s_db_%s_schema_%s_heap_blks_read_perc", Name: "miss"},
   979  		},
   980  	}
   981  	tableIORateChartTmpl = module.Chart{
   982  		ID:       "table_%s_db_%s_schema_%s_io_rate",
   983  		Title:    "Table I/O",
   984  		Units:    "B/s",
   985  		Fam:      "cache",
   986  		Ctx:      "postgres.table_io_rate",
   987  		Priority: prioTableIORate,
   988  		Dims: module.Dims{
   989  			{ID: "table_%s_db_%s_schema_%s_heap_blks_hit", Name: "memory", Algo: module.Incremental},
   990  			{ID: "table_%s_db_%s_schema_%s_heap_blks_read", Name: "disk", Algo: module.Incremental},
   991  		},
   992  	}
   993  	tableIndexCacheIORatioChartTmpl = module.Chart{
   994  		ID:       "table_%s_db_%s_schema_%s_index_cache_io_ratio",
   995  		Title:    "Table index I/O cache miss ratio",
   996  		Units:    "percentage",
   997  		Fam:      "cache",
   998  		Ctx:      "postgres.table_index_cache_io_ratio",
   999  		Priority: prioTableIndexCacheIORatio,
  1000  		Dims: module.Dims{
  1001  			{ID: "table_%s_db_%s_schema_%s_idx_blks_read_perc", Name: "miss", Algo: module.Incremental},
  1002  		},
  1003  	}
  1004  	tableIndexIORateChartTmpl = module.Chart{
  1005  		ID:       "table_%s_db_%s_schema_%s_index_io_rate",
  1006  		Title:    "Table index I/O",
  1007  		Units:    "B/s",
  1008  		Fam:      "cache",
  1009  		Ctx:      "postgres.table_index_io_rate",
  1010  		Priority: prioTableIndexIORate,
  1011  		Dims: module.Dims{
  1012  			{ID: "table_%s_db_%s_schema_%s_idx_blks_hit", Name: "memory", Algo: module.Incremental},
  1013  			{ID: "table_%s_db_%s_schema_%s_idx_blks_read", Name: "disk", Algo: module.Incremental},
  1014  		},
  1015  	}
  1016  	tableTOASCacheIORatioChartTmpl = module.Chart{
  1017  		ID:       "table_%s_db_%s_schema_%s_toast_cache_io_ratio",
  1018  		Title:    "Table TOAST I/O cache miss ratio",
  1019  		Units:    "percentage",
  1020  		Fam:      "cache",
  1021  		Ctx:      "postgres.table_toast_cache_io_ratio",
  1022  		Priority: prioTableToastCacheIORatio,
  1023  		Dims: module.Dims{
  1024  			{ID: "table_%s_db_%s_schema_%s_toast_blks_read_perc", Name: "miss", Algo: module.Incremental},
  1025  		},
  1026  	}
  1027  	tableTOASTIORateChartTmpl = module.Chart{
  1028  		ID:       "table_%s_db_%s_schema_%s_toast_io_rate",
  1029  		Title:    "Table TOAST I/O",
  1030  		Units:    "B/s",
  1031  		Fam:      "cache",
  1032  		Ctx:      "postgres.table_toast_io_rate",
  1033  		Priority: prioTableToastIORate,
  1034  		Dims: module.Dims{
  1035  			{ID: "table_%s_db_%s_schema_%s_toast_blks_hit", Name: "memory", Algo: module.Incremental},
  1036  			{ID: "table_%s_db_%s_schema_%s_toast_blks_read", Name: "disk", Algo: module.Incremental},
  1037  		},
  1038  	}
  1039  	tableTOASTIndexCacheIORatioChartTmpl = module.Chart{
  1040  		ID:       "table_%s_db_%s_schema_%s_toast_index_cache_io_ratio",
  1041  		Title:    "Table TOAST index I/O cache miss ratio",
  1042  		Units:    "percentage",
  1043  		Fam:      "cache",
  1044  		Ctx:      "postgres.table_toast_index_cache_io_ratio",
  1045  		Priority: prioTableToastIndexCacheIORatio,
  1046  		Dims: module.Dims{
  1047  			{ID: "table_%s_db_%s_schema_%s_tidx_blks_read_perc", Name: "miss", Algo: module.Incremental},
  1048  		},
  1049  	}
  1050  	tableTOASTIndexIORateChartTmpl = module.Chart{
  1051  		ID:       "table_%s_db_%s_schema_%s_toast_index_io_rate",
  1052  		Title:    "Table TOAST index I/O",
  1053  		Units:    "B/s",
  1054  		Fam:      "cache",
  1055  		Ctx:      "postgres.table_toast_index_io_rate",
  1056  		Priority: prioTableToastIndexIORate,
  1057  		Dims: module.Dims{
  1058  			{ID: "table_%s_db_%s_schema_%s_tidx_blks_hit", Name: "memory", Algo: module.Incremental},
  1059  			{ID: "table_%s_db_%s_schema_%s_tidx_blks_read", Name: "disk", Algo: module.Incremental},
  1060  		},
  1061  	}
  1062  	tableScansRateChartTmpl = module.Chart{
  1063  		ID:       "table_%s_db_%s_schema_%s_scans_rate",
  1064  		Title:    "Table scans",
  1065  		Units:    "scans/s",
  1066  		Fam:      "throughput",
  1067  		Ctx:      "postgres.table_scans_rate",
  1068  		Priority: prioTableScansRate,
  1069  		Dims: module.Dims{
  1070  			{ID: "table_%s_db_%s_schema_%s_idx_scan", Name: "index", Algo: module.Incremental},
  1071  			{ID: "table_%s_db_%s_schema_%s_seq_scan", Name: "sequential", Algo: module.Incremental},
  1072  		},
  1073  	}
  1074  	tableScansRowsRateChartTmpl = module.Chart{
  1075  		ID:       "table_%s_db_%s_schema_%s_scans_rows_rate",
  1076  		Title:    "Table live rows fetched by scans",
  1077  		Units:    "rows/s",
  1078  		Fam:      "throughput",
  1079  		Ctx:      "postgres.table_scans_rows_rate",
  1080  		Priority: prioTableScansRowsRate,
  1081  		Dims: module.Dims{
  1082  			{ID: "table_%s_db_%s_schema_%s_idx_tup_fetch", Name: "index", Algo: module.Incremental},
  1083  			{ID: "table_%s_db_%s_schema_%s_seq_tup_read", Name: "sequential", Algo: module.Incremental},
  1084  		},
  1085  	}
  1086  	tableAutoVacuumSinceTimeChartTmpl = module.Chart{
  1087  		ID:       "table_%s_db_%s_schema_%s_autovacuum_since_time",
  1088  		Title:    "Table time since last auto VACUUM",
  1089  		Units:    "seconds",
  1090  		Fam:      "vacuum and analyze",
  1091  		Ctx:      "postgres.table_autovacuum_since_time",
  1092  		Priority: prioTableAutovacuumSinceTime,
  1093  		Dims: module.Dims{
  1094  			{ID: "table_%s_db_%s_schema_%s_last_autovacuum_ago", Name: "time"},
  1095  		},
  1096  	}
  1097  	tableVacuumSinceTimeChartTmpl = module.Chart{
  1098  		ID:       "table_%s_db_%s_schema_%s_vacuum_since_time",
  1099  		Title:    "Table time since last manual VACUUM",
  1100  		Units:    "seconds",
  1101  		Fam:      "vacuum and analyze",
  1102  		Ctx:      "postgres.table_vacuum_since_time",
  1103  		Priority: prioTableVacuumSinceTime,
  1104  		Dims: module.Dims{
  1105  			{ID: "table_%s_db_%s_schema_%s_last_vacuum_ago", Name: "time"},
  1106  		},
  1107  	}
  1108  	tableAutoAnalyzeSinceTimeChartTmpl = module.Chart{
  1109  		ID:       "table_%s_db_%s_schema_%s_autoanalyze_since_time",
  1110  		Title:    "Table time since last auto ANALYZE",
  1111  		Units:    "seconds",
  1112  		Fam:      "vacuum and analyze",
  1113  		Ctx:      "postgres.table_autoanalyze_since_time",
  1114  		Priority: prioTableAutoAnalyzeSinceTime,
  1115  		Dims: module.Dims{
  1116  			{ID: "table_%s_db_%s_schema_%s_last_autoanalyze_ago", Name: "time"},
  1117  		},
  1118  	}
  1119  	tableAnalyzeSinceTimeChartTmpl = module.Chart{
  1120  		ID:       "table_%s_db_%s_schema_%s_analyze_since_time",
  1121  		Title:    "Table time since last manual ANALYZE",
  1122  		Units:    "seconds",
  1123  		Fam:      "vacuum and analyze",
  1124  		Ctx:      "postgres.table_analyze_since_time",
  1125  		Priority: prioTableLastAnalyzeAgo,
  1126  		Dims: module.Dims{
  1127  			{ID: "table_%s_db_%s_schema_%s_last_analyze_ago", Name: "time"},
  1128  		},
  1129  	}
  1130  	tableNullColumnsCountChartTmpl = module.Chart{
  1131  		ID:       "table_%s_db_%s_schema_%s_null_columns_count",
  1132  		Title:    "Table null columns",
  1133  		Units:    "columns",
  1134  		Fam:      "maintenance",
  1135  		Ctx:      "postgres.table_null_columns_count",
  1136  		Priority: prioTableNullColumns,
  1137  		Dims: module.Dims{
  1138  			{ID: "table_%s_db_%s_schema_%s_null_columns", Name: "null"},
  1139  		},
  1140  	}
  1141  	tableTotalSizeChartTmpl = module.Chart{
  1142  		ID:       "table_%s_db_%s_schema_%s_total_size",
  1143  		Title:    "Table total size",
  1144  		Units:    "B",
  1145  		Fam:      "size",
  1146  		Ctx:      "postgres.table_total_size",
  1147  		Priority: prioTableTotalSize,
  1148  		Dims: module.Dims{
  1149  			{ID: "table_%s_db_%s_schema_%s_total_size", Name: "size"},
  1150  		},
  1151  	}
  1152  	tableBloatSizePercChartTmpl = module.Chart{
  1153  		ID:       "table_%s_db_%s_schema_%s_bloat_size_perc",
  1154  		Title:    "Table bloat size percentage",
  1155  		Units:    "percentage",
  1156  		Fam:      "bloat",
  1157  		Ctx:      "postgres.table_bloat_size_perc",
  1158  		Priority: prioTableBloatSizePerc,
  1159  		Dims: module.Dims{
  1160  			{ID: "table_%s_db_%s_schema_%s_bloat_size_perc", Name: "bloat"},
  1161  		},
  1162  		Vars: module.Vars{
  1163  			{ID: "table_%s_db_%s_schema_%s_total_size", Name: "table_size"},
  1164  		},
  1165  	}
  1166  	tableBloatSizeChartTmpl = module.Chart{
  1167  		ID:       "table_%s_db_%s_schema_%s_bloat_size",
  1168  		Title:    "Table bloat size",
  1169  		Units:    "B",
  1170  		Fam:      "bloat",
  1171  		Ctx:      "postgres.table_bloat_size",
  1172  		Priority: prioTableBloatSize,
  1173  		Dims: module.Dims{
  1174  			{ID: "table_%s_db_%s_schema_%s_bloat_size", Name: "bloat"},
  1175  		},
  1176  	}
  1177  )
  1178  
  1179  func newTableCharts(tbl *tableMetrics) *module.Charts {
  1180  	charts := tableChartsTmpl.Copy()
  1181  
  1182  	if tbl.bloatSize == nil {
  1183  		_ = charts.Remove(tableBloatSizeChartTmpl.ID)
  1184  		_ = charts.Remove(tableBloatSizePercChartTmpl.ID)
  1185  	}
  1186  
  1187  	for i, chart := range *charts {
  1188  		(*charts)[i] = newTableChart(chart, tbl)
  1189  	}
  1190  
  1191  	return charts
  1192  }
  1193  
  1194  func newTableChart(chart *module.Chart, tbl *tableMetrics) *module.Chart {
  1195  	chart = chart.Copy()
  1196  	chart.ID = fmt.Sprintf(chart.ID, tbl.name, tbl.db, tbl.schema)
  1197  	chart.Labels = []module.Label{
  1198  		{Key: "database", Value: tbl.db},
  1199  		{Key: "schema", Value: tbl.schema},
  1200  		{Key: "table", Value: tbl.name},
  1201  		{Key: "parent_table", Value: tbl.parentName},
  1202  	}
  1203  	for _, d := range chart.Dims {
  1204  		d.ID = fmt.Sprintf(d.ID, tbl.name, tbl.db, tbl.schema)
  1205  	}
  1206  	for _, v := range chart.Vars {
  1207  		v.ID = fmt.Sprintf(v.ID, tbl.name, tbl.db, tbl.schema)
  1208  	}
  1209  	return chart
  1210  }
  1211  
  1212  func (p *Postgres) addNewTableCharts(tbl *tableMetrics) {
  1213  	charts := newTableCharts(tbl)
  1214  	if err := p.Charts().Add(*charts...); err != nil {
  1215  		p.Warning(err)
  1216  	}
  1217  }
  1218  
  1219  func (p *Postgres) addTableLastAutoVacuumAgoChart(tbl *tableMetrics) {
  1220  	chart := newTableChart(tableAutoVacuumSinceTimeChartTmpl.Copy(), tbl)
  1221  
  1222  	if err := p.Charts().Add(chart); err != nil {
  1223  		p.Warning(err)
  1224  	}
  1225  }
  1226  
  1227  func (p *Postgres) addTableLastVacuumAgoChart(tbl *tableMetrics) {
  1228  	chart := newTableChart(tableVacuumSinceTimeChartTmpl.Copy(), tbl)
  1229  
  1230  	if err := p.Charts().Add(chart); err != nil {
  1231  		p.Warning(err)
  1232  	}
  1233  }
  1234  
  1235  func (p *Postgres) addTableLastAutoAnalyzeAgoChart(tbl *tableMetrics) {
  1236  	chart := newTableChart(tableAutoAnalyzeSinceTimeChartTmpl.Copy(), tbl)
  1237  
  1238  	if err := p.Charts().Add(chart); err != nil {
  1239  		p.Warning(err)
  1240  	}
  1241  }
  1242  
  1243  func (p *Postgres) addTableLastAnalyzeAgoChart(tbl *tableMetrics) {
  1244  	chart := newTableChart(tableAnalyzeSinceTimeChartTmpl.Copy(), tbl)
  1245  
  1246  	if err := p.Charts().Add(chart); err != nil {
  1247  		p.Warning(err)
  1248  	}
  1249  }
  1250  
  1251  func (p *Postgres) addTableIOChartsCharts(tbl *tableMetrics) {
  1252  	charts := module.Charts{
  1253  		newTableChart(tableCacheIORatioChartTmpl.Copy(), tbl),
  1254  		newTableChart(tableIORateChartTmpl.Copy(), tbl),
  1255  	}
  1256  
  1257  	if err := p.Charts().Add(charts...); err != nil {
  1258  		p.Warning(err)
  1259  	}
  1260  }
  1261  
  1262  func (p *Postgres) addTableIndexIOCharts(tbl *tableMetrics) {
  1263  	charts := module.Charts{
  1264  		newTableChart(tableIndexCacheIORatioChartTmpl.Copy(), tbl),
  1265  		newTableChart(tableIndexIORateChartTmpl.Copy(), tbl),
  1266  	}
  1267  
  1268  	if err := p.Charts().Add(charts...); err != nil {
  1269  		p.Warning(err)
  1270  	}
  1271  }
  1272  
  1273  func (p *Postgres) addTableTOASTIOCharts(tbl *tableMetrics) {
  1274  	charts := module.Charts{
  1275  		newTableChart(tableTOASCacheIORatioChartTmpl.Copy(), tbl),
  1276  		newTableChart(tableTOASTIORateChartTmpl.Copy(), tbl),
  1277  	}
  1278  
  1279  	if err := p.Charts().Add(charts...); err != nil {
  1280  		p.Warning(err)
  1281  	}
  1282  }
  1283  
  1284  func (p *Postgres) addTableTOASTIndexIOCharts(tbl *tableMetrics) {
  1285  	charts := module.Charts{
  1286  		newTableChart(tableTOASTIndexCacheIORatioChartTmpl.Copy(), tbl),
  1287  		newTableChart(tableTOASTIndexIORateChartTmpl.Copy(), tbl),
  1288  	}
  1289  
  1290  	if err := p.Charts().Add(charts...); err != nil {
  1291  		p.Warning(err)
  1292  	}
  1293  }
  1294  
  1295  func (p *Postgres) removeTableCharts(tbl *tableMetrics) {
  1296  	prefix := fmt.Sprintf("table_%s_db_%s_schema_%s", tbl.name, tbl.db, tbl.schema)
  1297  	for _, c := range *p.Charts() {
  1298  		if strings.HasPrefix(c.ID, prefix) {
  1299  			c.MarkRemove()
  1300  			c.MarkNotCreated()
  1301  		}
  1302  	}
  1303  }
  1304  
  1305  var (
  1306  	indexChartsTmpl = module.Charts{
  1307  		indexSizeChartTmpl.Copy(),
  1308  		indexBloatSizePercChartTmpl.Copy(),
  1309  		indexBloatSizeChartTmpl.Copy(),
  1310  		indexUsageStatusChartTmpl.Copy(),
  1311  	}
  1312  	indexSizeChartTmpl = module.Chart{
  1313  		ID:       "index_%s_table_%s_db_%s_schema_%s_size",
  1314  		Title:    "Index size",
  1315  		Units:    "B",
  1316  		Fam:      "size",
  1317  		Ctx:      "postgres.index_size",
  1318  		Priority: prioIndexSize,
  1319  		Dims: module.Dims{
  1320  			{ID: "index_%s_table_%s_db_%s_schema_%s_size", Name: "size"},
  1321  		},
  1322  	}
  1323  	indexBloatSizePercChartTmpl = module.Chart{
  1324  		ID:       "index_%s_table_%s_db_%s_schema_%s_bloat_size_perc",
  1325  		Title:    "Index bloat size percentage",
  1326  		Units:    "percentage",
  1327  		Fam:      "bloat",
  1328  		Ctx:      "postgres.index_bloat_size_perc",
  1329  		Priority: prioIndexBloatSizePerc,
  1330  		Dims: module.Dims{
  1331  			{ID: "index_%s_table_%s_db_%s_schema_%s_bloat_size_perc", Name: "bloat"},
  1332  		},
  1333  		Vars: module.Vars{
  1334  			{ID: "index_%s_table_%s_db_%s_schema_%s_size", Name: "index_size"},
  1335  		},
  1336  	}
  1337  	indexBloatSizeChartTmpl = module.Chart{
  1338  		ID:       "index_%s_table_%s_db_%s_schema_%s_bloat_size",
  1339  		Title:    "Index bloat size",
  1340  		Units:    "B",
  1341  		Fam:      "bloat",
  1342  		Ctx:      "postgres.index_bloat_size",
  1343  		Priority: prioIndexBloatSize,
  1344  		Dims: module.Dims{
  1345  			{ID: "index_%s_table_%s_db_%s_schema_%s_bloat_size", Name: "bloat"},
  1346  		},
  1347  	}
  1348  	indexUsageStatusChartTmpl = module.Chart{
  1349  		ID:       "index_%s_table_%s_db_%s_schema_%s_usage_status",
  1350  		Title:    "Index usage status",
  1351  		Units:    "status",
  1352  		Fam:      "maintenance",
  1353  		Ctx:      "postgres.index_usage_status",
  1354  		Priority: prioIndexUsageStatus,
  1355  		Dims: module.Dims{
  1356  			{ID: "index_%s_table_%s_db_%s_schema_%s_usage_status_used", Name: "used"},
  1357  			{ID: "index_%s_table_%s_db_%s_schema_%s_usage_status_unused", Name: "unused"},
  1358  		},
  1359  	}
  1360  )
  1361  
  1362  func (p *Postgres) addNewIndexCharts(idx *indexMetrics) {
  1363  	charts := indexChartsTmpl.Copy()
  1364  
  1365  	if idx.bloatSize == nil {
  1366  		_ = charts.Remove(indexBloatSizeChartTmpl.ID)
  1367  		_ = charts.Remove(indexBloatSizePercChartTmpl.ID)
  1368  	}
  1369  
  1370  	for _, chart := range *charts {
  1371  		chart.ID = fmt.Sprintf(chart.ID, idx.name, idx.table, idx.db, idx.schema)
  1372  		chart.Labels = []module.Label{
  1373  			{Key: "database", Value: idx.db},
  1374  			{Key: "schema", Value: idx.schema},
  1375  			{Key: "table", Value: idx.table},
  1376  			{Key: "parent_table", Value: idx.parentTable},
  1377  			{Key: "index", Value: idx.name},
  1378  		}
  1379  		for _, d := range chart.Dims {
  1380  			d.ID = fmt.Sprintf(d.ID, idx.name, idx.table, idx.db, idx.schema)
  1381  		}
  1382  		for _, v := range chart.Vars {
  1383  			v.ID = fmt.Sprintf(v.ID, idx.name, idx.table, idx.db, idx.schema)
  1384  		}
  1385  	}
  1386  
  1387  	if err := p.Charts().Add(*charts...); err != nil {
  1388  		p.Warning(err)
  1389  	}
  1390  }
  1391  
  1392  func (p *Postgres) removeIndexCharts(idx *indexMetrics) {
  1393  	prefix := fmt.Sprintf("index_%s_table_%s_db_%s_schema_%s", idx.name, idx.table, idx.db, idx.schema)
  1394  	for _, c := range *p.Charts() {
  1395  		if strings.HasPrefix(c.ID, prefix) {
  1396  			c.MarkRemove()
  1397  			c.MarkNotCreated()
  1398  		}
  1399  	}
  1400  }