Skip to content

Commit e5532e8

Browse files
maksim.konovalovKaymeKaydex
maksim.konovalov
authored andcommitted
Implemented DefaultPrometheusProvider & add DecodeDuration metric & add fnc name to CallDuration
1 parent bdd3ef5 commit e5532e8

11 files changed

+259
-10
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
## Unreleased
22

3+
The go-vshard team apologizes for changing the interfaces to experimental status.
4+
This is due to an inherently bad approach to controversial interfaces.
5+
36
BUG FIXES:
47
* Router.Call bugfix: set destinationName := rs.info.Name if destination exists.
58
* Router.Route bugfix: handle outdated *Replicaset object (resolve issue #11).
69

710
FEATURES:
811
* Now when calling RemoveInstance, if an empty replicaset name is passed, the replicaset will be calculated automatically.
12+
* Implemented PrometheusProvider that can be usefully for default prometheus metrics.
913

1014
CHANGES:
1115
* Bump go-tarantool from v2.2.1 to v2.3.0.
1216
* Make comments more go-style.
1317
* Router.cronDiscovery: log panic in another goroutine.
1418
* Tiny optimization in SlogLoggerf.
19+
* The MetricsProvider interface has been made experimental. This means that during the release we may change its implementation. If you do not want to lose backward compatibility, use empty or the standard prometheus implementation.
20+
* RequestDuration metric interface requires procedure name.
1521

1622
TESTS:
1723
* Fixed etcd overlapping ports.

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,14 @@ func main() {
172172
}
173173
```
174174
### Providers
175+
<img align="center" src="docs/static/providers.png" alt="topology">
176+
177+
We understand that the implementations of loggers, metrics,
178+
and topology sources can vary significantly in both implementation and usage.
179+
Therefore, go-vshard-router gives you the flexibility to choose the tools you deem appropriate
180+
or implement them yourself by using interfaces.
181+
182+
#### Topology
175183
You can use topology (configuration) providers as the source of router configuration.
176184
Currently, the following providers are supported:
177185

@@ -182,6 +190,13 @@ Currently, the following providers are supported:
182190
- consul
183191
- files
184192

193+
#### Metrics
194+
Metrics providers are also available,
195+
you can use ready-made metrics providers that can be registered in prometheus and passed to go-vshard-router.
196+
This will allow you not to think about options and metrics settings.
197+
The following providers are currently available:
198+
- [prometheus](providers/prometheus)
199+
185200
### Learn more examples
186201
#### Quick Start
187202
Learn with th [Quick Start](docs/doc.md), which include examples and theory.

README_ru.md

+16
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ func main() {
180180

181181
### Провайдеры
182182

183+
<img align="center" src="docs/static/providers.png" alt="topology">
184+
185+
Мы понимаем, что реализации логгеров, метрик,
186+
а так же источников топологии могут сильно отличаться в реализации и использовании.
187+
Поэтому go-vshard-router дает возможность вам самим выбирать инструменты,
188+
которые вы считаете правильным использовать или реализовать их самим за счет использования интерфейсов.
189+
190+
#### Топология
183191
Как источник конфигурации вы можете использовать провайдеры топологии(конфигурации).
184192
На данный момент есть поддержка следующих провайдеров:
185193

@@ -192,6 +200,14 @@ func main() {
192200
- consul
193201
- files
194202

203+
#### Метрики
204+
205+
Также доступны провайдеры метрик, вы можете использовать готовые провайдеры метрик,
206+
которые можно зарегестрировать в prometheus и передать go-vshard-router.
207+
Что позволит вам не задумываться над опциями и настройкой метрик.
208+
На данный момент доступны следующие провайдеры:
209+
- [prometheus](providers/prometheus)
210+
195211
### Ознакомьтесь с другими примерами
196212

197213
#### Быстрое начало

api.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ func (r *Router) Call(ctx context.Context, bucketID uint64, mode CallMode,
264264

265265
for {
266266
if spent := time.Since(requestStartTime); spent > timeout {
267-
r.metrics().RequestDuration(spent, false, false)
267+
r.metrics().RequestDuration(spent, fnc, false, false)
268268

269269
r.log().Debugf(ctx, "Return result on timeout; spent %s of timeout %s", spent, timeout)
270270
if err == nil {
@@ -291,7 +291,8 @@ func (r *Router) Call(ctx context.Context, bucketID uint64, mode CallMode,
291291

292292
r.log().Infof(ctx, "Try call %s on replicaset %s for bucket %d", fnc, rs.info.Name, bucketID)
293293

294-
var storageCallResponse vshardStorageCallResponseProto
294+
storageCallResponse := vshardStorageCallResponseProto{}
295+
295296
err = rs.conn.Do(tntReq, poolMode).GetTyped(&storageCallResponse)
296297
if err != nil {
297298
return VshardRouterCallResp{}, fmt.Errorf("got error on future.GetTyped(): %w", err)
@@ -386,7 +387,7 @@ func (r *Router) Call(ctx context.Context, bucketID uint64, mode CallMode,
386387
}
387388
}
388389

389-
r.metrics().RequestDuration(time.Since(requestStartTime), true, false)
390+
r.metrics().RequestDuration(time.Since(requestStartTime), fnc, true, false)
390391

391392
return storageCallResponse.CallResp, nil
392393
}
@@ -646,7 +647,7 @@ func RouterMapCallRW[T any](r *Router, ctx context.Context,
646647
// map stage: get their responses
647648
nameToResult := make(map[string]T)
648649
for _, rsFuture := range rsFutures {
649-
var storageMapResponse storageMapResponseProto[T]
650+
storageMapResponse := storageMapResponseProto[T]{}
650651

651652
err := rsFuture.future.GetTyped(&storageMapResponse)
652653
if err != nil {
@@ -660,7 +661,7 @@ func RouterMapCallRW[T any](r *Router, ctx context.Context,
660661
nameToResult[rsFuture.name] = storageMapResponse.value
661662
}
662663

663-
r.metrics().RequestDuration(time.Since(timeStart), true, true)
664+
r.metrics().RequestDuration(time.Since(timeStart), fnc, true, true)
664665

665666
return nameToResult, nil
666667
}

docs/static/providers.png

74.1 KB
Loading

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.22
44

55
require (
66
github.com/google/uuid v1.6.0
7+
github.com/prometheus/client_golang v1.11.1
78
github.com/snksoft/crc v1.1.0
89
github.com/spf13/viper v1.19.0
910
github.com/stretchr/testify v1.10.0
@@ -74,7 +75,6 @@ require (
7475
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
7576
github.com/pkg/errors v0.9.1 // indirect
7677
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
77-
github.com/prometheus/client_golang v1.11.1 // indirect
7878
github.com/prometheus/client_model v0.2.0 // indirect
7979
github.com/prometheus/common v0.26.0 // indirect
8080
github.com/prometheus/procfs v0.6.0 // indirect

providers.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,12 @@ func (s SlogLoggerf) Errorf(ctx context.Context, format string, v ...any) {
133133

134134
// Metrics
135135

136-
// MetricsProvider is an interface for passing library metrics to your prometheus/graphite and other metrics
136+
// MetricsProvider is an interface for passing library metrics to your prometheus/graphite and other metrics.
137+
// This logic is experimental and may be changed in the release.
137138
type MetricsProvider interface {
138139
CronDiscoveryEvent(ok bool, duration time.Duration, reason string)
139140
RetryOnCall(reason string)
140-
RequestDuration(duration time.Duration, ok bool, mapReduce bool)
141+
RequestDuration(duration time.Duration, procedure string, ok, mapReduce bool)
141142
}
142143

143144
// EmptyMetrics is default empty metrics provider
@@ -146,7 +147,7 @@ type EmptyMetrics struct{}
146147

147148
func (e *EmptyMetrics) CronDiscoveryEvent(_ bool, _ time.Duration, _ string) {}
148149
func (e *EmptyMetrics) RetryOnCall(_ string) {}
149-
func (e *EmptyMetrics) RequestDuration(_ time.Duration, _ bool, _ bool) {}
150+
func (e *EmptyMetrics) RequestDuration(_ time.Duration, _ string, _, _ bool) {}
150151

151152
// TopologyProvider is external module that can lookup current topology of cluster
152153
// it might be etcd/config/consul or smth else

providers/prometheus/prometheus.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package prometheus
2+
3+
import (
4+
"strconv"
5+
"time"
6+
7+
"github.com/prometheus/client_golang/prometheus"
8+
vshardrouter "github.com/tarantool/go-vshard-router/v2"
9+
)
10+
11+
// Check that provider implements MetricsProvider interface
12+
var _ vshardrouter.MetricsProvider = (*Provider)(nil)
13+
14+
// Check that provider implements Collector interface
15+
var _ prometheus.Collector = (*Provider)(nil)
16+
17+
// Provider is a struct that implements collector and provider methods.
18+
// It gives users a simple way to use metrics for go-vshard-router.
19+
type Provider struct {
20+
// cronDiscoveryEvent - histogram for cron discovery events.
21+
cronDiscoveryEvent *prometheus.HistogramVec
22+
// retryOnCall - counter for retry calls.
23+
retryOnCall *prometheus.CounterVec
24+
// requestDuration - histogram for map reduce and single request durations.
25+
requestDuration *prometheus.HistogramVec
26+
}
27+
28+
// Describe sends the descriptors of each metric to the provided channel.
29+
func (pp *Provider) Describe(ch chan<- *prometheus.Desc) {
30+
pp.cronDiscoveryEvent.Describe(ch)
31+
pp.retryOnCall.Describe(ch)
32+
pp.requestDuration.Describe(ch)
33+
}
34+
35+
// Collect gathers the metrics and sends them to the provided channel.
36+
func (pp *Provider) Collect(ch chan<- prometheus.Metric) {
37+
pp.cronDiscoveryEvent.Collect(ch)
38+
pp.retryOnCall.Collect(ch)
39+
pp.requestDuration.Collect(ch)
40+
}
41+
42+
// CronDiscoveryEvent records the duration of a cron discovery event with labels.
43+
func (pp *Provider) CronDiscoveryEvent(ok bool, duration time.Duration, reason string) {
44+
pp.cronDiscoveryEvent.With(prometheus.Labels{
45+
"ok": strconv.FormatBool(ok),
46+
"reason": reason,
47+
}).Observe(float64(duration.Milliseconds()))
48+
}
49+
50+
// RetryOnCall increments the retry counter for a specific reason.
51+
func (pp *Provider) RetryOnCall(reason string) {
52+
pp.retryOnCall.With(prometheus.Labels{
53+
"reason": reason,
54+
}).Inc()
55+
}
56+
57+
// RequestDuration records the duration of a request with labels for success and map-reduce usage.
58+
func (pp *Provider) RequestDuration(duration time.Duration, procedure string, ok, mapReduce bool) {
59+
pp.requestDuration.With(prometheus.Labels{
60+
"ok": strconv.FormatBool(ok),
61+
"map_reduce": strconv.FormatBool(mapReduce),
62+
"procedure": procedure,
63+
}).Observe(float64(duration.Milliseconds()))
64+
}
65+
66+
// NewPrometheusProvider - is an experimental function.
67+
// Prometheus Provider is one of the ready-to-use providers implemented
68+
// for go-vshard-router. It can be used to easily integrate metrics into
69+
// your service without worrying about buckets, metric names, or other
70+
// metric options.
71+
//
72+
// The provider implements both the interface required by vshard-router
73+
// and the Prometheus collector interface.
74+
//
75+
// To register it in your Prometheus instance, use:
76+
// registry.MustRegister(provider)
77+
//
78+
// Then, pass it to go-vshard-router so that it can manage the metrics:
79+
//
80+
// vshard_router.NewRouter(ctx, vshard_router.Config{
81+
// Metrics: provider,
82+
// })
83+
//
84+
// This approach simplifies the process of collecting and handling metrics,
85+
// freeing you from manually managing metric-specific configurations.
86+
func NewPrometheusProvider() *Provider {
87+
return &Provider{
88+
cronDiscoveryEvent: prometheus.NewHistogramVec(prometheus.HistogramOpts{
89+
Name: "cron_discovery_event",
90+
Namespace: "vshard",
91+
}, []string{"ok", "reason"}), // Histogram for tracking cron discovery events
92+
93+
retryOnCall: prometheus.NewCounterVec(prometheus.CounterOpts{
94+
Name: "retry_on_call",
95+
Namespace: "vshard",
96+
}, []string{"reason"}), // Counter for retry attempts
97+
98+
requestDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
99+
Name: "request_duration",
100+
Namespace: "vshard",
101+
}, []string{"procedure", "ok", "map_reduce"}), // Histogram for request durations
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package prometheus
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"net/http/httptest"
8+
"strings"
9+
"time"
10+
11+
"github.com/prometheus/client_golang/prometheus"
12+
"github.com/prometheus/client_golang/prometheus/promhttp"
13+
)
14+
15+
func ExampleNewPrometheusProvider() {
16+
// Let's create new prometheus provider.
17+
provider := NewPrometheusProvider()
18+
19+
// Create new prometheus registry.
20+
registry := prometheus.NewRegistry()
21+
// Register prometheus provider.
22+
registry.MustRegister(provider)
23+
24+
// Create example http server.
25+
server := httptest.NewServer(promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
26+
defer server.Close()
27+
28+
// Then we can register our provider in go vshard router.
29+
// It will use our provider to write prometheus metrics.
30+
/*
31+
vshard_router.NewRouter(ctx, vshard_router.Config{
32+
Metrics: provider,
33+
})
34+
*/
35+
36+
provider.CronDiscoveryEvent(true, 150*time.Millisecond, "success")
37+
provider.RetryOnCall("timeout")
38+
provider.RequestDuration(200*time.Millisecond, "test", true, false)
39+
40+
resp, err := http.Get(server.URL + "/metrics")
41+
if err != nil {
42+
panic(err)
43+
}
44+
45+
defer resp.Body.Close()
46+
47+
body, err := io.ReadAll(resp.Body)
48+
if err != nil {
49+
panic(err)
50+
}
51+
52+
metricsOutput := string(body)
53+
54+
if strings.Contains(metricsOutput, "vshard_request_duration_bucket") {
55+
fmt.Println("Metrics output contains vshard_request_duration_bucket")
56+
}
57+
if strings.Contains(metricsOutput, "vshard_cron_discovery_event_bucket") {
58+
fmt.Println("Metrics output contains vshard_cron_discovery_event_bucket")
59+
}
60+
61+
if strings.Contains(metricsOutput, "vshard_retry_on_call") {
62+
fmt.Println("Metrics output contains vshard_retry_on_call")
63+
}
64+
// Output: Metrics output contains vshard_request_duration_bucket
65+
// Metrics output contains vshard_cron_discovery_event_bucket
66+
// Metrics output contains vshard_retry_on_call
67+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package prometheus
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
"time"
9+
10+
"github.com/prometheus/client_golang/prometheus"
11+
"github.com/prometheus/client_golang/prometheus/promhttp"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestPrometheusMetricsServer(t *testing.T) {
16+
provider := NewPrometheusProvider()
17+
18+
registry := prometheus.NewRegistry()
19+
registry.MustRegister(provider)
20+
21+
server := httptest.NewServer(promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
22+
defer server.Close()
23+
24+
provider.CronDiscoveryEvent(true, 150*time.Millisecond, "success")
25+
provider.RetryOnCall("timeout")
26+
provider.RequestDuration(200*time.Millisecond, "test", true, false)
27+
28+
resp, err := http.Get(server.URL + "/metrics")
29+
require.NoError(t, err)
30+
31+
defer resp.Body.Close()
32+
33+
body, err := io.ReadAll(resp.Body)
34+
require.NoError(t, err)
35+
metricsOutput := string(body)
36+
37+
require.Contains(t, metricsOutput, "vshard_request_duration_bucket")
38+
require.Contains(t, metricsOutput, "vshard_cron_discovery_event_bucket")
39+
require.Contains(t, metricsOutput, "vshard_retry_on_call")
40+
}

providers_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestEmptyMetrics_RetryOnCall(t *testing.T) {
2525

2626
func TestEmptyMetrics_RequestDuration(t *testing.T) {
2727
require.NotPanics(t, func() {
28-
emptyMetrics.RequestDuration(time.Second, false, false)
28+
emptyMetrics.RequestDuration(time.Second, "test", false, false)
2929
})
3030
}
3131

0 commit comments

Comments
 (0)