Skip to content

Commit 14fac96

Browse files
Concurrency for multi-database (#326)
Concurrency for multi-database Signed-off-by: Anders Swanson <anders.swanson@oracle.com>
1 parent ff4de3e commit 14fac96

File tree

5 files changed

+93
-50
lines changed

5 files changed

+93
-50
lines changed

collector/cache.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) 2025, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
4+
package collector
5+
6+
import (
7+
"github.com/prometheus/client_golang/prometheus"
8+
"time"
9+
)
10+
11+
func NewMetricsCache(metrics []*Metric) *MetricsCache {
12+
c := map[*Metric]*MetricCacheRecord{}
13+
14+
for _, metric := range metrics {
15+
c[metric] = &MetricCacheRecord{
16+
PrometheusMetrics: map[string]prometheus.Metric{},
17+
LastScraped: nil,
18+
}
19+
}
20+
return &MetricsCache{
21+
cache: c,
22+
}
23+
}
24+
25+
func (c *MetricsCache) SetLastScraped(m *Metric, tick *time.Time) {
26+
c.cache[m].LastScraped = tick
27+
}
28+
29+
func (c *MetricsCache) GetLastScraped(m *Metric) *time.Time {
30+
return c.cache[m].LastScraped
31+
}
32+
33+
func (c *MetricsCache) SendAll(ch chan<- prometheus.Metric, m *Metric) {
34+
for _, pm := range c.cache[m].PrometheusMetrics {
35+
ch <- pm
36+
}
37+
}
38+
39+
func (c *MetricsCache) CacheAndSend(ch chan<- prometheus.Metric, m *Metric, metric prometheus.Metric) {
40+
c.cache[m].PrometheusMetrics[metric.Desc().String()] = metric
41+
ch <- metric
42+
}

collector/collector.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,10 @@ func NewExporter(logger *slog.Logger, m *MetricsConfiguration) *Exporter {
104104
logger: logger,
105105
MetricsConfiguration: m,
106106
databases: databases,
107-
lastScraped: map[string]*time.Time{},
108107
allConstLabels: allConstLabels,
109108
}
110109
e.metricsToScrape = e.DefaultMetrics()
111-
110+
e.initCache()
112111
return e
113112
}
114113

@@ -253,7 +252,7 @@ func (e *Exporter) scrapeDatabase(ch chan<- prometheus.Metric, errChan chan<- er
253252
go func() {
254253
// If the metric doesn't need to be scraped, send the cached values
255254
if !isScrapeMetric {
256-
metric.sendAll(ch)
255+
d.MetricsCache.SendAll(ch, metric)
257256
errChan <- nil
258257
return
259258
}
@@ -416,6 +415,7 @@ func (e *Exporter) reloadMetrics() {
416415
} else {
417416
e.logger.Debug("No custom metrics defined.")
418417
}
418+
e.initCache()
419419
}
420420

421421
// ScrapeMetric is an interface method to call scrapeGenericValues using Metric struct values
@@ -475,9 +475,9 @@ func (e *Exporter) scrapeGenericValues(d *Database, ch chan<- prometheus.Metric,
475475
}
476476
buckets[lelimit] = counter
477477
}
478-
m.cacheAndSend(ch, prometheus.MustNewConstHistogram(desc, count, value, buckets, labelsValues...))
478+
d.MetricsCache.CacheAndSend(ch, m, prometheus.MustNewConstHistogram(desc, count, value, buckets, labelsValues...))
479479
} else {
480-
m.cacheAndSend(ch, prometheus.MustNewConstMetric(desc, getMetricType(metric, m.MetricsType), value, labelsValues...))
480+
d.MetricsCache.CacheAndSend(ch, m, prometheus.MustNewConstMetric(desc, getMetricType(metric, m.MetricsType), value, labelsValues...))
481481
}
482482
// If no labels, use metric name
483483
} else {
@@ -509,9 +509,9 @@ func (e *Exporter) scrapeGenericValues(d *Database, ch chan<- prometheus.Metric,
509509
}
510510
buckets[lelimit] = counter
511511
}
512-
m.cacheAndSend(ch, prometheus.MustNewConstHistogram(desc, count, value, buckets))
512+
d.MetricsCache.CacheAndSend(ch, m, prometheus.MustNewConstHistogram(desc, count, value, buckets))
513513
} else {
514-
m.cacheAndSend(ch, prometheus.MustNewConstMetric(desc, getMetricType(metric, m.MetricsType), value))
514+
d.MetricsCache.CacheAndSend(ch, m, prometheus.MustNewConstMetric(desc, getMetricType(metric, m.MetricsType), value))
515515
}
516516
}
517517
metricsCount++
@@ -578,6 +578,12 @@ func (e *Exporter) generatePrometheusMetrics(d *Database, parse func(row map[str
578578
return nil
579579
}
580580

581+
func (e *Exporter) initCache() {
582+
for _, d := range e.databases {
583+
d.initCache(e.metricsToScrape.Metric)
584+
}
585+
}
586+
581587
func getMetricType(metricType string, metricsType map[string]string) prometheus.ValueType {
582588
var strToPromType = map[string]prometheus.ValueType{
583589
"gauge": prometheus.GaugeValue,

collector/database.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ func NewDatabase(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) *D
8686
}
8787
}
8888

89+
// initCache resets the metrics cached. Used on startup and when metrics are reloaded.
90+
func (d *Database) initCache(metrics []*Metric) {
91+
d.MetricsCache = NewMetricsCache(metrics)
92+
}
93+
8994
// WarmupConnectionPool serially acquires connections to "warm up" the connection pool.
9095
// This is a workaround for a perceived bug in ODPI_C where rapid acquisition of connections
9196
// results in a SIGABRT.

collector/metrics.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@ func (e *Exporter) isScrapeMetric(tick *time.Time, metric *Metric, d *Database)
2828
if !ok {
2929
return true
3030
}
31-
id := metric.id(d.Name)
32-
lastScraped := e.lastScraped[id]
31+
lastScraped := d.MetricsCache.GetLastScraped(metric)
3332
shouldScrape := lastScraped == nil ||
3433
// If the metric's scrape interval is less than the time elapsed since the last scrape,
3534
// we should scrape the metric.
3635
interval < tick.Sub(*lastScraped)
3736
if shouldScrape {
38-
e.lastScraped[id] = tick
37+
d.MetricsCache.SetLastScraped(metric, lastScraped)
3938
}
4039
return shouldScrape
4140
}

collector/types.go

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/godror/godror/dsn"
88
"github.com/prometheus/client_golang/prometheus"
99
"log/slog"
10-
"strings"
1110
"sync"
1211
"time"
1312
)
@@ -23,7 +22,6 @@ type Exporter struct {
2322
scrapeResults []prometheus.Metric
2423
databases []*Database
2524
logger *slog.Logger
26-
lastScraped map[string]*time.Time
2725
allConstLabels []string
2826
}
2927

