mirror of
https://github.com/grafana/grafana.git
synced 2025-02-09 23:16:16 -06:00
Cloud Monitoring: Support SLO burn rate (#53710)
This commit is contained in:
parent
d5cc1bfff2
commit
2ff007b266
@ -208,6 +208,7 @@ The friendly names for the time series selectors are shown in Grafana. Here is t
|
||||
| SLI Value | select_slo_health |
|
||||
| SLO Compliance | select_slo_compliance |
|
||||
| SLO Error Budget Remaining | select_slo_budget_fraction |
|
||||
| SLO Burn Rate | select_slo_burn_rate |
|
||||
|
||||
#### Alias patterns for SLO queries
|
||||
|
||||
|
@ -429,7 +429,13 @@ func buildFilterString(metricType string, filterParts []string) string {
|
||||
}
|
||||
|
||||
func buildSLOFilterExpression(q sloQuery) string {
|
||||
return fmt.Sprintf(`%s("projects/%s/services/%s/serviceLevelObjectives/%s")`, q.SelectorName, q.ProjectName, q.ServiceId, q.SloId)
|
||||
sloName := fmt.Sprintf("projects/%s/services/%s/serviceLevelObjectives/%s", q.ProjectName, q.ServiceId, q.SloId)
|
||||
|
||||
if q.SelectorName == "select_slo_burn_rate" {
|
||||
return fmt.Sprintf(`%s("%s", "%s")`, q.SelectorName, sloName, q.LookbackPeriod)
|
||||
} else {
|
||||
return fmt.Sprintf(`%s("%s")`, q.SelectorName, sloName)
|
||||
}
|
||||
}
|
||||
|
||||
func setMetricAggParams(params *url.Values, query *metricQuery, durationSeconds int, intervalMs int64) {
|
||||
|
@ -589,6 +589,26 @@ func TestCloudMonitoring(t *testing.T) {
|
||||
|
||||
dl := qqueries[0].buildDeepLink()
|
||||
assert.Empty(t, dl)
|
||||
|
||||
req.Queries[0].JSON = json.RawMessage(`{
|
||||
"queryType": "slo",
|
||||
"sloQuery": {
|
||||
"projectName": "test-proj",
|
||||
"alignmentPeriod": "stackdriver-auto",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER",
|
||||
"aliasBy": "",
|
||||
"selectorName": "select_slo_burn_rate",
|
||||
"serviceId": "test-service",
|
||||
"sloId": "test-slo",
|
||||
"lookbackPeriod": "1h"
|
||||
},
|
||||
"metricQuery": {}
|
||||
}`)
|
||||
|
||||
qes, err = service.buildQueryExecutors(req)
|
||||
require.NoError(t, err)
|
||||
qqqueries := getCloudMonitoringQueriesFromInterface(t, qes)
|
||||
assert.Equal(t, `aggregation.alignmentPeriod=%2B60s&aggregation.perSeriesAligner=ALIGN_NEXT_OLDER&filter=select_slo_burn_rate%28%22projects%2Ftest-proj%2Fservices%2Ftest-service%2FserviceLevelObjectives%2Ftest-slo%22%2C+%221h%22%29&interval.endTime=2018-03-15T13%3A34%3A00Z&interval.startTime=2018-03-15T13%3A00%3A00Z`, qqqueries[0].Target)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -68,6 +68,7 @@ type (
|
||||
SelectorName string
|
||||
ServiceId string
|
||||
SloId string
|
||||
LookbackPeriod string
|
||||
}
|
||||
|
||||
grafanaQuery struct {
|
||||
|
@ -31,6 +31,7 @@ export const createMockSLOQuery: (overrides?: Partial<SLOQuery>) => SLOQuery = (
|
||||
serviceName: '',
|
||||
sloId: '',
|
||||
sloName: '',
|
||||
lookbackPeriod: '',
|
||||
...overrides,
|
||||
};
|
||||
};
|
||||
|
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import { LOOKBACK_PERIODS } from '../../constants';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (lookbackPeriod: string) => void;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
current?: string;
|
||||
}
|
||||
|
||||
export const LookbackPeriodSelect: React.FC<Props> = ({ refId, current, templateVariableOptions, onChange }) => {
|
||||
const options = LOOKBACK_PERIODS.map((lp) => ({
|
||||
...lp,
|
||||
label: lp.text,
|
||||
}));
|
||||
if (current && !options.find((op) => op.value === current)) {
|
||||
options.push({ label: current, text: current, value: current, hidden: false });
|
||||
}
|
||||
const visibleOptions = options.filter((lp) => !lp.hidden);
|
||||
|
||||
return (
|
||||
<EditorField label="Lookback period" htmlFor={`${refId}-lookback-period`}>
|
||||
<Select
|
||||
inputId={`${refId}-lookback-period`}
|
||||
width="auto"
|
||||
allowCustomValue
|
||||
value={[...options, ...templateVariableOptions].find((s) => s.value === current)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
{
|
||||
label: 'Predefined periods',
|
||||
expanded: true,
|
||||
options: visibleOptions,
|
||||
},
|
||||
]}
|
||||
onChange={({ value }) => onChange(value!)}
|
||||
/>
|
||||
</EditorField>
|
||||
);
|
||||
};
|
@ -3,12 +3,13 @@ import React, { useMemo } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/ui';
|
||||
|
||||
import { ALIGNMENT_PERIODS } from '../../constants';
|
||||
import { ALIGNMENT_PERIODS, SLO_BURN_RATE_SELECTOR_NAME } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { alignmentPeriodLabel } from '../../functions';
|
||||
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
|
||||
|
||||
import { AliasBy } from './AliasBy';
|
||||
import { LookbackPeriodSelect } from './LookbackPeriodSelect';
|
||||
import { PeriodSelect } from './PeriodSelect';
|
||||
import { Project } from './Project';
|
||||
import { SLO } from './SLO';
|
||||
@ -35,6 +36,7 @@ export const defaultQuery: (dataSource: CloudMonitoringDatasource) => SLOQuery =
|
||||
serviceName: '',
|
||||
sloId: '',
|
||||
sloName: '',
|
||||
lookbackPeriod: '',
|
||||
});
|
||||
|
||||
export function SLOQueryEditor({
|
||||
@ -77,6 +79,14 @@ export function SLOQueryEditor({
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{query.selectorName === SLO_BURN_RATE_SELECTOR_NAME && (
|
||||
<LookbackPeriodSelect
|
||||
refId={refId}
|
||||
onChange={(lookbackPeriod) => onChange({ ...query, lookbackPeriod: lookbackPeriod })}
|
||||
current={query.lookbackPeriod}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EditorFieldGroup>
|
||||
<EditorField label="Alignment period" tooltip={alignmentLabel}>
|
||||
|
@ -0,0 +1,53 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { QueryEditorRow } from '..';
|
||||
import { SELECT_WIDTH, LOOKBACK_PERIODS } from '../../constants';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (lookbackPeriod: string) => void;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
current?: string;
|
||||
}
|
||||
|
||||
export const LookbackPeriodSelect: FunctionComponent<Props> = ({
|
||||
refId,
|
||||
current,
|
||||
templateVariableOptions,
|
||||
onChange,
|
||||
}) => {
|
||||
const options = LOOKBACK_PERIODS.map((lp) => ({
|
||||
...lp,
|
||||
label: lp.text,
|
||||
}));
|
||||
if (current && !options.find((op) => op.value === current)) {
|
||||
options.push({ label: current, text: current, value: current, hidden: false });
|
||||
}
|
||||
const visibleOptions = options.filter((lp) => !lp.hidden);
|
||||
|
||||
return (
|
||||
<QueryEditorRow label="Lookback period" htmlFor={`${refId}-lookback-period`}>
|
||||
<Select
|
||||
inputId={`${refId}-lookback-period`}
|
||||
width={SELECT_WIDTH}
|
||||
allowCustomValue
|
||||
value={[...options, ...templateVariableOptions].find((s) => s.value === current)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
{
|
||||
label: 'Predefined periods',
|
||||
expanded: true,
|
||||
options: visibleOptions,
|
||||
},
|
||||
]}
|
||||
onChange={({ value }) => onChange(value!)}
|
||||
/>
|
||||
</QueryEditorRow>
|
||||
);
|
||||
};
|
@ -3,10 +3,12 @@ import React from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { AliasBy, PeriodSelect, AlignmentPeriodLabel, Project, QueryEditorRow } from '..';
|
||||
import { ALIGNMENT_PERIODS, SELECT_WIDTH } from '../../constants';
|
||||
import { ALIGNMENT_PERIODS, SELECT_WIDTH, SLO_BURN_RATE_SELECTOR_NAME } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
|
||||
|
||||
import { LookbackPeriodSelect } from './LookbackPeriodSelect';
|
||||
|
||||
import { Selector, Service, SLO } from '.';
|
||||
|
||||
export interface Props {
|
||||
@ -29,6 +31,7 @@ export const defaultQuery: (dataSource: CloudMonitoringDatasource) => SLOQuery =
|
||||
serviceName: '',
|
||||
sloId: '',
|
||||
sloName: '',
|
||||
lookbackPeriod: '',
|
||||
});
|
||||
|
||||
export function SLOQueryEditor({
|
||||
@ -70,6 +73,15 @@ export function SLOQueryEditor({
|
||||
onChange={onChange}
|
||||
></Selector>
|
||||
|
||||
{query.selectorName === SLO_BURN_RATE_SELECTOR_NAME && (
|
||||
<LookbackPeriodSelect
|
||||
refId={refId}
|
||||
onChange={(lookbackPeriod) => onChange({ ...query, lookbackPeriod: lookbackPeriod })}
|
||||
current={query.lookbackPeriod}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
/>
|
||||
)}
|
||||
|
||||
<QueryEditorRow label="Alignment period" htmlFor={`${refId}-alignment-period`}>
|
||||
<PeriodSelect
|
||||
inputId={`${refId}-alignment-period`}
|
||||
|
@ -278,6 +278,21 @@ export const GRAPH_PERIODS: periodOption[] = [
|
||||
{ text: '1w', value: '1w' },
|
||||
];
|
||||
|
||||
// Usable units: ns, us, ms, s, m, h
|
||||
// ref. https://cloud.google.com/stackdriver/docs/solutions/slo-monitoring/api/timeseries-selectors#tss-names-args
|
||||
export const LOOKBACK_PERIODS: periodOption[] = [
|
||||
{ text: '1m', value: '1m' },
|
||||
{ text: '2m', value: '2m' },
|
||||
{ text: '5m', value: '5m' },
|
||||
{ text: '10m', value: '10m' },
|
||||
{ text: '30m', value: '30m' },
|
||||
{ text: '1h', value: '1h' },
|
||||
{ text: '3h', value: '3h' },
|
||||
{ text: '6h', value: '6h' },
|
||||
{ text: '24h', value: '24h' },
|
||||
{ text: '72h', value: '72h' },
|
||||
];
|
||||
|
||||
export const SYSTEM_LABELS = [
|
||||
'metadata.system_labels.cloud_account',
|
||||
'metadata.system_labels.name',
|
||||
@ -291,10 +306,13 @@ export const SYSTEM_LABELS = [
|
||||
'metadata.system_labels.container_image',
|
||||
];
|
||||
|
||||
export const SLO_BURN_RATE_SELECTOR_NAME = 'select_slo_burn_rate';
|
||||
|
||||
export const SELECTORS = [
|
||||
{ label: 'SLI Value', value: 'select_slo_health' },
|
||||
{ label: 'SLO Compliance', value: 'select_slo_compliance' },
|
||||
{ label: 'SLO Error Budget Remaining', value: 'select_slo_budget_fraction' },
|
||||
{ label: 'SLO Burn Rate', value: SLO_BURN_RATE_SELECTOR_NAME },
|
||||
];
|
||||
|
||||
export const QUERY_TYPES = [
|
||||
|
@ -14,6 +14,7 @@ import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
||||
import { CloudMonitoringAnnotationSupport } from './annotationSupport';
|
||||
import { SLO_BURN_RATE_SELECTOR_NAME } from './constants';
|
||||
import {
|
||||
CloudMonitoringOptions,
|
||||
CloudMonitoringQuery,
|
||||
@ -224,8 +225,14 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
|
||||
}
|
||||
|
||||
if (query.queryType && query.queryType === QueryType.SLO && query.sloQuery) {
|
||||
const { selectorName, serviceId, sloId, projectName } = query.sloQuery;
|
||||
return !!selectorName && !!serviceId && !!sloId && !!projectName;
|
||||
const { selectorName, serviceId, sloId, projectName, lookbackPeriod } = query.sloQuery;
|
||||
return (
|
||||
!!selectorName &&
|
||||
!!serviceId &&
|
||||
!!sloId &&
|
||||
!!projectName &&
|
||||
(selectorName !== SLO_BURN_RATE_SELECTOR_NAME || !!lookbackPeriod)
|
||||
);
|
||||
}
|
||||
|
||||
if (query.queryType && query.queryType === QueryType.METRICS && query.metricQuery.editorMode === EditorMode.MQL) {
|
||||
|
@ -143,6 +143,7 @@ export interface SLOQuery extends BaseQuery {
|
||||
sloId: string;
|
||||
sloName: string;
|
||||
goal?: number;
|
||||
lookbackPeriod?: string;
|
||||
}
|
||||
|
||||
export interface CloudMonitoringQuery extends DataQuery {
|
||||
|
Loading…
Reference in New Issue
Block a user