The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/tsdb/parca/plugin.go

123 lines
5.1 KiB

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
}