-
Notifications
You must be signed in to change notification settings - Fork 211
/
prometheus.go
156 lines (142 loc) · 4.25 KB
/
prometheus.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package metrics
import (
"context"
"fmt"
"time"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/sync/errgroup"
"github.com/spacemeshos/go-spacemesh/log"
"github.com/spacemeshos/go-spacemesh/metrics"
"github.com/spacemeshos/go-spacemesh/sql"
)
const (
enabledDBStat = "ENABLE_DBSTAT_VTAB"
subsystem = "database" // subsystem shared by all metrics exposed by this package.
)
// DBMetricsCollector collects metrics from db.
type DBMetricsCollector struct {
logger log.Logger
checkInterval time.Duration
db *sql.Database
tablesList map[string]struct{}
eg errgroup.Group
cancel context.CancelFunc
tableSize *prometheus.GaugeVec
indexSize *prometheus.GaugeVec
totalSize *prometheus.GaugeVec
}
// NewDBMetricsCollector creates new DBMetricsCollector.
func NewDBMetricsCollector(
ctx context.Context,
db *sql.Database,
logger log.Logger,
checkInterval time.Duration,
) *DBMetricsCollector {
ctx, cancel := context.WithCancel(ctx)
collector := &DBMetricsCollector{
checkInterval: checkInterval,
logger: logger.WithName("db_metrics"),
db: db,
cancel: cancel,
tableSize: metrics.NewGauge("table_size", subsystem, "Size of table in bytes", []string{"name"}),
indexSize: metrics.NewGauge("index_size", subsystem, "Size of index in bytes", []string{"name"}),
totalSize: metrics.NewGauge("total_size", subsystem, "Total size of db in bytes", nil),
}
statEnabled, err := collector.checkCompiledWithDBStat()
if err != nil {
collector.logger.With().Error("error check compile options", log.Err(err))
return nil
}
if !statEnabled {
collector.logger.With().Info("sqlite compiled without `SQLITE_ENABLE_DBSTAT_VTAB`. Metrics will not collected")
return nil
}
collector.tablesList, err = collector.getListOfTables()
if err != nil {
collector.logger.With().Error("error get list of tables", log.Err(err))
return nil
}
collector.logger.With().Info("start collect stat")
collector.eg.Go(func() error {
collector.CollectMetrics(ctx)
return nil
})
return collector
}
// Close closes DBMetricsCollector.
func (d *DBMetricsCollector) Close() {
d.cancel()
if err := d.eg.Wait(); err != nil {
d.logger.With().Error("received error waiting for db metrics collector", log.Err(err))
}
}
// CollectMetrics collects metrics from db.
func (d *DBMetricsCollector) CollectMetrics(ctx context.Context) {
ticker := time.NewTicker(d.checkInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
d.logger.Debug("collect stats from db")
if err := d.collect(); err != nil {
d.logger.With().Error("error check db metrics", log.Err(err))
}
case <-ctx.Done():
return
}
}
}
func (d *DBMetricsCollector) collect() error {
sizes := make(map[string]int64, 30)
_, err := d.db.Exec(
"SELECT name, sum(pgsize) as sum FROM dbstat GROUP BY name",
nil,
func(stmt *sql.Statement) bool {
sizes[stmt.ColumnText(0)] = stmt.ColumnInt64(1)
return true
},
)
if err != nil {
return fmt.Errorf("error execute stat metrics: %w", err)
}
var totalSize int64
for name, size := range sizes {
totalSize += size
_, ok := d.tablesList[name]
if ok {
d.tableSize.WithLabelValues(name).Set(float64(size))
continue
}
d.indexSize.WithLabelValues(name).Set(float64(size))
}
d.totalSize.WithLabelValues().Set(float64(totalSize))
return nil
}
func (d *DBMetricsCollector) checkCompiledWithDBStat() (bool, error) {
var options []string
_, err := d.db.Exec("PRAGMA compile_options", nil, func(stmt *sql.Statement) bool {
options = append(options, stmt.ColumnText(0))
return true
})
if err != nil {
return false, fmt.Errorf("error check db compiler options: %w", err)
}
for _, option := range options {
if option == enabledDBStat {
return true, nil
}
}
return false, nil
}
// getListOfTables returns list of tables in db. need to separate size indexes and tables.
func (d *DBMetricsCollector) getListOfTables() (map[string]struct{}, error) {
tables := make(map[string]struct{})
_, err := d.db.Exec("SELECT name FROM sqlite_master WHERE type='table'", nil, func(stmt *sql.Statement) bool {
tables[stmt.ColumnText(0)] = struct{}{}
return true
})
if err != nil {
return nil, fmt.Errorf("error get list of tables: %w", err)
}
return tables, nil
}