Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jaeger] Add Panels for Service Performance Monitoring #429

Merged
merged 1 commit into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/plugins/azure.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
67 changes: 66 additions & 1 deletion docs/plugins/jaeger.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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)
5 changes: 3 additions & 2 deletions plugins/plugin-jaeger/cmd/jaeger.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,15 @@ 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")
step := r.URL.Query().Get("step")
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 {
Expand All @@ -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")
Expand Down
11 changes: 8 additions & 3 deletions plugins/plugin-jaeger/pkg/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
14 changes: 7 additions & 7 deletions plugins/plugin-jaeger/pkg/instance/instance_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions plugins/plugin-jaeger/src/components/page/Monitor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ const Monitor: React.FunctionComponent<IMonitorProps> = ({ instance }: IMonitorP
const [details, setDetails] = useState<React.ReactNode>(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}`,
);
};

Expand Down Expand Up @@ -58,13 +60,14 @@ const Monitor: React.FunctionComponent<IMonitorProps> = ({ instance }: IMonitorP
toolbarContent={<MonitorToolbar instance={instance} options={options} setOptions={changeOptions} />}
panelContent={details}
>
{options.service ? (
{options.service && options.spanKinds.length > 0 ? (
<Grid hasGutter={true}>
<GridItem style={{ height: '300px' }} sm={12} md={12} lg={4} xl={4} xl2={4}>
<MonitorServiceLatency
title="Latency (ms)"
instance={instance}
service={options.service}
spanKinds={options.spanKinds}
times={options.times}
/>
</GridItem>
Expand All @@ -73,6 +76,7 @@ const Monitor: React.FunctionComponent<IMonitorProps> = ({ instance }: IMonitorP
title="Error Rate (%)"
instance={instance}
service={options.service}
spanKinds={options.spanKinds}
times={options.times}
/>
</GridItem>
Expand All @@ -81,6 +85,7 @@ const Monitor: React.FunctionComponent<IMonitorProps> = ({ instance }: IMonitorP
title="Request Rate (req/s)"
instance={instance}
service={options.service}
spanKinds={options.spanKinds}
times={options.times}
/>
</GridItem>
Expand All @@ -89,6 +94,7 @@ const Monitor: React.FunctionComponent<IMonitorProps> = ({ instance }: IMonitorP
title="Operations"
instance={instance}
service={options.service}
spanKinds={options.spanKinds}
times={options.times}
setDetails={setDetails}
/>
Expand Down
22 changes: 22 additions & 0 deletions plugins/plugin-jaeger/src/components/page/MonitorToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -16,10 +17,28 @@ const MonitorToolbar: React.FunctionComponent<IMonitorToolbarProps> = ({
setOptions,
}: IMonitorToolbarProps) => {
const [service, setService] = useState<string>(options.service);
const [spanKinds, setSpanKinds] = useState<string[]>(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,
});
};
Expand All @@ -29,6 +48,9 @@ const MonitorToolbar: React.FunctionComponent<IMonitorToolbarProps> = ({
<ToolbarItem grow={true}>
<TracesToolbarServices instance={instance} service={service} setService={(value): void => setService(value)} />
</ToolbarItem>
<ToolbarItem>
<MonitorToolbarSpanKinds spanKinds={spanKinds} setSpanKind={(value): void => selectSpanKind(value)} />
</ToolbarItem>

<Options times={options.times} showOptions={true} showSearchButton={true} setOptions={changeOptions} />
</Toolbar>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IMonitorToolbarSpanKindsProps> = ({
spanKinds,
setSpanKind,
}: IMonitorToolbarSpanKindsProps) => {
const [show, setShow] = useState<boolean>(false);

return (
<Select
variant={SelectVariant.checkbox}
typeAheadAriaLabel="Span Kinds"
placeholderText="Span Kinds"
onToggle={(): void => setShow(!show)}
onSelect={(e, value): void => setSpanKind(value as string)}
selections={spanKinds}
isOpen={show}
maxHeight="50vh"
>
{['unspecified', 'internal', 'server', 'client', 'producer', 'consumer'].map((spanKind) => (
<SelectOption key={spanKind} value={spanKind} />
))}
</Select>
);
};

export default MonitorToolbarSpanKinds;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface IMonitorOperationsProps {
description?: string;
instance: IPluginInstance;
service: string;
spanKinds: string[];
times: ITimes;
setDetails?: (details: React.ReactNode) => void;
}
Expand All @@ -18,11 +19,12 @@ const MonitorOperations: React.FunctionComponent<IMonitorOperationsProps> = ({
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 ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,28 @@ interface IMonitorServiceCallsProps {
description?: string;
instance: IPluginInstance;
service: string;
spanKinds: string[];
times: ITimes;
}

const MonitorServiceCalls: React.FunctionComponent<IMonitorServiceCallsProps> = ({
title,
description,
instance,
times,
service,
spanKinds,
times,
}: IMonitorServiceCallsProps) => {
const { isError, isLoading, error, data } = useQuery<IChartData[], Error>(
['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
Expand Down
Loading