Skip to content

Commit b79df14

Browse files
authored
Tracing: Support remote, rate-limited, and probabilistic sampling (#781)
* Initial work for remote, rate-limited, and probabilistic sampling * go mod tidy * sampling -> sampler * Lint * Fix wrong value for SamplerTypeRemote * Unexport tracingConfig attributes * go mod tidy * Read service name from build info * Add build.InfoGetter, get service name from build info * Fix buildinfo error not being printed * Add tests * Add TestNewOtelSampler * refactoring * Use different log params for sampler type, param and remote * Use different log params for sample remote url and service name * better error handling for remote sampler service name * Add comments for tracing sampling env var constants * PR review feedback: use log.Fatalln * Unexport tracingConfig.isEnabled * PR review feedback: Log errors * PR review feedback * PR review feedback * go mod tidy
1 parent 515cdb4 commit b79df14

File tree

11 files changed

+422
-44
lines changed

11 files changed

+422
-44
lines changed

backend/app/manage.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package app
22

33
import (
44
"fmt"
5+
"log"
56
"os"
67

78
"github.com/grafana/grafana-plugin-sdk-go/backend"
@@ -25,7 +26,7 @@ func Manage(pluginID string, instanceFactory InstanceFactoryFunc, opts ManageOpt
2526
// If we are running in build info mode, run that and exit
2627
if buildinfo.InfoModeEnabled() {
2728
if err := buildinfo.RunInfoMode(); err != nil {
28-
os.Exit(1)
29+
log.Fatalln(err)
2930
return err
3031
}
3132
os.Exit(0)

backend/datasource/manage.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package datasource
22

33
import (
44
"fmt"
5+
"log"
56
"os"
67

78
"github.com/grafana/grafana-plugin-sdk-go/backend"
@@ -25,7 +26,7 @@ func Manage(pluginID string, instanceFactory InstanceFactoryFunc, opts ManageOpt
2526
// If we are running in build info mode, run that and exit
2627
if buildinfo.InfoModeEnabled() {
2728
if err := buildinfo.RunInfoMode(); err != nil {
28-
os.Exit(1)
29+
log.Fatalln(err)
2930
return err
3031
}
3132
os.Exit(0)

backend/serve.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func Serve(opts ServeOpts) error {
110110
}
111111

112112
// GracefulStandaloneServe starts a gRPC server that is not managed by hashicorp.
113-
// The provided standalone.Args must have an Address set, or the function returns an error.
113+
// The provided standalone.Args must have an address set, or the function returns an error.
114114
// The function handles creating/cleaning up the standalone address file, and graceful GRPC server termination.
115115
// The function returns after the GRPC server has been terminated.
116116
func GracefulStandaloneServe(dsopts ServeOpts, info standalone.ServerSettings) error {

backend/setup.go

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ import (
55
"net/http"
66
"net/http/pprof"
77
"os"
8+
"strconv"
89

910
"go.opentelemetry.io/otel"
1011
"go.opentelemetry.io/otel/attribute"
1112
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
1213

14+
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
1315
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
1416
"github.com/grafana/grafana-plugin-sdk-go/build"
1517
"github.com/grafana/grafana-plugin-sdk-go/internal/tracerprovider"
1618
)
1719

18-
var (
20+
const (
1921
// PluginProfilerEnvDeprecated is a deprecated constant for the GF_PLUGINS_PROFILER environment variable used to enable pprof.
2022
PluginProfilerEnvDeprecated = "GF_PLUGINS_PROFILER"
2123
// PluginProfilingEnabledEnv is a constant for the GF_PLUGIN_PROFILING_ENABLED environment variable used to enable pprof.
@@ -27,15 +29,33 @@ var (
2729
PluginProfilingPortEnv = "GF_PLUGIN_PROFILING_PORT" // nolint:gosec
2830

2931
// PluginTracingOpenTelemetryOTLPAddressEnv is a constant for the GF_INSTANCE_OTLP_ADDRESS
30-
// environment variable used to specify the OTLP Address.
32+
// environment variable used to specify the OTLP address.
3133
PluginTracingOpenTelemetryOTLPAddressEnv = "GF_INSTANCE_OTLP_ADDRESS" // nolint:gosec
3234
// PluginTracingOpenTelemetryOTLPPropagationEnv is a constant for the GF_INSTANCE_OTLP_PROPAGATION
3335
// environment variable used to specify the OTLP propagation format.
3436
PluginTracingOpenTelemetryOTLPPropagationEnv = "GF_INSTANCE_OTLP_PROPAGATION"
3537

38+
// PluginTracingSamplerTypeEnv is a constant for the GF_INSTANCE_OTLP_SAMPLER_TYPE
39+
// environment variable used to specify the OTLP sampler type.
40+
PluginTracingSamplerTypeEnv = "GF_INSTANCE_OTLP_SAMPLER_TYPE"
41+
42+
// PluginTracingSamplerParamEnv is a constant for the GF_INSTANCE_OTLP_SAMPLER_PARAM
43+
// environment variable used to specify an additional float parameter used by the OTLP sampler,
44+
// depending on the type.
45+
PluginTracingSamplerParamEnv = "GF_INSTANCE_OTLP_SAMPLER_PARAM"
46+
47+
// PluginTracingSamplerRemoteURL is a constant for the GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL
48+
// environment variable used to specify the remote url for the sampler type. This is relevant
49+
// only when GF_INSTANCE_OTLP_SAMPLER_TYPE is "remote".
50+
PluginTracingSamplerRemoteURL = "GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL"
51+
3652
// PluginVersionEnv is a constant for the GF_PLUGIN_VERSION environment variable containing the plugin's version.
3753
// Deprecated: Use build.GetBuildInfo().Version instead.
3854
PluginVersionEnv = "GF_PLUGIN_VERSION"
55+
56+
// defaultRemoteSamplerServiceName is the default service name passed to the remote sampler when it cannot be
57+
// determined from the build info.
58+
defaultRemoteSamplerServiceName = "grafana-plugin"
3959
)
4060

4161
// SetupPluginEnvironment will read the environment variables and apply the
@@ -117,17 +137,17 @@ func getTracerCustomAttributes(pluginID string) []attribute.KeyValue {
117137
// SetupTracer sets up the global OTEL trace provider and tracer.
118138
func SetupTracer(pluginID string, tracingOpts tracing.Opts) error {
119139
// Set up tracing
120-
tracingCfg := getTracingConfig()
121-
if tracingCfg.IsEnabled() {
140+
tracingCfg := getTracingConfig(build.GetBuildInfo)
141+
if tracingCfg.isEnabled() {
122142
// Append custom attributes to the default ones
123143
tracingOpts.CustomAttributes = append(getTracerCustomAttributes(pluginID), tracingOpts.CustomAttributes...)
124144

125145
// Initialize global tracer provider
126-
tp, err := tracerprovider.NewTracerProvider(tracingCfg.Address, tracingOpts)
146+
tp, err := tracerprovider.NewTracerProvider(tracingCfg.address, tracingCfg.sampler, tracingOpts)
127147
if err != nil {
128148
return fmt.Errorf("new trace provider: %w", err)
129149
}
130-
pf, err := tracerprovider.NewTextMapPropagator(tracingCfg.Propagation)
150+
pf, err := tracerprovider.NewTextMapPropagator(tracingCfg.propagation)
131151
if err != nil {
132152
return fmt.Errorf("new propagator format: %w", err)
133153
}
@@ -136,30 +156,90 @@ func SetupTracer(pluginID string, tracingOpts tracing.Opts) error {
136156
// Initialize global tracer for plugin developer usage
137157
tracing.InitDefaultTracer(otel.Tracer(pluginID))
138158
}
139-
Logger.Debug("Tracing", "enabled", tracingCfg.IsEnabled(), "propagation", tracingCfg.Propagation)
159+
160+
enabled := tracingCfg.isEnabled()
161+
Logger.Debug("Tracing", "enabled", enabled)
162+
if enabled {
163+
Logger.Debug(
164+
"Tracing configuration",
165+
"propagation", tracingCfg.propagation,
166+
"samplerType", tracingCfg.sampler.SamplerType,
167+
"samplerParam", tracingCfg.sampler.Param,
168+
"samplerRemoteURL", tracingCfg.sampler.Remote.URL,
169+
"samplerRemoteServiceName", tracingCfg.sampler.Remote.ServiceName,
170+
)
171+
}
140172
return nil
141173
}
142174

143175
// tracingConfig contains the configuration for OTEL tracing.
144176
type tracingConfig struct {
145-
Address string
146-
Propagation string
177+
address string
178+
propagation string
179+
180+
sampler tracerprovider.SamplerOptions
147181
}
148182

149-
// IsEnabled returns true if OTEL tracing is enabled.
150-
func (c tracingConfig) IsEnabled() bool {
151-
return c.Address != ""
183+
// isEnabled returns true if OTEL tracing is enabled.
184+
func (c tracingConfig) isEnabled() bool {
185+
return c.address != ""
152186
}
153187

154188
// getTracingConfig returns a new tracingConfig based on the current environment variables.
155-
func getTracingConfig() tracingConfig {
156-
var otelAddr, otelPropagation string
189+
func getTracingConfig(buildInfoGetter build.InfoGetter) tracingConfig {
190+
var otelAddr, otelPropagation, samplerRemoteURL, samplerParamString string
191+
var samplerType tracerprovider.SamplerType
192+
var samplerParam float64
157193
otelAddr, ok := os.LookupEnv(PluginTracingOpenTelemetryOTLPAddressEnv)
158194
if ok {
195+
// Additional OTEL config
159196
otelPropagation = os.Getenv(PluginTracingOpenTelemetryOTLPPropagationEnv)
197+
198+
// Sampling config
199+
samplerType = tracerprovider.SamplerType(os.Getenv(PluginTracingSamplerTypeEnv))
200+
samplerRemoteURL = os.Getenv(PluginTracingSamplerRemoteURL)
201+
samplerParamString = os.Getenv(PluginTracingSamplerParamEnv)
202+
var err error
203+
samplerParam, err = strconv.ParseFloat(samplerParamString, 64)
204+
if err != nil {
205+
// Default value if invalid float is provided is 1.0 (AlwaysSample)
206+
log.DefaultLogger.Warn(
207+
"Could not parse sampler param to float, defaulting to 1.0",
208+
"samplerParam", samplerParamString, "error", err,
209+
)
210+
samplerParam = 1.0
211+
}
212+
}
213+
var serviceName string
214+
if samplerType == tracerprovider.SamplerTypeRemote {
215+
serviceName = remoteSamplerServiceName(buildInfoGetter)
160216
}
161217
return tracingConfig{
162-
Address: otelAddr,
163-
Propagation: otelPropagation,
218+
address: otelAddr,
219+
propagation: otelPropagation,
220+
sampler: tracerprovider.SamplerOptions{
221+
SamplerType: samplerType,
222+
Param: samplerParam,
223+
Remote: tracerprovider.RemoteSamplerOptions{
224+
URL: samplerRemoteURL,
225+
ServiceName: serviceName,
226+
},
227+
},
228+
}
229+
}
230+
231+
// remoteSamplerServiceName returns the service name for the remote tracing sampler.
232+
// It attempts to get it from the provided buildinfo getter. If unsuccessful or empty,
233+
// defaultRemoteSamplerServiceName is returned instead.
234+
func remoteSamplerServiceName(buildInfoGetter build.InfoGetter) string {
235+
// Use plugin id as service name, if possible. Otherwise, use a generic default value.
236+
bi, err := buildInfoGetter.GetInfo()
237+
if err != nil {
238+
log.DefaultLogger.Warn("Could not get build info for remote sampler service name", "error", err)
239+
return defaultRemoteSamplerServiceName
240+
}
241+
if bi.PluginID == "" {
242+
return defaultRemoteSamplerServiceName
164243
}
244+
return bi.PluginID
165245
}

backend/setup_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package backend
2+
3+
import (
4+
"testing"
5+
6+
"github.com/grafana/grafana-plugin-sdk-go/build"
7+
"github.com/grafana/grafana-plugin-sdk-go/internal/tracerprovider"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestGetTracingConfig(t *testing.T) {
12+
for _, tc := range []struct {
13+
name string
14+
15+
env map[string]string
16+
buildInfoGetter build.InfoGetter
17+
18+
expEnabled bool
19+
expCfg tracingConfig
20+
}{
21+
{
22+
name: "disabled",
23+
env: nil,
24+
expEnabled: false,
25+
expCfg: tracingConfig{},
26+
},
27+
{
28+
name: "otel with default sampler",
29+
env: map[string]string{
30+
PluginTracingOpenTelemetryOTLPAddressEnv: "127.0.0.1:10000",
31+
PluginTracingOpenTelemetryOTLPPropagationEnv: "jaeger",
32+
},
33+
expEnabled: true,
34+
expCfg: tracingConfig{
35+
address: "127.0.0.1:10000",
36+
propagation: "jaeger",
37+
sampler: tracerprovider.SamplerOptions{
38+
SamplerType: "",
39+
Param: 1.0, // always sample
40+
Remote: tracerprovider.RemoteSamplerOptions{},
41+
},
42+
},
43+
},
44+
{
45+
name: "otel with sampler and sampler param",
46+
env: map[string]string{
47+
PluginTracingOpenTelemetryOTLPAddressEnv: "127.0.0.1:10000",
48+
PluginTracingOpenTelemetryOTLPPropagationEnv: "jaeger",
49+
PluginTracingSamplerTypeEnv: "rateLimiting",
50+
PluginTracingSamplerParamEnv: "0.5",
51+
},
52+
expEnabled: true,
53+
expCfg: tracingConfig{
54+
address: "127.0.0.1:10000",
55+
propagation: "jaeger",
56+
sampler: tracerprovider.SamplerOptions{
57+
SamplerType: "rateLimiting",
58+
Param: 0.5,
59+
Remote: tracerprovider.RemoteSamplerOptions{},
60+
},
61+
},
62+
},
63+
{
64+
name: "otel with remote sampler",
65+
env: map[string]string{
66+
PluginTracingOpenTelemetryOTLPAddressEnv: "127.0.0.1:10000",
67+
PluginTracingOpenTelemetryOTLPPropagationEnv: "jaeger",
68+
PluginTracingSamplerTypeEnv: "remote",
69+
PluginTracingSamplerParamEnv: "0.5",
70+
PluginTracingSamplerRemoteURL: "127.0.0.1:10001",
71+
},
72+
expEnabled: true,
73+
expCfg: tracingConfig{
74+
address: "127.0.0.1:10000",
75+
propagation: "jaeger",
76+
sampler: tracerprovider.SamplerOptions{
77+
SamplerType: "remote",
78+
Param: 0.5,
79+
Remote: tracerprovider.RemoteSamplerOptions{
80+
URL: "127.0.0.1:10001",
81+
ServiceName: "grafana-plugin",
82+
},
83+
},
84+
},
85+
},
86+
{
87+
name: "otel with remote sampler and buildinfo service name",
88+
env: map[string]string{
89+
PluginTracingOpenTelemetryOTLPAddressEnv: "127.0.0.1:10000",
90+
PluginTracingOpenTelemetryOTLPPropagationEnv: "jaeger",
91+
PluginTracingSamplerTypeEnv: "remote",
92+
PluginTracingSamplerParamEnv: "0.5",
93+
PluginTracingSamplerRemoteURL: "127.0.0.1:10001",
94+
},
95+
buildInfoGetter: build.InfoGetterFunc(func() (build.Info, error) {
96+
return build.Info{PluginID: "my-example-datasource"}, nil
97+
}),
98+
expEnabled: true,
99+
expCfg: tracingConfig{
100+
address: "127.0.0.1:10000",
101+
propagation: "jaeger",
102+
sampler: tracerprovider.SamplerOptions{
103+
SamplerType: "remote",
104+
Param: 0.5,
105+
Remote: tracerprovider.RemoteSamplerOptions{
106+
URL: "127.0.0.1:10001",
107+
ServiceName: "my-example-datasource",
108+
},
109+
},
110+
},
111+
},
112+
} {
113+
t.Run(tc.name, func(t *testing.T) {
114+
for e, v := range tc.env {
115+
t.Setenv(e, v)
116+
}
117+
if tc.buildInfoGetter == nil {
118+
tc.buildInfoGetter = build.GetBuildInfo
119+
}
120+
cfg := getTracingConfig(tc.buildInfoGetter)
121+
require.Equal(t, tc.expEnabled, cfg.isEnabled())
122+
require.Equal(t, tc.expCfg, cfg)
123+
})
124+
}
125+
}

build/info.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,26 @@ func getBuildInfoFromEnvironment() Info {
111111
return v
112112
}
113113

114-
// GetBuildInfo returns the build information that was compiled into the binary using:
114+
// InfoGetter is an interface with a method for returning the build info.
115+
type InfoGetter interface {
116+
// GetInfo returns the build info.
117+
GetInfo() (Info, error)
118+
}
119+
120+
// InfoGetterFunc can be used to adapt ordinary functions into types satisfying the InfoGetter interface .
121+
type InfoGetterFunc func() (Info, error)
122+
123+
func (f InfoGetterFunc) GetInfo() (Info, error) {
124+
return f()
125+
}
126+
127+
// GetBuildInfo is the default InfoGetter that returns the build information that was compiled into the binary using:
115128
// -X `github.com/grafana/grafana-plugin-sdk-go/build.buildInfoJSON={...}`
116-
func GetBuildInfo() (Info, error) {
129+
var GetBuildInfo = InfoGetterFunc(func() (Info, error) {
117130
v := Info{}
118131
if buildInfoJSON == "" {
119132
return v, fmt.Errorf("build info was now set when this was compiled")
120133
}
121134
err := json.Unmarshal([]byte(buildInfoJSON), &v)
122135
return v, err
123-
}
136+
})

0 commit comments

Comments
 (0)