From 3838b854bed3cb2d852a21ba719736bb34050392 Mon Sep 17 00:00:00 2001 From: Eduardo Aleixo Date: Tue, 11 Apr 2023 10:47:49 +0100 Subject: [PATCH 1/5] feat: make tags work in the ui --- pkg/phlare/modules.go | 1 + pkg/querier/http.go | 26 +++++++++++++++++++++++++- public/app/overrides/services/tags.ts | 24 +++++++++++++++++++++--- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/pkg/phlare/modules.go b/pkg/phlare/modules.go index 51abd3775..2dcd0baa1 100644 --- a/pkg/phlare/modules.go +++ b/pkg/phlare/modules.go @@ -211,6 +211,7 @@ func (f *Phlare) initQuerier() (services.Service, error) { f.Server.HTTP.Handle("/pyroscope/render", util.AuthenticateUser(f.Cfg.MultitenancyEnabled).Wrap(http.HandlerFunc(querierSvc.RenderHandler))) f.Server.HTTP.Handle("/pyroscope/label-values", util.AuthenticateUser(f.Cfg.MultitenancyEnabled).Wrap(http.HandlerFunc(querierSvc.LabelValuesHandler))) + f.Server.HTTP.Handle("/pyroscope/labels", util.AuthenticateUser(f.Cfg.MultitenancyEnabled).Wrap(http.HandlerFunc(querierSvc.LabelNamesHandler))) sm, err := services.NewManager(querierSvc, worker) if err != nil { diff --git a/pkg/querier/http.go b/pkg/querier/http.go index 9cfaabc07..171a65b33 100644 --- a/pkg/querier/http.go +++ b/pkg/querier/http.go @@ -19,6 +19,28 @@ import ( phlaremodel "github.com/grafana/phlare/pkg/model" ) +// LabelHandler returns all labels +// /labels +func (q *Querier) LabelNamesHandler(w http.ResponseWriter, req *http.Request) { + var ( + res []string + err error + ) + + response, err := q.LabelNames(req.Context(), connect.NewRequest(&querierv1.LabelNamesRequest{})) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + res = response.Msg.GetNames() + + w.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(res); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + // LabelValuesHandler only returns the label values for the given label name. // This is mostly for fulfilling the pyroscope API and won't be used in the future. // /label-values?label=__name__ @@ -43,7 +65,9 @@ func (q *Querier) LabelValuesHandler(w http.ResponseWriter, req *http.Request) { res = append(res, t.ID) } } else { - response, err := q.LabelValues(req.Context(), connect.NewRequest(&querierv1.LabelValuesRequest{})) + response, err := q.LabelValues(req.Context(), connect.NewRequest(&querierv1.LabelValuesRequest{ + Name: label, + })) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/public/app/overrides/services/tags.ts b/public/app/overrides/services/tags.ts index cc589d9b5..1bbdb681d 100644 --- a/public/app/overrides/services/tags.ts +++ b/public/app/overrides/services/tags.ts @@ -1,7 +1,22 @@ import { Result } from '@webapp/util/fp'; +import { + Tags, + TagsValuesSchema, + TagsValues, + TagsSchema, +} from '@webapp/models/tags'; +import { parseResponse, request } from '@webapp/services/base'; export async function fetchTags(query: string, from: number, until: number) { - return Result.ok([]); + const response = await request('/pyroscope/labels'); + const isMetaTag = (tag: string) => tag.startsWith('__') && tag.endsWith('__'); + + return parseResponse( + response, + TagsSchema.transform((tags) => { + return tags.filter((t) => !isMetaTag(t)); + }) + ); } export async function fetchLabelValues( @@ -10,7 +25,10 @@ export async function fetchLabelValues( from: number, until: number ) { - return Result.err({ - message: 'TODO: implement ', + const searchParams = new URLSearchParams({ + label, }); + const response = await request('/pyroscope/label-values?' + searchParams); + + return parseResponse(response, TagsValuesSchema); } From 04f3dae76ae5671ae2bd40250b4d2826d4bd8d51 Mon Sep 17 00:00:00 2001 From: Eduardo Aleixo Date: Tue, 11 Apr 2023 10:56:13 +0100 Subject: [PATCH 2/5] fix type check --- public/app/overrides/services/tags.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/app/overrides/services/tags.ts b/public/app/overrides/services/tags.ts index 1bbdb681d..3864ec718 100644 --- a/public/app/overrides/services/tags.ts +++ b/public/app/overrides/services/tags.ts @@ -1,4 +1,3 @@ -import { Result } from '@webapp/util/fp'; import { Tags, TagsValuesSchema, @@ -11,7 +10,7 @@ export async function fetchTags(query: string, from: number, until: number) { const response = await request('/pyroscope/labels'); const isMetaTag = (tag: string) => tag.startsWith('__') && tag.endsWith('__'); - return parseResponse( + return parseResponse( response, TagsSchema.transform((tags) => { return tags.filter((t) => !isMetaTag(t)); @@ -30,5 +29,5 @@ export async function fetchLabelValues( }); const response = await request('/pyroscope/label-values?' + searchParams); - return parseResponse(response, TagsValuesSchema); + return parseResponse(response, TagsValuesSchema); } From ad0eec4d4cf29dad3da9e3ac1bad669087ebdcbe Mon Sep 17 00:00:00 2001 From: Eduardo Aleixo Date: Thu, 13 Apr 2023 14:58:17 +0100 Subject: [PATCH 3/5] use series API to load labels --- public/app/overrides/services/tags.ts | 63 +++++++++++++++++++-------- scripts/webpack/webpack.dev.js | 1 + 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/public/app/overrides/services/tags.ts b/public/app/overrides/services/tags.ts index 3864ec718..075ae8a36 100644 --- a/public/app/overrides/services/tags.ts +++ b/public/app/overrides/services/tags.ts @@ -1,33 +1,62 @@ -import { - Tags, - TagsValuesSchema, - TagsValues, - TagsSchema, -} from '@webapp/models/tags'; import { parseResponse, request } from '@webapp/services/base'; +import { z } from 'zod'; -export async function fetchTags(query: string, from: number, until: number) { - const response = await request('/pyroscope/labels'); +const seriesLabelsSchema = z.object({ + labelsSet: z.array( + z.object({ + labels: z.array( + z.object({ + name: z.string(), + value: z.string(), + }) + ), + }) + ), +}); + +async function fetchLabelsSeries( + query: string, + transformFn: (t: { name: string; value: string }[]) => T +) { + const profileTypeID = query.replace(/\{.*/g, ''); + const response = await request('/querier.v1.QuerierService/Series', { + method: 'POST', + body: JSON.stringify({ + matchers: [`{__profile_type__=\"${profileTypeID}\"}`], + }), + headers: { + 'content-type': 'application/json', + }, + }); const isMetaTag = (tag: string) => tag.startsWith('__') && tag.endsWith('__'); - return parseResponse( + return parseResponse( response, - TagsSchema.transform((tags) => { - return tags.filter((t) => !isMetaTag(t)); - }) + seriesLabelsSchema + .transform((res) => { + return res.labelsSet + .flatMap((a) => a.labels) + .filter((a) => !isMetaTag(a.name)); + }) + .transform(transformFn) ); } +export async function fetchTags(query: string, from: number, until: number) { + return fetchLabelsSeries(query, function (t) { + const labelNames = t.map((a) => a.name); + return Array.from(new Set(labelNames)); + }); +} + export async function fetchLabelValues( label: string, query: string, from: number, until: number ) { - const searchParams = new URLSearchParams({ - label, + return fetchLabelsSeries(query, function (t) { + const labelValues = t.filter((l) => label === l.name).map((a) => a.value); + return Array.from(new Set(labelValues)); }); - const response = await request('/pyroscope/label-values?' + searchParams); - - return parseResponse(response, TagsValuesSchema); } diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index 777e8599d..7637bb58f 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -8,6 +8,7 @@ module.exports = merge(common, { port: 4040, proxy: { '/pyroscope': 'http://localhost:4100', + '/querier.v1.QuerierService': 'http://localhost:4100', }, }, optimization: { From 81a466fcb94aea751af122738a63f8160b76cf1c Mon Sep 17 00:00:00 2001 From: Eduardo Aleixo Date: Thu, 13 Apr 2023 15:06:55 +0100 Subject: [PATCH 4/5] revert backend impl since it's all done client side --- pkg/phlare/modules.go | 1 - pkg/querier/http.go | 26 +------------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/pkg/phlare/modules.go b/pkg/phlare/modules.go index 2dcd0baa1..51abd3775 100644 --- a/pkg/phlare/modules.go +++ b/pkg/phlare/modules.go @@ -211,7 +211,6 @@ func (f *Phlare) initQuerier() (services.Service, error) { f.Server.HTTP.Handle("/pyroscope/render", util.AuthenticateUser(f.Cfg.MultitenancyEnabled).Wrap(http.HandlerFunc(querierSvc.RenderHandler))) f.Server.HTTP.Handle("/pyroscope/label-values", util.AuthenticateUser(f.Cfg.MultitenancyEnabled).Wrap(http.HandlerFunc(querierSvc.LabelValuesHandler))) - f.Server.HTTP.Handle("/pyroscope/labels", util.AuthenticateUser(f.Cfg.MultitenancyEnabled).Wrap(http.HandlerFunc(querierSvc.LabelNamesHandler))) sm, err := services.NewManager(querierSvc, worker) if err != nil { diff --git a/pkg/querier/http.go b/pkg/querier/http.go index 171a65b33..9cfaabc07 100644 --- a/pkg/querier/http.go +++ b/pkg/querier/http.go @@ -19,28 +19,6 @@ import ( phlaremodel "github.com/grafana/phlare/pkg/model" ) -// LabelHandler returns all labels -// /labels -func (q *Querier) LabelNamesHandler(w http.ResponseWriter, req *http.Request) { - var ( - res []string - err error - ) - - response, err := q.LabelNames(req.Context(), connect.NewRequest(&querierv1.LabelNamesRequest{})) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - res = response.Msg.GetNames() - - w.Header().Add("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(res); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } -} - // LabelValuesHandler only returns the label values for the given label name. // This is mostly for fulfilling the pyroscope API and won't be used in the future. // /label-values?label=__name__ @@ -65,9 +43,7 @@ func (q *Querier) LabelValuesHandler(w http.ResponseWriter, req *http.Request) { res = append(res, t.ID) } } else { - response, err := q.LabelValues(req.Context(), connect.NewRequest(&querierv1.LabelValuesRequest{ - Name: label, - })) + response, err := q.LabelValues(req.Context(), connect.NewRequest(&querierv1.LabelValuesRequest{})) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return From 85fd81cbc013a74174b577d9f16b910f7c293f03 Mon Sep 17 00:00:00 2001 From: Eduardo Aleixo Date: Thu, 13 Apr 2023 16:32:29 +0100 Subject: [PATCH 5/5] don't fail if response doesn't have the correct shape --- public/app/overrides/services/tags.ts | 33 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/public/app/overrides/services/tags.ts b/public/app/overrides/services/tags.ts index 075ae8a36..4d124ffc5 100644 --- a/public/app/overrides/services/tags.ts +++ b/public/app/overrides/services/tags.ts @@ -1,18 +1,27 @@ import { parseResponse, request } from '@webapp/services/base'; import { z } from 'zod'; -const seriesLabelsSchema = z.object({ - labelsSet: z.array( - z.object({ - labels: z.array( - z.object({ - name: z.string(), - value: z.string(), - }) - ), - }) - ), -}); +const seriesLabelsSchema = z.preprocess( + (a: any) => { + if ('labelsSet' in a) { + return a; + } + + return { labelsSet: [{ labels: [] }] }; + }, + z.object({ + labelsSet: z.array( + z.object({ + labels: z.array( + z.object({ + name: z.string(), + value: z.string(), + }) + ), + }) + ), + }) +); async function fetchLabelsSeries( query: string,