mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -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 |
|
| SLI Value | select_slo_health |
|
||||||
| SLO Compliance | select_slo_compliance |
|
| SLO Compliance | select_slo_compliance |
|
||||||
| SLO Error Budget Remaining | select_slo_budget_fraction |
|
| SLO Error Budget Remaining | select_slo_budget_fraction |
|
||||||
|
| SLO Burn Rate | select_slo_burn_rate |
|
||||||
|
|
||||||
#### Alias patterns for SLO queries
|
#### Alias patterns for SLO queries
|
||||||
|
|
||||||
|
@ -429,7 +429,13 @@ func buildFilterString(metricType string, filterParts []string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildSLOFilterExpression(q sloQuery) 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) {
|
func setMetricAggParams(params *url.Values, query *metricQuery, durationSeconds int, intervalMs int64) {
|
||||||
|
@ -589,6 +589,26 @@ func TestCloudMonitoring(t *testing.T) {
|
|||||||
|
|
||||||
dl := qqueries[0].buildDeepLink()
|
dl := qqueries[0].buildDeepLink()
|
||||||
assert.Empty(t, dl)
|
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
|
SelectorName string
|
||||||
ServiceId string
|
ServiceId string
|
||||||
SloId string
|
SloId string
|
||||||
|
LookbackPeriod string
|
||||||
}
|
}
|
||||||
|
|
||||||
grafanaQuery struct {
|
grafanaQuery struct {
|
||||||
|
@ -31,6 +31,7 @@ export const createMockSLOQuery: (overrides?: Partial<SLOQuery>) => SLOQuery = (
|
|||||||
serviceName: '',
|
serviceName: '',
|
||||||
sloId: '',
|
sloId: '',
|
||||||
sloName: '',
|
sloName: '',
|
||||||
|
lookbackPeriod: '',
|
||||||
...overrides,
|
...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 { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/ui';
|
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 CloudMonitoringDatasource from '../../datasource';
|
||||||
import { alignmentPeriodLabel } from '../../functions';
|
import { alignmentPeriodLabel } from '../../functions';
|
||||||
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
|
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
|
||||||
|
|
||||||
import { AliasBy } from './AliasBy';
|
import { AliasBy } from './AliasBy';
|
||||||
|
import { LookbackPeriodSelect } from './LookbackPeriodSelect';
|
||||||
import { PeriodSelect } from './PeriodSelect';
|
import { PeriodSelect } from './PeriodSelect';
|
||||||
import { Project } from './Project';
|
import { Project } from './Project';
|
||||||
import { SLO } from './SLO';
|
import { SLO } from './SLO';
|
||||||
@ -35,6 +36,7 @@ export const defaultQuery: (dataSource: CloudMonitoringDatasource) => SLOQuery =
|
|||||||
serviceName: '',
|
serviceName: '',
|
||||||
sloId: '',
|
sloId: '',
|
||||||
sloName: '',
|
sloName: '',
|
||||||
|
lookbackPeriod: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
export function SLOQueryEditor({
|
export function SLOQueryEditor({
|
||||||
@ -77,6 +79,14 @@ export function SLOQueryEditor({
|
|||||||
query={query}
|
query={query}
|
||||||
onChange={onChange}
|
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>
|
<EditorFieldGroup>
|
||||||
<EditorField label="Alignment period" tooltip={alignmentLabel}>
|
<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 { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
import { AliasBy, PeriodSelect, AlignmentPeriodLabel, Project, QueryEditorRow } from '..';
|
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 CloudMonitoringDatasource from '../../datasource';
|
||||||
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
|
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
|
||||||
|
|
||||||
|
import { LookbackPeriodSelect } from './LookbackPeriodSelect';
|
||||||
|
|
||||||
import { Selector, Service, SLO } from '.';
|
import { Selector, Service, SLO } from '.';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -29,6 +31,7 @@ export const defaultQuery: (dataSource: CloudMonitoringDatasource) => SLOQuery =
|
|||||||
serviceName: '',
|
serviceName: '',
|
||||||
sloId: '',
|
sloId: '',
|
||||||
sloName: '',
|
sloName: '',
|
||||||
|
lookbackPeriod: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
export function SLOQueryEditor({
|
export function SLOQueryEditor({
|
||||||
@ -70,6 +73,15 @@ export function SLOQueryEditor({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
></Selector>
|
></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`}>
|
<QueryEditorRow label="Alignment period" htmlFor={`${refId}-alignment-period`}>
|
||||||
<PeriodSelect
|
<PeriodSelect
|
||||||
inputId={`${refId}-alignment-period`}
|
inputId={`${refId}-alignment-period`}
|
||||||
|
@ -278,6 +278,21 @@ export const GRAPH_PERIODS: periodOption[] = [
|
|||||||
{ text: '1w', value: '1w' },
|
{ 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 = [
|
export const SYSTEM_LABELS = [
|
||||||
'metadata.system_labels.cloud_account',
|
'metadata.system_labels.cloud_account',
|
||||||
'metadata.system_labels.name',
|
'metadata.system_labels.name',
|
||||||
@ -291,10 +306,13 @@ export const SYSTEM_LABELS = [
|
|||||||
'metadata.system_labels.container_image',
|
'metadata.system_labels.container_image',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const SLO_BURN_RATE_SELECTOR_NAME = 'select_slo_burn_rate';
|
||||||
|
|
||||||
export const SELECTORS = [
|
export const SELECTORS = [
|
||||||
{ label: 'SLI Value', value: 'select_slo_health' },
|
{ label: 'SLI Value', value: 'select_slo_health' },
|
||||||
{ label: 'SLO Compliance', value: 'select_slo_compliance' },
|
{ label: 'SLO Compliance', value: 'select_slo_compliance' },
|
||||||
{ label: 'SLO Error Budget Remaining', value: 'select_slo_budget_fraction' },
|
{ label: 'SLO Error Budget Remaining', value: 'select_slo_budget_fraction' },
|
||||||
|
{ label: 'SLO Burn Rate', value: SLO_BURN_RATE_SELECTOR_NAME },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const QUERY_TYPES = [
|
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 { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
|
||||||
import { CloudMonitoringAnnotationSupport } from './annotationSupport';
|
import { CloudMonitoringAnnotationSupport } from './annotationSupport';
|
||||||
|
import { SLO_BURN_RATE_SELECTOR_NAME } from './constants';
|
||||||
import {
|
import {
|
||||||
CloudMonitoringOptions,
|
CloudMonitoringOptions,
|
||||||
CloudMonitoringQuery,
|
CloudMonitoringQuery,
|
||||||
@ -224,8 +225,14 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.queryType && query.queryType === QueryType.SLO && query.sloQuery) {
|
if (query.queryType && query.queryType === QueryType.SLO && query.sloQuery) {
|
||||||
const { selectorName, serviceId, sloId, projectName } = query.sloQuery;
|
const { selectorName, serviceId, sloId, projectName, lookbackPeriod } = query.sloQuery;
|
||||||
return !!selectorName && !!serviceId && !!sloId && !!projectName;
|
return (
|
||||||
|
!!selectorName &&
|
||||||
|
!!serviceId &&
|
||||||
|
!!sloId &&
|
||||||
|
!!projectName &&
|
||||||
|
(selectorName !== SLO_BURN_RATE_SELECTOR_NAME || !!lookbackPeriod)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.queryType && query.queryType === QueryType.METRICS && query.metricQuery.editorMode === EditorMode.MQL) {
|
if (query.queryType && query.queryType === QueryType.METRICS && query.metricQuery.editorMode === EditorMode.MQL) {
|
||||||
|
@ -143,6 +143,7 @@ export interface SLOQuery extends BaseQuery {
|
|||||||
sloId: string;
|
sloId: string;
|
||||||
sloName: string;
|
sloName: string;
|
||||||
goal?: number;
|
goal?: number;
|
||||||
|
lookbackPeriod?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CloudMonitoringQuery extends DataQuery {
|
export interface CloudMonitoringQuery extends DataQuery {
|
||||||
|
Loading…
Reference in New Issue
Block a user