mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 04:04:00 -06:00
5a21a6d938
Parca: decouple backend
124 lines
5.1 KiB
Go
124 lines
5.1 KiB
Go
package parca
|
|
|
|
import (
|
|
"context"
|
|
|
|
"buf.build/gen/go/parca-dev/parca/bufbuild/connect-go/parca/query/v1alpha1/queryv1alpha1connect"
|
|
v1alpha1 "buf.build/gen/go/parca-dev/parca/protocolbuffers/go/parca/query/v1alpha1"
|
|
"github.com/bufbuild/connect-go"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
// Make sure ParcaDatasource implements required interfaces. This is important to do
|
|
// since otherwise we will only get a not implemented error response from plugin in
|
|
// runtime. In this example datasource instance implements backend.QueryDataHandler,
|
|
// backend.CheckHealthHandler, backend.StreamHandler interfaces. Plugin should not
|
|
// implement all these interfaces - only those which are required for a particular task.
|
|
// For example if plugin does not need streaming functionality then you are free to remove
|
|
// methods that implement backend.StreamHandler. Implementing instancemgmt.InstanceDisposer
|
|
// is useful to clean up resources used by previous datasource instance when a new datasource
|
|
// instance created upon datasource settings changed.
|
|
var (
|
|
_ backend.QueryDataHandler = (*ParcaDatasource)(nil)
|
|
_ backend.CallResourceHandler = (*ParcaDatasource)(nil)
|
|
_ backend.CheckHealthHandler = (*ParcaDatasource)(nil)
|
|
)
|
|
|
|
// ParcaDatasource is a datasource for querying application performance profiles.
|
|
type ParcaDatasource struct {
|
|
client queryv1alpha1connect.QueryServiceClient
|
|
}
|
|
|
|
// NewParcaDatasource creates a new datasource instance.
|
|
func NewParcaDatasource(ctx context.Context, httpClientProvider *httpclient.Provider, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
|
ctxLogger := logger.FromContext(ctx)
|
|
opt, err := settings.HTTPClientOptions(ctx)
|
|
if err != nil {
|
|
ctxLogger.Error("Failed to get HTTP options", "error", err, "function", logEntrypoint())
|
|
return nil, err
|
|
}
|
|
httpClient, err := httpClientProvider.New(opt)
|
|
if err != nil {
|
|
ctxLogger.Error("Failed to create HTTP client", "error", err, "function", logEntrypoint())
|
|
return nil, err
|
|
}
|
|
|
|
return &ParcaDatasource{
|
|
client: queryv1alpha1connect.NewQueryServiceClient(httpClient, settings.URL, connect.WithGRPCWeb()),
|
|
}, nil
|
|
}
|
|
|
|
// Dispose here tells plugin SDK that plugin wants to clean up resources when a new instance
|
|
// created. As soon as datasource settings change detected by SDK old datasource instance will
|
|
// be disposed and a new one will be created using NewSampleDatasource factory function.
|
|
func (d *ParcaDatasource) Dispose() {
|
|
// Clean up datasource instance resources.
|
|
}
|
|
|
|
func (d *ParcaDatasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
|
ctxLogger := logger.FromContext(ctx)
|
|
ctxLogger.Debug("CallResource", "Path", req.Path, "Method", req.Method, "Body", req.Body, "function", logEntrypoint())
|
|
ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.parca.CallResource", trace.WithAttributes(attribute.String("path", req.Path), attribute.String("method", req.Method)))
|
|
defer span.End()
|
|
|
|
if req.Path == "profileTypes" {
|
|
return d.callProfileTypes(ctx, req, sender)
|
|
}
|
|
if req.Path == "labelNames" {
|
|
return d.callLabelNames(ctx, req, sender)
|
|
}
|
|
if req.Path == "labelValues" {
|
|
ctxLogger.Debug("CallResource completed", "function", logEntrypoint())
|
|
return d.callLabelValues(ctx, req, sender)
|
|
}
|
|
return sender.Send(&backend.CallResourceResponse{
|
|
Status: 404,
|
|
})
|
|
}
|
|
|
|
// QueryData handles multiple queries and returns multiple responses.
|
|
// req contains the queries []DataQuery (where each query contains RefID as a unique identifier).
|
|
// The QueryDataResponse contains a map of RefID to the response for each query, and each response
|
|
// contains Frames ([]*Frame).
|
|
func (d *ParcaDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
|
// create response struct
|
|
response := backend.NewQueryDataResponse()
|
|
|
|
// loop over queries and execute them individually.
|
|
for _, q := range req.Queries {
|
|
res := d.query(ctx, req.PluginContext, q)
|
|
|
|
// save the response in a hashmap
|
|
// based on with RefID as identifier
|
|
response.Responses[q.RefID] = res
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// CheckHealth handles health checks sent from Grafana to the plugin.
|
|
// The main use case for these health checks is the test button on the
|
|
// datasource configuration page which allows users to verify that
|
|
// a datasource is working as expected.
|
|
func (d *ParcaDatasource) CheckHealth(ctx context.Context, _ *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
|
ctxLogger := logger.FromContext(ctx)
|
|
status := backend.HealthStatusOk
|
|
message := "Data source is working"
|
|
|
|
if _, err := d.client.ProfileTypes(ctx, connect.NewRequest(&v1alpha1.ProfileTypesRequest{})); err != nil {
|
|
status = backend.HealthStatusError
|
|
message = err.Error()
|
|
}
|
|
|
|
ctxLogger.Debug("CheckHealth completed", "function", logEntrypoint())
|
|
return &backend.CheckHealthResult{
|
|
Status: status,
|
|
Message: message,
|
|
}, nil
|
|
}
|