Prometheus: add functionality to specify desired step interval in dashboards panels (#36422)

* Add select component for choosing step option

* Add onStepChange

* Add functionality for max step

* Rename minInterval to stepInterval to describe min, max and exact step interval

* Change select option from standard to exact

* Add new type StepType for better type safety

* Add tests for adjustInterval

* Add functionality and tests for exact step option

* Prometheus: Spell out min and max in select component

* Prometheus: Change width of step select component and add placeholder

* Prometheus: Adjust for the factor in exact step

* Prometheus: Update tooltip of step lable to include max and exact options and add padding to select component to give it some breathing room from other components

* Update snapshot for step tooltip

* Prometheus: make tooltip more informative

* Prometheus: add tooltip to interval input element

* Prometheus: extract default step option

* Prometheus: update snapshot for PromQueryEditor

* Prometheus: change step labels to uppercase

* Prometheus: define a default step option

* Prometheus: use default step option in both ui component and logic

* Prometheus: update snapshot for PromQueryEditor

* Prometheus: refactor datasource.ts for better readability

* Prometheus: change tool tip for step

* Prometheus: update snapshots

* Prometheus: add correct styling

* Prometheus: update snapshots

* Prometheus change variable name to something less superfluous

* Prometheus: refactor

* Prometheus: add new test for adjustInterval

* Docs: Update docummentation on the step parameter for prometheus

* Prometheus: make step input field smaller and change placeholder text to 15s

* Prometheus: update snapshots

* Prometheus: Make stepMode uniform in all places in the code

* Prometheus: update documentation and tooltip for step

* Prometheus: update snapshots
pull/37294/head
Olof Bourghardt 5 years ago committed by GitHub
parent 69dff96c1b
commit ddf5b65c51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      docs/sources/datasources/prometheus.md
  2. 57
      public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx
  3. 42
      public/app/plugins/datasource/prometheus/components/__snapshots__/PromQueryEditor.test.tsx.snap
  4. 56
      public/app/plugins/datasource/prometheus/datasource.test.ts
  5. 32
      public/app/plugins/datasource/prometheus/datasource.ts
  6. 3
      public/app/plugins/datasource/prometheus/types.ts

@ -46,8 +46,10 @@ Open a graph in edit mode by clicking the title > Edit (or by pressing `e` key w
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Query expression` | Prometheus query expression, check out the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). |
| `Legend format` | Controls the name of the time series, using name or pattern. For example `{{hostname}}` is replaced with the label value for the label `hostname`. |
| `Min step` | An additional lower limit for the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) and for the `$__interval` and `$__rate_interval` variables. The limit is absolute, it cannot modified by the _Resolution_ setting. |
| `Resolution` | `1/1` sets both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) such that each pixel corresponds to one data point. For better performance, lower resolutions can be picked. `1/2` only retrieves a data point for every other pixel, and `1/10` retrieves one data point per 10 pixels. Note that both _Min time interval_ and _Min step_ limit the final value of `$__interval` and `step`. |
| `Step` | Use 'Minimum' or 'Maximum' step mode to set the lower or upper bounds respectively on the interval between data points. For example, set "minimum 1h" to hint that measurements were not taken more frequently. Use the 'Exact' step mode to set an exact interval between data points. `$__interval` and `$__rate_interval` are supported. |
| `Resolution` | `1/1` sets both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) such that each pixel corresponds to one data point. For better performance, lower resolutions can be picked. `1/2` only retrieves a data point for every other pixel, and `1/10` retrieves one data point per 10 pixels. Note that both _Min time interval_ and _Step_ limit the final value of `$__interval` and `step`. |
| `Metric lookup` | Search for metric names in this input field. |
| `Format as` | Switch between `Table`, `Time series`, or `Heatmap`. `Table` will only work in the Table panel. `Heatmap` is suitable for displaying metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. |
| `Instant` | Perform an "instant" query, to return only the latest value that Prometheus has scraped for the requested time series. Instant queries return results much faster than normal range queries. Use them to look up label sets. |

@ -4,7 +4,7 @@ import React, { PureComponent } from 'react';
// Types
import { InlineFormLabel, LegacyForms, Select } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
import { PromQuery } from '../types';
import { PromQuery, StepMode } from '../types';
import PromQueryField from './PromQueryField';
import PromLink from './PromLink';
@ -24,11 +24,29 @@ const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = map([1, 2, 3, 4,
label: '1/' + value,
}));
export const DEFAULT_STEP_MODE: SelectableValue<StepMode> = {
value: 'min',
label: 'Minimum',
};
const STEP_MODES: Array<SelectableValue<StepMode>> = [
DEFAULT_STEP_MODE,
{
value: 'max',
label: 'Maximum',
},
{
value: 'exact',
label: 'Exact',
},
];
interface State {
legendFormat?: string;
formatOption: SelectableValue<string>;
interval?: string;
intervalFactorOption: SelectableValue<number>;
stepMode: SelectableValue<StepMode>;
instant: boolean;
exemplar: boolean;
}
@ -52,6 +70,8 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
formatOption: FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0],
intervalFactorOption:
INTERVAL_FACTOR_OPTIONS.find((option) => option.value === query.intervalFactor) || INTERVAL_FACTOR_OPTIONS[0],
// Step mode
stepMode: STEP_MODES.find((option) => option.value === query.stepMode) || DEFAULT_STEP_MODE,
// Switch options
instant: Boolean(query.instant),
exemplar: Boolean(query.exemplar),
@ -84,6 +104,11 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
this.setState({ intervalFactorOption: option }, this.onRunQuery);
};
onStepChange = (option: SelectableValue<StepMode>) => {
this.query.stepMode = option.value;
this.setState({ stepMode: option }, this.onRunQuery);
};
onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
const legendFormat = e.currentTarget.value;
this.query.legendFormat = legendFormat;
@ -105,7 +130,7 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
render() {
const { datasource, query, range, data } = this.props;
const { formatOption, instant, interval, intervalFactorOption, legendFormat, exemplar } = this.state;
const { formatOption, instant, interval, intervalFactorOption, stepMode, legendFormat, exemplar } = this.state;
return (
<PromQueryField
@ -139,27 +164,36 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
<div className="gf-form">
<InlineFormLabel
width={7}
width={5}
tooltip={
<>
An additional lower limit for the step parameter of the Prometheus query and for the{' '}
<code>$__interval</code> and <code>$__rate_interval</code> variables. The limit is absolute and not
modified by the &quot;Resolution&quot; setting.
Use &apos;Minimum&apos; or &apos;Maximum&apos; step mode to set the lower or upper bounds
respectively on the interval between data points. For example, set &quot;minimum 1h&quot; to hint
that measurements were not taken more frequently. Use the &apos;Exact&apos; step mode to set an
exact interval between data points. <code>$__interval</code> and <code>$__rate_interval</code> are
supported.
</>
}
>
Min step
Step
</InlineFormLabel>
<Select
className={'select-container'}
width={16}
isSearchable={false}
options={STEP_MODES}
onChange={this.onStepChange}
value={stepMode}
/>
<input
type="text"
className="gf-form-input width-8"
placeholder={interval}
className="gf-form-input width-4"
placeholder="15s"
onChange={this.onIntervalChange}
onBlur={this.onRunQuery}
value={interval}
/>
</div>
<div className="gf-form">
<div className="gf-form-label">Resolution</div>
<Select
@ -169,10 +203,10 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
value={intervalFactorOption}
/>
</div>
<div className="gf-form">
<div className="gf-form-label width-7">Format</div>
<Select
className={'select-container'}
width={16}
isSearchable={false}
options={FORMAT_OPTIONS}
@ -189,7 +223,6 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
/>
</InlineFormLabel>
</div>
<PromExemplarField isEnabled={exemplar} onChange={this.onExemplarChange} datasource={datasource} />
</div>
}

@ -31,8 +31,7 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
<FormLabel
tooltip={
<React.Fragment>
An additional lower limit for the step parameter of the Prometheus query and for the
Use 'Minimum' or 'Maximum' step mode to set the lower or upper bounds respectively on the interval between data points. For example, set "minimum 1h" to hint that measurements were not taken more frequently. Use the 'Exact' step mode to set an exact interval between data points.
<code>
$__interval
</code>
@ -40,18 +39,46 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
<code>
$__rate_interval
</code>
variables. The limit is absolute and not modified by the "Resolution" setting.
are supported.
</React.Fragment>
}
width={7}
width={5}
>
Min step
Step
</FormLabel>
<Select
className="select-container"
isSearchable={false}
onChange={[Function]}
options={
Array [
Object {
"label": "Minimum",
"value": "min",
},
Object {
"label": "Maximum",
"value": "max",
},
Object {
"label": "Exact",
"value": "exact",
},
]
}
value={
Object {
"label": "Minimum",
"value": "min",
}
}
width={16}
/>
<input
className="gf-form-input width-8"
className="gf-form-input width-4"
onBlur={[Function]}
onChange={[Function]}
placeholder=""
placeholder="15s"
type="text"
value=""
/>
@ -112,6 +139,7 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
Format
</div>
<Select
className="select-container"
isSearchable={false}
onChange={[Function]}
options={

@ -18,7 +18,7 @@ import {
prometheusRegularEscape,
prometheusSpecialRegexEscape,
} from './datasource';
import { PromOptions, PromQuery } from './types';
import { PromOptions, PromQuery, StepMode } from './types';
import { VariableHide } from '../../../features/variables/types';
import { describe } from '../../../../test/lib/common';
import { QueryOptions } from 'app/types';
@ -1685,6 +1685,60 @@ describe('PrometheusDatasource', () => {
templateSrvStub.replace = jest.fn((a: string) => a);
});
});
describe('adjustInterval', () => {
const dynamicInterval = 15;
const stepInterval = 35;
const range = 1642;
describe('when max step option is used', () => {
it('should return the minimum interval', () => {
let intervalFactor = 1;
let interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'max');
expect(interval).toBe(dynamicInterval * intervalFactor);
intervalFactor = 3;
interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'max');
expect(interval).toBe(stepInterval);
});
});
describe('when min step option is used', () => {
it('should return the maximum interval', () => {
let intervalFactor = 1;
let interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'min');
expect(interval).toBe(stepInterval);
intervalFactor = 3;
interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'min');
expect(interval).toBe(dynamicInterval * intervalFactor);
});
});
describe('when exact step option is used', () => {
it('should return the stepInterval * intervalFactor', () => {
let intervalFactor = 3;
let interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'exact');
expect(interval).toBe(stepInterval * intervalFactor);
});
});
it('should not return a value less than the safe interval', () => {
let newStepInterval = 0.13;
let intervalFactor = 1;
let stepMode: StepMode = 'min';
let safeInterval = range / 11000;
if (safeInterval > 1) {
safeInterval = Math.ceil(safeInterval);
}
let interval = ds.adjustInterval(dynamicInterval, newStepInterval, range, intervalFactor, stepMode);
expect(interval).toBeGreaterThanOrEqual(safeInterval);
stepMode = 'max';
interval = ds.adjustInterval(dynamicInterval, newStepInterval, range, intervalFactor, stepMode);
expect(interval).toBeGreaterThanOrEqual(safeInterval);
stepMode = 'exact';
interval = ds.adjustInterval(dynamicInterval, newStepInterval, range, intervalFactor, stepMode);
expect(interval).toBeGreaterThanOrEqual(safeInterval);
});
});
});
describe('PrometheusDatasource for POST', () => {

@ -38,9 +38,11 @@ import {
PromQueryRequest,
PromScalarData,
PromVectorData,
StepMode,
} from './types';
import { PrometheusVariableSupport } from './variables';
import PrometheusMetricFindQuery from './metric_find_query';
import { DEFAULT_STEP_MODE } from './components/PromQueryEditor';
export const ANNOTATION_QUERY_STEP_DEFAULT = '60s';
const EXEMPLARS_NOT_AVAILABLE = 'Exemplars for this data source are not available.';
@ -410,11 +412,12 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
end: 0,
};
const range = Math.ceil(end - start);
// target.stepMode specifies whether to use min, max or exact step
const stepMode = target.stepMode || (DEFAULT_STEP_MODE.value as StepMode);
// options.interval is the dynamically calculated interval
let interval: number = rangeUtil.intervalToSeconds(options.interval);
// Minimum interval ("Min step"), if specified for the query, or same as interval otherwise.
const minInterval = rangeUtil.intervalToSeconds(
const stepInterval = rangeUtil.intervalToSeconds(
this.templateSrv.replace(target.interval || options.interval, options.scopedVars)
);
// Scrape interval as specified for the query ("Min step") or otherwise taken from the datasource.
@ -425,7 +428,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
const intervalFactor = target.intervalFactor || 1;
// Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits
const adjustedInterval = this.adjustInterval(interval, minInterval, range, intervalFactor);
const adjustedInterval = this.adjustInterval(interval, stepInterval, range, intervalFactor, stepMode);
let scopedVars = {
...options.scopedVars,
...this.getRangeScopedVars(options.range),
@ -478,7 +481,13 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
return { __rate_interval: { text: rateInterval + 's', value: rateInterval + 's' } };
}
adjustInterval(interval: number, minInterval: number, range: number, intervalFactor: number) {
adjustInterval(
dynamicInterval: number,
stepInterval: number,
range: number,
intervalFactor: number,
stepMode: StepMode
) {
// Prometheus will drop queries that might return more than 11000 data points.
// Calculate a safe interval as an additional minimum to take into account.
// Fractional safeIntervals are allowed, however serve little purpose if the interval is greater than 1
@ -487,7 +496,20 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
if (safeInterval > 1) {
safeInterval = Math.ceil(safeInterval);
}
return Math.max(interval * intervalFactor, minInterval, safeInterval);
//Calculate adjusted interval based on the current step option
let adjustedInterval = safeInterval;
if (stepMode === 'min') {
adjustedInterval = Math.max(dynamicInterval * intervalFactor, stepInterval, safeInterval);
} else if (stepMode === 'max') {
adjustedInterval = Math.min(dynamicInterval * intervalFactor, stepInterval);
if (adjustedInterval < safeInterval) {
adjustedInterval = safeInterval;
}
} else if (stepMode === 'exact') {
adjustedInterval = Math.max(stepInterval * intervalFactor, safeInterval);
}
return adjustedInterval;
}
performTimeSeriesQuery(query: PromQueryRequest, start: number, end: number) {

@ -10,6 +10,7 @@ export interface PromQuery extends DataQuery {
hinting?: boolean;
interval?: string;
intervalFactor?: number;
stepMode?: StepMode;
legendFormat?: string;
valueWithRefId?: boolean;
requestId?: string;
@ -17,6 +18,8 @@ export interface PromQuery extends DataQuery {
showingTable?: boolean;
}
export type StepMode = 'min' | 'max' | 'exact';
export interface PromOptions extends DataSourceJsonData {
timeInterval: string;
queryTimeout: string;

Loading…
Cancel
Save