mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Pyroscope: Remove support for old pyroscope (#74683)
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user