@@ -33,6 +31,26 @@ type Database struct {
3331
Session *sql.DB
3432
Type float64
3533
Config DatabaseConfig
34+
// MetricsCache holds computed metrics for a database, so these metrics are available on each scrape.
35+
// Given a metric's scrape configuration, it may not be computed on the same interval as other metrics.
36+
MetricsCache *MetricsCache
37+
}
38+
39+
type MetricsCache struct {
40+
// The outer map is to be initialized at startup, and when metrics are reloaded.
41+
// Read access is concurrent, write access is (and must) be from a single thread.
42+
cache map[*Metric]*MetricCacheRecord
43+
}
44+
45+
// MetricCacheRecord stores metadata associated with a given Metric
46+
// As one metric may have multiple prometheus.Metric representations,
47+
// These are cached as a map value.
48+
type MetricCacheRecord struct {
49+
// PrometheusMetrics stores cached prometheus metric values.
50+
// Used when custom scrape intervals are used, and the metric must be returned to the collector, but not scraped.
51+
PrometheusMetrics map[string]prometheus.Metric
52+
// LastScraped is the collector tick time when the metric was last computed.
53+
LastScraped *time.Time
3654
}
3755

3856
type Config struct {
@@ -57,18 +75,17 @@ type Config struct {
5775

5876
// Metric is an object description
5977
type Metric struct {
60-
Context string
61-
Labels []string
62-
MetricsDesc map[string]string
63-
MetricsType map[string]string
64-
MetricsBuckets map[string]map[string]string
65-
FieldToAppend string
66-
Request string
67-
IgnoreZeroResult bool
68-
QueryTimeout string
69-
ScrapeInterval string
70-
Databases []string
71-
PrometheusMetrics map[string]prometheus.Metric
78+
Context string
79+
Labels []string
80+
MetricsDesc map[string]string
81+
MetricsType map[string]string
82+
MetricsBuckets map[string]map[string]string
83+
FieldToAppend string
84+
Request string
85+
IgnoreZeroResult bool
86+
QueryTimeout string
87+
ScrapeInterval string
88+
Databases []string
7289
}
7390

7491
// Metrics is a container structure for prometheus metrics
@@ -78,29 +95,3 @@ type Metrics struct {
7895

7996
type ScrapeContext struct {
8097
}
81-
82-
func (m *Metric) id(dbname string) string {
83-
builder := strings.Builder{}
84-
builder.WriteString(dbname)
85-
builder.WriteString(m.Context)
86-
for _, d := range m.MetricsDesc {
87-
builder.WriteString(d)
88-
}
89-
return builder.String()
90-
}
91-
92-
// sendAll sends all cached metrics to the collector.
93-
func (m *Metric) sendAll(ch chan<- prometheus.Metric) {
94-
for _, metric := range m.PrometheusMetrics {
95-
ch <- metric
96-
}
97-
}
98-
99-
// cacheAndSend caches the metric and sends it to the collector.
100-
func (m *Metric) cacheAndSend(ch chan<- prometheus.Metric, metric prometheus.Metric) {
101-
if len(m.PrometheusMetrics) == 0 {
102-
m.PrometheusMetrics = map[string]prometheus.Metric{}
103-
}
104-
m.PrometheusMetrics[metric.Desc().String()] = metric
105-
ch <- metric
106-
}

0 commit comments

Comments
 (0)