diff --git a/docs/plugins/assets/jaeger-service-performance-monitoring.png b/docs/plugins/assets/jaeger-service-performance-monitoring.png new file mode 100644 index 000000000..cd53958d1 Binary files /dev/null and b/docs/plugins/assets/jaeger-service-performance-monitoring.png differ diff --git a/docs/plugins/azure.md b/docs/plugins/azure.md index 0a95edfac..491801bf0 100644 --- a/docs/plugins/azure.md +++ b/docs/plugins/azure.md @@ -171,7 +171,7 @@ The `*` value is a special value, which allows access to all resources, resource - `verbs`: `delete`, `get`, `post` and `put` !!! note - You have to set the `permissionsEnabled` property in the configuration to `true` and you must enable [authentication](../getting-started/configuration/authentication.md) for kobs to use this feature. + You have to set the `permissionsEnabled` property in the configuration to `true` and you must enable [authentication](../getting-started/configuration/auth.md) for kobs to use this feature. ### Metrics diff --git a/docs/plugins/jaeger.md b/docs/plugins/jaeger.md index e70a7650a..0452491c3 100644 --- a/docs/plugins/jaeger.md +++ b/docs/plugins/jaeger.md @@ -52,7 +52,8 @@ The following options can be used for a panel with the Jaeger plugin: | Field | Type | Description | Required | | ----- | ---- | ----------- | -------- | | showChart | boolean | If this is `true` the chart with the traces will be shown. | No | -| queries | [[]Query](#query) | A list of Jaeger queries, which can be selected by the user. | Yes | +| queries | [[]Query](#query) | A list of Jaeger queries, which can be selected by the user. | No | +| metrics | [Metrics](#metrics) | The configuration to show the metrics for the Service Performance Monitoring. | No | ### Query @@ -66,6 +67,14 @@ The following options can be used for a panel with the Jaeger plugin: | operation | string | An optional operation to retrieve traces for. | No | | tags | string | Tags, which the traces must be contain. | No | +### Metrics + +| Field | Type | Description | Required | +| ----- | ---- | ----------- | -------- | +| type | string | The metrics type which should be displayed. Must be one of the following: `servicelatency`, `serviceerrors`, `servicecalls` or `operations`. | Yes | +| service | string | The service for which the selected metrics should be displayed. | Yes | +| spanKinds | string | A list of span kinds for which the selected metrics should be displayed. By default it includes all span kinds: `unspecified`, `internal`, `server`, `client`, `producer` and `consumer`. | No | + ## Notification Options !!! note @@ -107,3 +116,59 @@ spec: ``` ![Dashboard](assets/jaeger-dashboard.png) + +```yaml +--- +apiVersion: kobs.io/v1 +kind: Application +metadata: + name: satellite + namespace: kobs +spec: + dashboards: + - title: Service Performance Monitoring + inline: + rows: + - size: 3 + panels: + - title: Latency (ms) + colSpan: 4 + plugin: + name: jaeger + type: jaeger + options: + metrics: + type: servicelatency + service: satellite + - title: Errors (%) + colSpan: 4 + plugin: + name: jaeger + type: jaeger + options: + metrics: + type: serviceerrors + service: satellite + - title: Request Rate (req/s) + colSpan: 4 + plugin: + name: jaeger + type: jaeger + options: + metrics: + type: servicecalls + service: satellite + - size: -1 + panels: + - title: Operations + colSpan: 4 + plugin: + name: jaeger + type: jaeger + options: + metrics: + type: operations + service: satellite +``` + +![Service Performance Monitoring](assets/jaeger-service-performance-monitoring.png) diff --git a/plugins/plugin-jaeger/cmd/jaeger.go b/plugins/plugin-jaeger/cmd/jaeger.go index ac69e28bc..f7e5db21c 100644 --- a/plugins/plugin-jaeger/cmd/jaeger.go +++ b/plugins/plugin-jaeger/cmd/jaeger.go @@ -158,6 +158,7 @@ func (router *Router) getMetrics(w http.ResponseWriter, r *http.Request) { name := r.Header.Get("x-kobs-plugin") metric := r.URL.Query().Get("metric") service := r.URL.Query().Get("service") + spanKinds := r.URL.Query()["spanKind"] groupByOperation := r.URL.Query().Get("groupByOperation") quantile := r.URL.Query().Get("quantile") ratePer := r.URL.Query().Get("ratePer") @@ -165,7 +166,7 @@ func (router *Router) getMetrics(w http.ResponseWriter, r *http.Request) { timeEnd := r.URL.Query().Get("timeEnd") timeStart := r.URL.Query().Get("timeStart") - log.Debug(r.Context(), "Get metrics parameters", zap.String("name", name), zap.String("metric", metric), zap.String("service", service), zap.String("groupByOperation", groupByOperation), zap.String("quantile", quantile), zap.String("ratePer", ratePer), zap.String("step", step), zap.String("timeEnd", timeEnd), zap.String("timeStart", timeStart)) + log.Debug(r.Context(), "Get metrics parameters", zap.String("name", name), zap.String("metric", metric), zap.String("service", service), zap.Strings("spanKinds", spanKinds), zap.String("groupByOperation", groupByOperation), zap.String("quantile", quantile), zap.String("ratePer", ratePer), zap.String("step", step), zap.String("timeEnd", timeEnd), zap.String("timeStart", timeStart)) i := router.getInstance(name) if i == nil { @@ -188,7 +189,7 @@ func (router *Router) getMetrics(w http.ResponseWriter, r *http.Request) { return } - body, err := i.GetMetrics(r.Context(), metric, service, groupByOperation, quantile, ratePer, step, parsedTimeStart, parsedTimeEnd) + body, err := i.GetMetrics(r.Context(), metric, service, groupByOperation, quantile, ratePer, step, spanKinds, parsedTimeStart, parsedTimeEnd) if err != nil { log.Error(r.Context(), "Could not get metrics", zap.Error(err)) errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get metrics") diff --git a/plugins/plugin-jaeger/pkg/instance/instance.go b/plugins/plugin-jaeger/pkg/instance/instance.go index 1b2434c47..594c22241 100644 --- a/plugins/plugin-jaeger/pkg/instance/instance.go +++ b/plugins/plugin-jaeger/pkg/instance/instance.go @@ -38,7 +38,7 @@ type Instance interface { GetOperations(ctx context.Context, service string) (map[string]any, error) GetTraces(ctx context.Context, limit, maxDuration, minDuration, operation, service, tags string, timeStart, timeEnd int64) (map[string]any, error) GetTrace(ctx context.Context, traceID string) (map[string]any, error) - GetMetrics(ctx context.Context, metric, service, groupByOperation, quantile, ratePer, step string, timeStart, timeEnd int64) (map[string]any, error) + GetMetrics(ctx context.Context, metric, service, groupByOperation, quantile, ratePer, step string, spanKinds []string, timeStart, timeEnd int64) (map[string]any, error) } type instance struct { @@ -112,12 +112,17 @@ func (i *instance) GetTrace(ctx context.Context, traceID string) (map[string]any return i.doRequest(ctx, fmt.Sprintf("/api/traces/%s", traceID)) } -func (i *instance) GetMetrics(ctx context.Context, metric, service, groupByOperation, quantile, ratePer, step string, timeStart, timeEnd int64) (map[string]any, error) { +func (i *instance) GetMetrics(ctx context.Context, metric, service, groupByOperation, quantile, ratePer, step string, spanKinds []string, timeStart, timeEnd int64) (map[string]any, error) { timeStart = timeStart * 1000 timeEnd = timeEnd * 1000 lookback := timeEnd - timeStart - return i.doRequest(ctx, fmt.Sprintf("/api/metrics/%s?service=%s&endTs=%d&lookback=%d&groupByOperation=%s&quantile=%s&ratePer=%s&step=%s&spanKind=unspecified&spanKind=internal&spanKind=server&spanKind=client&spanKind=producer&spanKind=consumer", metric, service, timeEnd, lookback, groupByOperation, quantile, ratePer, step)) + var spanKindsParameters string + for _, spanKind := range spanKinds { + spanKindsParameters = fmt.Sprintf("%s&spanKind=%s", spanKindsParameters, spanKind) + } + + return i.doRequest(ctx, fmt.Sprintf("/api/metrics/%s?service=%s&endTs=%d&lookback=%d&groupByOperation=%s&quantile=%s&ratePer=%s&step=%s%s", metric, service, timeEnd, lookback, groupByOperation, quantile, ratePer, step, spanKindsParameters)) } // New returns a new Jaeger instance for the given configuration. diff --git a/plugins/plugin-jaeger/pkg/instance/instance_mock.go b/plugins/plugin-jaeger/pkg/instance/instance_mock.go index bb3ec7796..8ba055ca3 100644 --- a/plugins/plugin-jaeger/pkg/instance/instance_mock.go +++ b/plugins/plugin-jaeger/pkg/instance/instance_mock.go @@ -13,13 +13,13 @@ type MockInstance struct { mock.Mock } -// GetMetrics provides a mock function with given fields: ctx, metric, service, groupByOperation, quantile, ratePer, step, timeEnd, timeStart -func (_m *MockInstance) GetMetrics(ctx context.Context, metric string, service string, groupByOperation string, quantile string, ratePer string, step string, timeEnd int64, timeStart int64) (map[string]interface{}, error) { - ret := _m.Called(ctx, metric, service, groupByOperation, quantile, ratePer, step, timeEnd, timeStart) +// GetMetrics provides a mock function with given fields: ctx, metric, service, groupByOperation, quantile, ratePer, step, spanKinds, timeStart, timeEnd +func (_m *MockInstance) GetMetrics(ctx context.Context, metric string, service string, groupByOperation string, quantile string, ratePer string, step string, spanKinds []string, timeStart int64, timeEnd int64) (map[string]interface{}, error) { + ret := _m.Called(ctx, metric, service, groupByOperation, quantile, ratePer, step, spanKinds, timeStart, timeEnd) var r0 map[string]interface{} - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string, int64, int64) map[string]interface{}); ok { - r0 = rf(ctx, metric, service, groupByOperation, quantile, ratePer, step, timeEnd, timeStart) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string, []string, int64, int64) map[string]interface{}); ok { + r0 = rf(ctx, metric, service, groupByOperation, quantile, ratePer, step, spanKinds, timeStart, timeEnd) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(map[string]interface{}) @@ -27,8 +27,8 @@ func (_m *MockInstance) GetMetrics(ctx context.Context, metric string, service s } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, string, string, int64, int64) error); ok { - r1 = rf(ctx, metric, service, groupByOperation, quantile, ratePer, step, timeEnd, timeStart) + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, string, string, []string, int64, int64) error); ok { + r1 = rf(ctx, metric, service, groupByOperation, quantile, ratePer, step, spanKinds, timeStart, timeEnd) } else { r1 = ret.Error(1) } diff --git a/plugins/plugin-jaeger/src/components/page/Monitor.tsx b/plugins/plugin-jaeger/src/components/page/Monitor.tsx index 75ff6fceb..e6bf85992 100644 --- a/plugins/plugin-jaeger/src/components/page/Monitor.tsx +++ b/plugins/plugin-jaeger/src/components/page/Monitor.tsx @@ -24,10 +24,12 @@ const Monitor: React.FunctionComponent = ({ instance }: IMonitorP const [details, setDetails] = useState(undefined); const changeOptions = (opts: IMonitorOptions): void => { + const spanKinds = opts.spanKinds.map((spanKind) => `&spanKind=${spanKind}`); + navigate( - `${location.pathname}?service=${encodeURIComponent(opts.service)}&time=${opts.times.time}&timeEnd=${ - opts.times.timeEnd - }&timeStart=${opts.times.timeStart}`, + `${location.pathname}?service=${encodeURIComponent(opts.service)}${ + spanKinds.length > 0 ? spanKinds.join('') : '' + }&time=${opts.times.time}&timeEnd=${opts.times.timeEnd}&timeStart=${opts.times.timeStart}`, ); }; @@ -58,13 +60,14 @@ const Monitor: React.FunctionComponent = ({ instance }: IMonitorP toolbarContent={} panelContent={details} > - {options.service ? ( + {options.service && options.spanKinds.length > 0 ? ( @@ -73,6 +76,7 @@ const Monitor: React.FunctionComponent = ({ instance }: IMonitorP title="Error Rate (%)" instance={instance} service={options.service} + spanKinds={options.spanKinds} times={options.times} /> @@ -81,6 +85,7 @@ const Monitor: React.FunctionComponent = ({ instance }: IMonitorP title="Request Rate (req/s)" instance={instance} service={options.service} + spanKinds={options.spanKinds} times={options.times} /> @@ -89,6 +94,7 @@ const Monitor: React.FunctionComponent = ({ instance }: IMonitorP title="Operations" instance={instance} service={options.service} + spanKinds={options.spanKinds} times={options.times} setDetails={setDetails} /> diff --git a/plugins/plugin-jaeger/src/components/page/MonitorToolbar.tsx b/plugins/plugin-jaeger/src/components/page/MonitorToolbar.tsx index 14c57d017..dd3994e5e 100644 --- a/plugins/plugin-jaeger/src/components/page/MonitorToolbar.tsx +++ b/plugins/plugin-jaeger/src/components/page/MonitorToolbar.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { IOptionsAdditionalFields, IPluginInstance, ITimes, Options, Toolbar, ToolbarItem } from '@kobsio/shared'; import { IMonitorOptions } from '../../utils/interfaces'; +import MonitorToolbarSpanKinds from './MonitorToolbarSpanKinds'; import TracesToolbarServices from './TracesToolbarServices'; interface IMonitorToolbarProps { @@ -16,10 +17,28 @@ const MonitorToolbar: React.FunctionComponent = ({ setOptions, }: IMonitorToolbarProps) => { const [service, setService] = useState(options.service); + const [spanKinds, setSpanKinds] = useState(options.spanKinds); + + const selectSpanKind = (spanKind: string): void => { + if (spanKind === '') { + setSpanKinds([]); + } else { + if (spanKind) { + if (spanKinds.includes(spanKind)) { + setSpanKinds(spanKinds.filter((item) => item !== spanKind)); + } else { + setSpanKinds([...spanKinds, spanKind]); + } + } else { + setSpanKinds([spanKind]); + } + } + }; const changeOptions = (times: ITimes, additionalFields: IOptionsAdditionalFields[] | undefined): void => { setOptions({ service: service, + spanKinds: spanKinds, times: times, }); }; @@ -29,6 +48,9 @@ const MonitorToolbar: React.FunctionComponent = ({ setService(value)} /> + + selectSpanKind(value)} /> + diff --git a/plugins/plugin-jaeger/src/components/page/MonitorToolbarSpanKinds.tsx b/plugins/plugin-jaeger/src/components/page/MonitorToolbarSpanKinds.tsx new file mode 100644 index 000000000..a7a76bc98 --- /dev/null +++ b/plugins/plugin-jaeger/src/components/page/MonitorToolbarSpanKinds.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; + +interface IMonitorToolbarSpanKindsProps { + spanKinds: string[]; + setSpanKind: (spanKind: string) => void; +} + +const MonitorToolbarSpanKinds: React.FunctionComponent = ({ + spanKinds, + setSpanKind, +}: IMonitorToolbarSpanKindsProps) => { + const [show, setShow] = useState(false); + + return ( + + ); +}; + +export default MonitorToolbarSpanKinds; diff --git a/plugins/plugin-jaeger/src/components/panel/MonitorOperations.tsx b/plugins/plugin-jaeger/src/components/panel/MonitorOperations.tsx index 8bc3cce14..a76952db8 100644 --- a/plugins/plugin-jaeger/src/components/panel/MonitorOperations.tsx +++ b/plugins/plugin-jaeger/src/components/panel/MonitorOperations.tsx @@ -10,6 +10,7 @@ interface IMonitorOperationsProps { description?: string; instance: IPluginInstance; service: string; + spanKinds: string[]; times: ITimes; setDetails?: (details: React.ReactNode) => void; } @@ -18,11 +19,12 @@ const MonitorOperations: React.FunctionComponent = ({ title, description, instance, - times, service, + spanKinds, + times, setDetails, }: IMonitorOperationsProps) => { - const metrics = useGetOperationMetrics(instance, service, times); + const metrics = useGetOperationMetrics(instance, service, spanKinds, times); if ( metrics[0].isLoading || diff --git a/plugins/plugin-jaeger/src/components/panel/MonitorServiceCalls.tsx b/plugins/plugin-jaeger/src/components/panel/MonitorServiceCalls.tsx index 0fd1f4169..8a5f76772 100644 --- a/plugins/plugin-jaeger/src/components/panel/MonitorServiceCalls.tsx +++ b/plugins/plugin-jaeger/src/components/panel/MonitorServiceCalls.tsx @@ -12,6 +12,7 @@ interface IMonitorServiceCallsProps { description?: string; instance: IPluginInstance; service: string; + spanKinds: string[]; times: ITimes; } @@ -19,15 +20,20 @@ const MonitorServiceCalls: React.FunctionComponent = title, description, instance, - times, service, + spanKinds, + times, }: IMonitorServiceCallsProps) => { const { isError, isLoading, error, data } = useQuery( - ['jaeger/metrics/calls/service', instance, service, times], + ['jaeger/metrics/calls/service', instance, service, spanKinds, times], async () => { try { + const sk = spanKinds.map((spanKind) => `&spanKind=${spanKind}`); + const response = await fetch( - `/api/plugins/jaeger/metrics?metric=calls&service=${service}&groupByOperation=false&ratePer=600000&step=60000&timeStart=${times.timeStart}&timeEnd=${times.timeEnd}`, + `/api/plugins/jaeger/metrics?metric=calls&service=${service}${ + sk.length > 0 ? sk.join('') : '' + }&groupByOperation=false&ratePer=600000&step=60000&timeStart=${times.timeStart}&timeEnd=${times.timeEnd}`, { headers: { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/plugins/plugin-jaeger/src/components/panel/MonitorServiceErrors.tsx b/plugins/plugin-jaeger/src/components/panel/MonitorServiceErrors.tsx index c5cb95870..dd62a4b49 100644 --- a/plugins/plugin-jaeger/src/components/panel/MonitorServiceErrors.tsx +++ b/plugins/plugin-jaeger/src/components/panel/MonitorServiceErrors.tsx @@ -12,6 +12,7 @@ interface IMonitorServiceErrorsProps { description?: string; instance: IPluginInstance; service: string; + spanKinds: string[]; times: ITimes; } @@ -19,15 +20,20 @@ const MonitorServiceErrors: React.FunctionComponent title, description, instance, - times, service, + spanKinds, + times, }: IMonitorServiceErrorsProps) => { const { isError, isLoading, error, data } = useQuery( - ['jaeger/metrics/errors/service', instance, service, times], + ['jaeger/metrics/errors/service', instance, service, spanKinds, times], async () => { try { + const sk = spanKinds.map((spanKind) => `&spanKind=${spanKind}`); + const response = await fetch( - `/api/plugins/jaeger/metrics?metric=errors&service=${service}&groupByOperation=false&ratePer=600000&step=60000&timeStart=${times.timeStart}&timeEnd=${times.timeEnd}`, + `/api/plugins/jaeger/metrics?metric=errors&service=${service}${ + sk.length > 0 ? sk.join('') : '' + }&groupByOperation=false&ratePer=600000&step=60000&timeStart=${times.timeStart}&timeEnd=${times.timeEnd}`, { headers: { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/plugins/plugin-jaeger/src/components/panel/MonitorServiceLatency.tsx b/plugins/plugin-jaeger/src/components/panel/MonitorServiceLatency.tsx index 6860be092..775ae8664 100644 --- a/plugins/plugin-jaeger/src/components/panel/MonitorServiceLatency.tsx +++ b/plugins/plugin-jaeger/src/components/panel/MonitorServiceLatency.tsx @@ -10,6 +10,7 @@ interface IMonitorServiceLatencyProps { description?: string; instance: IPluginInstance; service: string; + spanKinds: string[]; times: ITimes; } @@ -17,10 +18,11 @@ const MonitorServiceLatency: React.FunctionComponent { - const latencies = useGetServiceLatency(instance, service, times); + const latencies = useGetServiceLatency(instance, service, spanKinds, times); return ( diff --git a/plugins/plugin-jaeger/src/components/panel/Panel.tsx b/plugins/plugin-jaeger/src/components/panel/Panel.tsx index e39938195..21065a0f6 100644 --- a/plugins/plugin-jaeger/src/components/panel/Panel.tsx +++ b/plugins/plugin-jaeger/src/components/panel/Panel.tsx @@ -2,6 +2,10 @@ import React from 'react'; import { IPluginPanelProps, PluginPanelError } from '@kobsio/shared'; import { IPanelOptions } from '../../utils/interfaces'; +import MonitorOperations from './MonitorOperations'; +import MonitorServiceCalls from './MonitorServiceCalls'; +import MonitorServiceErrors from './MonitorServiceErrors'; +import MonitorServiceLatency from './MonitorServiceLatency'; import TracesWrapper from './TracesWrapper'; interface IJaegerPluginPanelProps extends IPluginPanelProps { @@ -30,6 +34,66 @@ const Panel: React.FunctionComponent = ({ ); } + if (options && options.metrics && options.metrics.type && options.metrics.service && times) { + const spanKinds = + options.metrics.spanKinds && Array.isArray(options.metrics.spanKinds) && options.metrics.spanKinds.length > 0 + ? options.metrics.spanKinds + : ['unspecified', 'internal', 'server', 'client', 'producer', 'consumer']; + + if (options.metrics.type === 'servicelatency') { + return ( + + ); + } + + if (options.metrics.type === 'serviceerrors') { + return ( + + ); + } + + if (options.metrics.type === 'servicecalls') { + return ( + + ); + } + + if (options.metrics.type === 'operations') { + return ( + + ); + } + } + return ( { export const getInitialMonitorOptions = (search: string, isInitial: boolean): IMonitorOptions => { const params = new URLSearchParams(search); const service = params.get('service'); + const spanKinds = params.getAll('spanKind'); return { service: service ? service : '', + spanKinds: + spanKinds.length > 0 ? spanKinds : ['unspecified', 'internal', 'server', 'client', 'producer', 'consumer'], times: getTimeParams(params, isInitial), }; }; @@ -295,16 +298,20 @@ export const getInitialMonitorOptions = (search: string, isInitial: boolean): IM export const useGetServiceLatency = ( instance: IPluginInstance, service: string, + spanKinds: string[], times: ITimes, ): [UseQueryResult, UseQueryResult, UseQueryResult] => { - const p50 = useQuery(['jaeger/metrics/latencies/service', instance, service, times, 0.5], () => - getServiceLatency(instance, service, times, 0.5), + const p50 = useQuery( + ['jaeger/metrics/latencies/service', instance, service, spanKinds, times, 0.5], + () => getServiceLatency(instance, service, spanKinds, times, 0.5), ); - const p75 = useQuery(['jaeger/metrics/latencies/service', instance, service, times, 0.75], () => - getServiceLatency(instance, service, times, 0.75), + const p75 = useQuery( + ['jaeger/metrics/latencies/service', instance, service, spanKinds, times, 0.75], + () => getServiceLatency(instance, service, spanKinds, times, 0.75), ); - const p95 = useQuery(['jaeger/metrics/latencies/service', instance, service, times, 0.95], () => - getServiceLatency(instance, service, times, 0.95), + const p95 = useQuery( + ['jaeger/metrics/latencies/service', instance, service, spanKinds, times, 0.95], + () => getServiceLatency(instance, service, spanKinds, times, 0.95), ); return [p50, p75, p95]; }; @@ -312,11 +319,18 @@ export const useGetServiceLatency = ( const getServiceLatency = async ( instance: IPluginInstance, service: string, + spanKinds: string[], times: ITimes, quantile: number, ): Promise => { + const sk = spanKinds.map((spanKind) => `&spanKind=${spanKind}`); + const response = await fetch( - `/api/plugins/jaeger/metrics?metric=latencies&service=${service}&quantile=${quantile}&groupByOperation=false&ratePer=600000&step=60000&timeStart=${times.timeStart}&timeEnd=${times.timeEnd}`, + `/api/plugins/jaeger/metrics?metric=latencies&service=${service}${ + sk.length > 0 ? sk.join('') : '' + }&quantile=${quantile}&groupByOperation=false&ratePer=600000&step=60000&timeStart=${times.timeStart}&timeEnd=${ + times.timeEnd + }`, { headers: { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -352,7 +366,12 @@ export const serviceMetricsToChartData = (metrics: { name: string; metrics?: IMe for (const metricPoint of metric.metrics.metrics[0].metricPoints) { data.push({ x: new Date(metricPoint.timestamp), - y: typeof metricPoint.gaugeValue.doubleValue === 'number' ? metricPoint.gaugeValue.doubleValue : null, + y: + typeof metricPoint.gaugeValue.doubleValue === 'number' + ? metric.name === 'Errors' + ? metricPoint.gaugeValue.doubleValue * 100 + : metricPoint.gaugeValue.doubleValue + : null, }); } @@ -368,6 +387,7 @@ export const serviceMetricsToChartData = (metrics: { name: string; metrics?: IMe export const useGetOperationMetrics = ( instance: IPluginInstance, service: string, + spanKinds: string[], times: ITimes, ): [ UseQueryResult, @@ -376,20 +396,25 @@ export const useGetOperationMetrics = ( UseQueryResult, UseQueryResult, ] => { - const p50 = useQuery(['jaeger/metrics/latencies/operations', instance, service, times, 0.5], () => - getOperationMetrics(instance, service, times, 'latencies', 0.5), + const p50 = useQuery( + ['jaeger/metrics/latencies/operations', instance, service, spanKinds, times, 0.5], + () => getOperationMetrics(instance, service, spanKinds, times, 'latencies', 0.5), ); - const p75 = useQuery(['jaeger/metrics/latencies/operations', instance, service, times, 0.75], () => - getOperationMetrics(instance, service, times, 'latencies', 0.75), + const p75 = useQuery( + ['jaeger/metrics/latencies/operations', instance, service, spanKinds, times, 0.75], + () => getOperationMetrics(instance, service, spanKinds, times, 'latencies', 0.75), ); - const p95 = useQuery(['jaeger/metrics/latencies/operations', instance, service, times, 0.95], () => - getOperationMetrics(instance, service, times, 'latencies', 0.95), + const p95 = useQuery( + ['jaeger/metrics/latencies/operations', instance, service, spanKinds, times, 0.95], + () => getOperationMetrics(instance, service, spanKinds, times, 'latencies', 0.95), ); - const errors = useQuery(['jaeger/metrics/errors/operations', instance, service, times], () => - getOperationMetrics(instance, service, times, 'errors'), + const errors = useQuery( + ['jaeger/metrics/errors/operations', instance, service, spanKinds, times], + () => getOperationMetrics(instance, service, spanKinds, times, 'errors'), ); - const calls = useQuery(['jaeger/metrics/calls/operations', instance, service, times], () => - getOperationMetrics(instance, service, times, 'calls'), + const calls = useQuery( + ['jaeger/metrics/calls/operations', instance, service, spanKinds, times], + () => getOperationMetrics(instance, service, spanKinds, times, 'calls'), ); return [p50, p75, p95, errors, calls]; }; @@ -397,12 +422,15 @@ export const useGetOperationMetrics = ( const getOperationMetrics = async ( instance: IPluginInstance, service: string, + spanKinds: string[], times: ITimes, metric: string, quantile?: number, ): Promise => { + const sk = spanKinds.map((spanKind) => `&spanKind=${spanKind}`); + const response = await fetch( - `/api/plugins/jaeger/metrics?metric=${metric}&service=${service}${ + `/api/plugins/jaeger/metrics?metric=${metric}&service=${service}${sk.length > 0 ? sk.join('') : ''}${ quantile ? `&quantile=${quantile}` : '' }&groupByOperation=true&ratePer=600000&step=60000&timeStart=${times.timeStart}&timeEnd=${times.timeEnd}`, { @@ -452,16 +480,24 @@ export const operationMetricsToData = (metrics: { name: string; metrics?: IMetri if (typeof metricPoint.gaugeValue.doubleValue === 'number') { count = count + 1; total = total + metricPoint.gaugeValue.doubleValue; + } else if (metric.name === 'Errors') { + count = count + 1; } data.push({ x: new Date(metricPoint.timestamp), - y: typeof metricPoint.gaugeValue.doubleValue === 'number' ? metricPoint.gaugeValue.doubleValue : null, + y: + typeof metricPoint.gaugeValue.doubleValue === 'number' + ? metric.name === 'Errors' + ? metricPoint.gaugeValue.doubleValue * 100 + : metricPoint.gaugeValue.doubleValue + : null, }); } if (operationName in operations) { - operations[operationName].avgs[i] = roundNumber(total / count); + operations[operationName].avgs[i] = + metric.name === 'Errors' ? roundNumber((total / count) * 100) : roundNumber(total / count); operations[operationName].chartData[i] = { data: data, name: metric.name }; } else { const avgs: number[] = [0, 0, 0, 0, 0]; @@ -473,7 +509,7 @@ export const operationMetricsToData = (metrics: { name: string; metrics?: IMetri { data: [], name: 'Calls' }, ]; - avgs[i] = roundNumber(total / count); + avgs[i] = metric.name === 'Errors' ? roundNumber((total / count) * 100) : roundNumber(total / count); chartData[i] = { data: data, name: metric.name }; if (i === 0) diff --git a/plugins/plugin-jaeger/src/utils/interfaces.ts b/plugins/plugin-jaeger/src/utils/interfaces.ts index 92e87bf5d..d01e8fd94 100644 --- a/plugins/plugin-jaeger/src/utils/interfaces.ts +++ b/plugins/plugin-jaeger/src/utils/interfaces.ts @@ -18,6 +18,11 @@ export interface IOptions { export interface IPanelOptions { queries?: IQuery[]; showChart?: boolean; + metrics?: { + type: string; + service?: string; + spanKinds?: string[]; + }; } export interface IQuery { @@ -123,6 +128,7 @@ export interface IDeduplicateTags { // IMonitorOptions is the interface for all options, which can be set for Jaeger to get a the metrics for a service. export interface IMonitorOptions { service: string; + spanKinds: string[]; times: ITimes; }