Pyroscope: Remove support for old pyroscope (#74683)

This commit is contained in:
Andrej Ocenas 2023-09-19 10:09:28 +02:00 committed by GitHub
parent 8b4d167de5
commit f7aab06e23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 78 additions and 601 deletions

1
.github/CODEOWNERS vendored
View File

@ -194,7 +194,6 @@
/devenv/docker/blocks/mysql_opendata/ @grafana/oss-big-tent
/devenv/docker/blocks/mysql_tests/ @grafana/oss-big-tent
/devenv/docker/blocks/opentsdb/ @grafana/observability-metrics
/devenv/docker/blocks/phlare/ @grafana/observability-traces-and-profiling
/devenv/docker/blocks/postgres/ @grafana/oss-big-tent
/devenv/docker/blocks/postgres_tests/ @grafana/oss-big-tent
/devenv/docker/blocks/prometheus/ @grafana/observability-metrics

View File

@ -1,8 +0,0 @@
phlare:
image: grafana/phlare:latest
command:
- --config.file=/etc/phlare.yaml
ports:
- 4100:4100
volumes:
- ./docker/blocks/phlare/phlare.yaml:/etc/phlare.yaml

View File

@ -1,5 +0,0 @@
scrape_configs:
- job_name: "default"
scrape_interval: "15s"
static_configs:
- targets: ["127.0.0.1:4100"]

View File

@ -1,6 +1,4 @@
pyroscope:
image: "pyroscope/pyroscope:latest"
command:
- "server"
image: "grafana/pyroscope:latest"
ports:
- "4040:4040"

View File

@ -21,7 +21,7 @@ weight: 1150
# Grafana Pyroscope data source
Formerly Phlare data source, it supports both Phlare and Pyroscope, a horizontally scalable, highly-available, multi-tenant, OSS, continuous profiling aggregation systems. Add it as a data source, and you are ready to query your profiles in [Explore][explore].
Formerly Phlare data source, now Grafana Pyroscope, a horizontally scalable, highly-available, multi-tenant, OSS, continuous profiling aggregation system. Add it as a data source, and you are ready to query your profiles in [Explore][explore].
## Configure the Grafana Pyroscope data source
@ -45,7 +45,6 @@ To configure basic settings for the data source, complete the following steps:
| `User` | User name for basic authentication. |
| `Password` | Password for basic authentication. |
| `Minimal step` | Used for queries returning timeseries data. Phlare backend, similar to Prometheus, scrapes profiles at certain intervals. To prevent querying at smaller interval use Minimal step same or higher than your Phlare scrape interval. For Pyroscope backend this prevents returning too many data points to the front end. |
| `Backend type` | Select a backend type between Phlare and Pyroscope. It is autodetected if not set but once set you have to change it manually. |
## Querying
@ -55,13 +54,13 @@ To configure basic settings for the data source, complete the following steps:
Query editor gives you access to a profile type selector, a label selector, and collapsible options.
![Profile or App selector](/static/img/docs/phlare/select-profile.png 'Profile or App selector')
![Profile selector](/static/img/docs/phlare/select-profile.png 'Profile selector')
Select a profile type or app from the drop-down menu. While the label selector can be left empty to query all profiles without filtering by labels, the profile type or app must be selected for the query to be valid. Grafana does not show any data if the profile type or app isnt selected when a query is run.
Select a profile type from the drop-down menu. While the label selector can be left empty to query all profiles without filtering by labels, the profile type or app must be selected for the query to be valid. Grafana does not show any data if the profile type or app isnt selected when a query is run.
![Labels selector](/static/img/docs/phlare/labels-selector.png 'Labels selector')
Use the labels selector input to filter by labels. Phlare and Pyroscope uses similar syntax to Prometheus to filter labels. Refer to [Phlare documentation](https://grafana.com/docs/phlare/latest/) for available operators and syntax.
Use the labels selector input to filter by labels. Pyroscope uses similar syntax to Prometheus to filter labels. Refer to [Pyroscope documentation](https://grafana.com/docs/pyroscope/latest/) for available operators and syntax.
![Options section](/static/img/docs/phlare/options-section.png 'Options section')
@ -77,7 +76,7 @@ Profiles can be visualized in a flame graph. See the [Flame Graph documentation]
![Flame graph](/static/img/docs/phlare/flame-graph.png 'Flame graph')
Phlare and Pyroscope returns profiles aggregated over a selected time range, and the absolute values in the flame graph grow as the time range gets bigger while keeping the relative values meaningful. You can zoom in on the time range to get a higher granularity profile up to the point of a single scrape interval.
Pyroscope returns profiles aggregated over a selected time range, and the absolute values in the flame graph grow as the time range gets bigger while keeping the relative values meaningful. You can zoom in on the time range to get a higher granularity profile up to the point of a single scrape interval.
### Metrics query results
@ -98,11 +97,10 @@ apiVersion: 1
datasources:
- name: Grafana Pyroscope
type: phlare
url: http://localhost:4100
type: grafana-pyroscope-datasource
url: http://localhost:4040
jsonData:
minStep: '15s'
backendType: 'pyroscope'
```
{{% docs/reference %}}

View File

@ -671,7 +671,7 @@
"name": "Grafana Labs",
"url": "https://www.grafana.com"
},
"description": "Supports Phlare and Pyroscope backends, horizontally-scalable, highly-available, multi-tenant continuous profiling aggregation systems.",
"description": "Data source for Grafana Pyroscope, horizontally-scalable, highly-available, multi-tenant continuous profiling aggregation system.",
"links": [
{
"name": "GitHub Project",
@ -1896,4 +1896,4 @@
"signatureOrg": "",
"angularDetected": false
}
]
]

View File

@ -1,66 +0,0 @@
package phlare
import (
"context"
"net/http"
)
type ProfilingClient interface {
ProfileTypes(context.Context) ([]*ProfileType, error)
LabelNames(ctx context.Context, query string, start int64, end int64) ([]string, error)
LabelValues(ctx context.Context, query string, label string, start int64, end int64) ([]string, error)
GetSeries(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, groupBy []string, step float64) (*SeriesResponse, error)
GetProfile(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, maxNodes *int64) (*ProfileResponse, error)
}
type ProfileType struct {
ID string `json:"id"`
Label string `json:"label"`
}
func getClient(backendType string, httpClient *http.Client, url string) ProfilingClient {
if backendType == "pyroscope" {
return NewPyroscopeClient(httpClient, url)
}
// We treat unset value as phlare
return NewPhlareClient(httpClient, url)
}
type Flamebearer struct {
Names []string
Levels []*Level
Total int64
MaxSelf int64
}
type Level struct {
Values []int64
}
type Series struct {
Labels []*LabelPair
Points []*Point
}
type LabelPair struct {
Name string
Value string
}
type Point struct {
Value float64
// Milliseconds unix timestamp
Timestamp int64
}
type ProfileResponse struct {
Flamebearer *Flamebearer
Units string
}
type SeriesResponse struct {
Series []*Series
Units string
Label string
}

View File

@ -6,7 +6,6 @@ import (
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
@ -14,8 +13,6 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/datasources"
)
var (
@ -25,6 +22,14 @@ var (
_ backend.StreamHandler = (*PhlareDatasource)(nil)
)
type ProfilingClient interface {
ProfileTypes(context.Context) ([]*ProfileType, error)
LabelNames(ctx context.Context) ([]string, error)
LabelValues(ctx context.Context, label string) ([]string, error)
GetSeries(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, groupBy []string, step float64) (*SeriesResponse, error)
GetProfile(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, maxNodes *int64) (*ProfileResponse, error)
}
// PhlareDatasource is a datasource for querying application performance profiles.
type PhlareDatasource struct {
httpClient *http.Client
@ -33,10 +38,6 @@ type PhlareDatasource struct {
ac accesscontrol.AccessControl
}
type JsonData struct {
BackendType string `json:"backendType"`
}
// NewPhlareDatasource creates a new datasource instance.
func NewPhlareDatasource(httpClientProvider httpclient.Provider, settings backend.DataSourceInstanceSettings, ac accesscontrol.AccessControl) (instancemgmt.Instance, error) {
opt, err := settings.HTTPClientOptions()
@ -48,15 +49,9 @@ func NewPhlareDatasource(httpClientProvider httpclient.Provider, settings backen
return nil, err
}
var jsonData *JsonData
err = json.Unmarshal(settings.JSONData, &jsonData)
if err != nil {
return nil, err
}
return &PhlareDatasource{
httpClient: httpClient,
client: getClient(jsonData.BackendType, httpClient, settings.URL),
client: NewPhlareClient(httpClient, settings.URL),
settings: settings,
ac: ac,
}, nil
@ -73,9 +68,6 @@ func (d *PhlareDatasource) CallResource(ctx context.Context, req *backend.CallRe
if req.Path == "labelValues" {
return d.labelValues(ctx, req, sender)
}
if req.Path == "backendType" {
return d.backendType(ctx, req, sender)
}
return sender.Send(&backend.CallResourceResponse{
Status: 404,
})
@ -98,21 +90,7 @@ func (d *PhlareDatasource) profileTypes(ctx context.Context, req *backend.CallRe
}
func (d *PhlareDatasource) labelNames(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
u, err := url.Parse(req.URL)
if err != nil {
return err
}
query := u.Query()
start, err := strconv.ParseInt(query["start"][0], 10, 64)
if err != nil {
return err
}
end, err := strconv.ParseInt(query["end"][0], 10, 64)
if err != nil {
return err
}
res, err := d.client.LabelNames(ctx, query["query"][0], start, end)
res, err := d.client.LabelNames(ctx)
if err != nil {
return fmt.Errorf("error calling LabelNames: %v", err)
}
@ -140,16 +118,8 @@ func (d *PhlareDatasource) labelValues(ctx context.Context, req *backend.CallRes
return err
}
query := u.Query()
start, err := strconv.ParseInt(query["start"][0], 10, 64)
if err != nil {
return err
}
end, err := strconv.ParseInt(query["end"][0], 10, 64)
if err != nil {
return err
}
res, err := d.client.LabelValues(ctx, query["query"][0], query["label"][0], start, end)
res, err := d.client.LabelValues(ctx, query["label"][0])
if err != nil {
return fmt.Errorf("error calling LabelValues: %v", err)
}
@ -164,74 +134,6 @@ func (d *PhlareDatasource) labelValues(ctx context.Context, req *backend.CallRes
return nil
}
type BackendTypeRespBody struct {
BackendType string `json:"backendType"` // "phlare" or "pyroscope"
}
// backendType is a simplistic test to figure out if we are speaking to phlare or pyroscope backend
func (d *PhlareDatasource) backendType(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
// To prevent any user sending arbitrary URL for us to test with we allow this only for users who can edit the datasource
// as config page is where this is meant to be used.
ok, err := d.isUserAllowedToEditDatasource(ctx)
if err != nil {
return err
}
if !ok {
return sender.Send(&backend.CallResourceResponse{Headers: req.Headers, Status: 401})
}
u, err := url.Parse(req.URL)
if err != nil {
return err
}
query := u.Query()
body := &BackendTypeRespBody{BackendType: "unknown"}
// We take the url from the request query because the data source may not yet be saved in DB with the URL we want
// to test with (like when filling in the confgi page for the first time)
url := query["url"][0]
pyroClient := getClient("pyroscope", d.httpClient, url)
_, err = pyroClient.ProfileTypes(ctx)
if err == nil {
body.BackendType = "pyroscope"
} else {
phlareClient := getClient("phlare", d.httpClient, url)
_, err := phlareClient.ProfileTypes(ctx)
if err == nil {
body.BackendType = "phlare"
}
}
data, err := json.Marshal(body)
if err != nil {
return err
}
return sender.Send(&backend.CallResourceResponse{Body: data, Headers: req.Headers, Status: 200})
}
func (d *PhlareDatasource) isUserAllowedToEditDatasource(ctx context.Context) (bool, error) {
reqCtx := contexthandler.FromContext(ctx)
uidScope := datasources.ScopeProvider.GetResourceScopeUID(accesscontrol.Parameter(":uid"))
if reqCtx == nil || reqCtx.SignedInUser == nil {
return false, nil
}
ok, err := d.ac.Evaluate(ctx, reqCtx.SignedInUser, accesscontrol.EvalPermission(datasources.ActionWrite, uidScope))
if err != nil {
return false, err
}
if !ok {
return false, nil
}
return true, nil
}
// 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

View File

@ -11,6 +11,49 @@ import (
"github.com/grafana/phlare/api/gen/proto/go/querier/v1/querierv1connect"
)
type ProfileType struct {
ID string `json:"id"`
Label string `json:"label"`
}
type Flamebearer struct {
Names []string
Levels []*Level
Total int64
MaxSelf int64
}
type Level struct {
Values []int64
}
type Series struct {
Labels []*LabelPair
Points []*Point
}
type LabelPair struct {
Name string
Value string
}
type Point struct {
Value float64
// Milliseconds unix timestamp
Timestamp int64
}
type ProfileResponse struct {
Flamebearer *Flamebearer
Units string
}
type SeriesResponse struct {
Series []*Series
Units string
Label string
}
type PhlareClient struct {
connectClient querierv1connect.QuerierServiceClient
}
@ -136,7 +179,7 @@ func getUnits(profileTypeID string) string {
return unit
}
func (c *PhlareClient) LabelNames(ctx context.Context, query string, start int64, end int64) ([]string, error) {
func (c *PhlareClient) LabelNames(ctx context.Context) ([]string, error) {
resp, err := c.connectClient.LabelNames(ctx, connect.NewRequest(&querierv1.LabelNamesRequest{}))
if err != nil {
return nil, fmt.Errorf("error seding LabelNames request %v", err)
@ -152,7 +195,7 @@ func (c *PhlareClient) LabelNames(ctx context.Context, query string, start int64
return filtered, nil
}
func (c *PhlareClient) LabelValues(ctx context.Context, query string, label string, start int64, end int64) ([]string, error) {
func (c *PhlareClient) LabelValues(ctx context.Context, label string) ([]string, error) {
resp, err := c.connectClient.LabelValues(ctx, connect.NewRequest(&querierv1.LabelValuesRequest{Name: label}))
if err != nil {
return nil, err

View File

@ -1,282 +0,0 @@
package phlare
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
)
type PyroscopeClient struct {
httpClient *http.Client
URL string
}
type App struct {
Name string `json:"name"`
}
func NewPyroscopeClient(httpClient *http.Client, url string) *PyroscopeClient {
return &PyroscopeClient{
httpClient: httpClient,
URL: url,
}
}
func (c *PyroscopeClient) ProfileTypes(ctx context.Context) ([]*ProfileType, error) {
resp, err := c.httpClient.Get(c.URL + "/api/apps")
if err != nil {
return nil, err
}
defer func() {
if err := resp.Body.Close(); err != nil {
logger.Error("Failed to close response body", "err", err)
}
}()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var apps []App
err = json.Unmarshal(body, &apps)
if err != nil {
return nil, err
}
var profileTypes []*ProfileType
for _, app := range apps {
profileTypes = append(profileTypes, &ProfileType{
ID: app.Name,
Label: app.Name,
})
}
return profileTypes, nil
}
type PyroscopeProfileResponse struct {
Flamebearer *PyroFlamebearer `json:"flamebearer"`
Metadata *Metadata `json:"metadata"`
Groups map[string]*Group `json:"groups"`
}
type Metadata struct {
Units string `json:"units"`
}
type Group struct {
StartTime int64 `json:"startTime"`
Samples []int64 `json:"samples"`
DurationDelta int64 `json:"durationDelta"`
}
type PyroFlamebearer struct {
Levels [][]int64 `json:"levels"`
MaxSelf int64 `json:"maxSelf"`
NumTicks int64 `json:"numTicks"`
Names []string `json:"names"`
}
func (c *PyroscopeClient) getProfileData(ctx context.Context, profileTypeID, labelSelector string, start, end int64, maxNodes *int64, groupBy []string) (*PyroscopeProfileResponse, error) {
params := url.Values{}
params.Add("from", strconv.FormatInt(start, 10))
params.Add("until", strconv.FormatInt(end, 10))
params.Add("query", profileTypeID+labelSelector)
if maxNodes != nil {
params.Add("maxNodes", strconv.FormatInt(*maxNodes, 10))
}
params.Add("format", "json")
if len(groupBy) > 0 {
params.Add("groupBy", groupBy[0])
}
url := c.URL + "/render?" + params.Encode()
logger.Debug("Calling /render", "url", url)
resp, err := c.httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("error calling /render api: %v", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
logger.Error("Failed to close response body", "err", err)
}
}()
var respData *PyroscopeProfileResponse
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %v", err)
}
err = json.Unmarshal(body, &respData)
if err != nil {
logger.Debug("Flamegraph data", "body", string(body))
return nil, fmt.Errorf("error decoding flamegraph data: %v", err)
}
return respData, nil
}
func (c *PyroscopeClient) GetProfile(ctx context.Context, profileTypeID, labelSelector string, start, end int64, maxNodes *int64) (*ProfileResponse, error) {
respData, err := c.getProfileData(ctx, profileTypeID, labelSelector, start, end, maxNodes, nil)
if err != nil {
return nil, err
}
mappedLevels := make([]*Level, len(respData.Flamebearer.Levels))
for i, level := range respData.Flamebearer.Levels {
mappedLevels[i] = &Level{
Values: level,
}
}
units := "short"
if respData.Metadata.Units == "bytes" {
units = "bytes"
}
if respData.Metadata.Units == "samples" {
units = "ms"
}
return &ProfileResponse{
Flamebearer: &Flamebearer{
Names: respData.Flamebearer.Names,
Levels: mappedLevels,
Total: respData.Flamebearer.NumTicks,
MaxSelf: respData.Flamebearer.MaxSelf,
},
Units: units,
}, nil
}
func (c *PyroscopeClient) GetSeries(ctx context.Context, profileTypeID string, labelSelector string, start, end int64, groupBy []string, step float64) (*SeriesResponse, error) {
// This is super ineffective at the moment. We need 2 different APIs one for profile one for series (timeline) data
// but Pyro returns all in single response. This currently does the simplest thing and calls the same API 2 times
// and gets the part of the response it needs.
respData, err := c.getProfileData(ctx, profileTypeID, labelSelector, start, end, nil, groupBy)
if err != nil {
return nil, err
}
stepMillis := int64(step * 1000)
var series []*Series
if len(respData.Groups) == 1 {
series = []*Series{processGroup(respData.Groups["*"], stepMillis, nil)}
} else {
for key, val := range respData.Groups {
// If we have a group by, we don't want the * group
if key != "*" {
label := &LabelPair{
Name: groupBy[0],
Value: key,
}
series = append(series, processGroup(val, stepMillis, label))
}
}
}
return &SeriesResponse{Series: series}, nil
}
// processGroup turns group timeline data into the Series format. Pyro does not seem to have a way to define step, so we
// always get the data in specific step, and we have to aggregate a bit into s step size we need.
func processGroup(group *Group, step int64, label *LabelPair) *Series {
series := &Series{}
if label != nil {
series.Labels = []*LabelPair{label}
}
durationDeltaMillis := group.DurationDelta * 1000
timestamp := group.StartTime * 1000
value := int64(0)
for i, sample := range group.Samples {
pointsLen := int64(len(series.Points))
// Check if the timestamp of the sample is more than next timestamp in the series. If so we create a new point
// with the value we have so far.
if int64(i)*durationDeltaMillis > step*pointsLen+1 {
series.Points = append(series.Points, &Point{
Value: float64(value),
Timestamp: timestamp + step*pointsLen,
})
value = 0
}
value += sample
}
return series
}
func (c *PyroscopeClient) LabelNames(ctx context.Context, query string, start int64, end int64) ([]string, error) {
params := url.Values{}
// Seems like this should be seconds instead of millis for other endpoints
params.Add("from", strconv.FormatInt(start/1000, 10))
params.Add("until", strconv.FormatInt(end/1000, 10))
params.Add("query", query)
resp, err := c.httpClient.Get(c.URL + "/labels?" + params.Encode())
if err != nil {
return nil, err
}
defer func() {
if err := resp.Body.Close(); err != nil {
logger.Error("Failed to close response body", "err", err)
}
}()
var names []string
err = json.NewDecoder(resp.Body).Decode(&names)
if err != nil {
return nil, err
}
var filtered []string
for _, label := range names {
// Using the same func from Phlare client, works but should do separate one probably
if !isPrivateLabel(label) {
filtered = append(filtered, label)
}
}
return filtered, nil
}
func (c *PyroscopeClient) LabelValues(ctx context.Context, query string, label string, start int64, end int64) ([]string, error) {
params := url.Values{}
// Seems like this should be seconds instead of millis for other endpoints
params.Add("from", strconv.FormatInt(start/1000, 10))
params.Add("until", strconv.FormatInt(end/1000, 10))
params.Add("label", label)
params.Add("query", query)
resp, err := c.httpClient.Get(c.URL + "/labels?" + params.Encode())
if err != nil {
return nil, err
}
defer func() {
if err := resp.Body.Close(); err != nil {
logger.Error("Failed to close response body", "err", err)
}
}()
var values []string
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &values)
if err != nil {
logger.Debug("Response", "body", string(body))
return nil, fmt.Errorf("error unmarshaling response %v", err)
}
return values, nil
}

View File

@ -288,11 +288,11 @@ func (f *FakeClient) ProfileTypes(ctx context.Context) ([]*ProfileType, error) {
}, nil
}
func (f *FakeClient) LabelValues(ctx context.Context, query string, label string, start int64, end int64) ([]string, error) {
func (f *FakeClient) LabelValues(ctx context.Context, label string) ([]string, error) {
panic("implement me")
}
func (f *FakeClient) LabelNames(ctx context.Context, query string, start int64, end int64) ([]string, error) {
func (f *FakeClient) LabelNames(ctx context.Context) ([]string, error) {
panic("implement me")
}

View File

@ -1,57 +1,20 @@
import React from 'react';
import { useAsyncFn, useDebounce } from 'react-use';
import { DataSourcePluginOptionsEditorProps, SelectableValue } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { Alert, DataSourceHttpSettings, EventsWithValidation, LegacyForms, regexValidation } from '@grafana/ui';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { DataSourceHttpSettings, EventsWithValidation, LegacyForms, regexValidation } from '@grafana/ui';
import { config } from 'app/core/config';
import { PhlareDataSource } from './datasource';
import { BackendType, PhlareDataSourceOptions } from './types';
import { PhlareDataSourceOptions } from './types';
interface Props extends DataSourcePluginOptionsEditorProps<PhlareDataSourceOptions> {}
export const ConfigEditor = (props: Props) => {
const { options, onOptionsChange } = props;
const [mismatchedBackendType, setMismatchedBackendType] = React.useState<BackendType | undefined>();
const dataSourceSrv = getDataSourceSrv();
const [, getBackendType] = useAsyncFn(async () => {
if (!options.url) {
return;
}
const ds = await dataSourceSrv.get({ type: options.type, uid: options.uid });
if (!(ds instanceof PhlareDataSource)) {
// Should not happen, makes TS happy
throw new Error('Datasource is not a PhlareDataSource');
}
const { backendType } = await ds.getBackendType(options.url);
if (backendType === 'unknown') {
setMismatchedBackendType(undefined);
return;
}
// If user already has something selected don't overwrite but show warning.
if (options.jsonData.backendType) {
if (backendType !== options.jsonData.backendType) {
setMismatchedBackendType(backendType);
} else {
setMismatchedBackendType(undefined);
}
return;
}
onOptionsChange({ ...options, jsonData: { ...options.jsonData, backendType } });
}, [options]);
useDebounce(getBackendType, 500, [options]);
return (
<>
<DataSourceHttpSettings
defaultUrl={'http://localhost:4100'}
defaultUrl={'http://localhost:4040'}
dataSourceConfig={options}
showAccessOptions={false}
onChange={onOptionsChange}
@ -94,50 +57,7 @@ export const ConfigEditor = (props: Props) => {
/>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<LegacyForms.FormField
label="Backend type"
labelWidth={13}
inputEl={
<LegacyForms.Select<BackendType>
allowCustomValue={false}
value={options.jsonData.backendType ? backendTypeOptions[options.jsonData.backendType] : undefined}
options={Object.values(backendTypeOptions)}
onChange={(option) => {
onOptionsChange({
...options,
jsonData: {
...options.jsonData,
backendType: option.value,
},
});
}}
/>
}
tooltip="Select what type of backend you use. This datasource supports both Phlare and Pyroscope backends."
/>
</div>
</div>
{mismatchedBackendType && (
<Alert
title={`"${options.jsonData.backendType}" option is selected but it seems like you are using "${mismatchedBackendType}" backend.`}
severity="warning"
/>
)}
</div>
</>
);
};
const backendTypeOptions: Record<BackendType, SelectableValue<BackendType>> = {
phlare: {
label: 'Phlare',
value: 'phlare',
},
pyroscope: {
label: 'Pyroscope',
value: 'pyroscope',
},
};

View File

@ -69,12 +69,7 @@ describe('VariableQueryEditor', () => {
});
function getMockDatasource() {
const ds = new PhlareDataSource(
{
jsonData: { backendType: 'phlare' },
} as DataSourceInstanceSettings<PhlareDataSourceOptions>,
new TemplateSrv()
);
const ds = new PhlareDataSource({} as DataSourceInstanceSettings<PhlareDataSourceOptions>, new TemplateSrv());
ds.getResource = jest.fn();
(ds.getResource as jest.Mock).mockImplementation(async (type: string) => {
if (type === 'profileTypes') {

View File

@ -133,19 +133,13 @@ function ProfileTypeRow(props: {
initialValue?: string;
}) {
const profileTypes = useProfileTypes(props.datasource);
const label = props.datasource.backendType === 'phlare' ? 'Profile type' : 'Application';
return (
<InlineFieldRow>
<InlineField
label={label}
aria-label={label}
label={'Profile type'}
aria-label={'Profile type'}
labelWidth={20}
tooltip={
<div>
Select {props.datasource.backendType === 'phlare' ? 'profile type' : 'application'} for which to retrieve
available labels
</div>
}
tooltip={<div>Select profile type for which to retrieve available labels</div>}
>
{profileTypes ? (
<ProfileTypesCascader

View File

@ -15,17 +15,14 @@ import { extractLabelMatchers, toPromLikeExpr } from '../prometheus/language_uti
import { VariableSupport } from './VariableSupport';
import { defaultGrafanaPyroscope, defaultPhlareQueryType } from './dataquery.gen';
import { PhlareDataSourceOptions, Query, ProfileTypeMessage, BackendType } from './types';
import { PhlareDataSourceOptions, Query, ProfileTypeMessage } from './types';
export class PhlareDataSource extends DataSourceWithBackend<Query, PhlareDataSourceOptions> {
backendType: BackendType;
constructor(
instanceSettings: DataSourceInstanceSettings<PhlareDataSourceOptions>,
private readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super(instanceSettings);
this.backendType = instanceSettings.jsonData.backendType ?? 'phlare';
this.variables = new VariableSupport(this);
}
@ -68,11 +65,6 @@ export class PhlareDataSource extends DataSourceWithBackend<Query, PhlareDataSou
});
}
// We need the URL here because it may not be saved on the backend yet when used from config page.
async getBackendType(url: string): Promise<{ backendType: BackendType | 'unknown' }> {
return await super.getResource('backendType', { url });
}
applyTemplateVariables(query: Query, scopedVars: ScopedVars): Query {
return {
...query,

View File

@ -13,7 +13,7 @@
"backend": true,
"info": {
"description": "Supports Phlare and Pyroscope backends, horizontally-scalable, highly-available, multi-tenant continuous profiling aggregation systems.",
"description": "Data source for Grafana Pyroscope, horizontally-scalable, highly-available, multi-tenant continuous profiling aggregation system.",
"author": {
"name": "Grafana Labs",
"url": "https://www.grafana.com"

View File

@ -16,11 +16,8 @@ export interface ProfileTypeMessage {
*/
export interface PhlareDataSourceOptions extends DataSourceJsonData {
minStep?: string;
backendType?: BackendType; // if not set we assume it's phlare
}
export type BackendType = 'phlare' | 'pyroscope';
export type ProfileTypeQuery = {
type: 'profileType';
refId: string;