mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki: add support for resolution (#36710)
* Add input to specify min step * Add stepInterval to as input to component * Add onBlur to Input component * Loki: add functionality for min step * Loki: change name on props to step to make it more clear * Loki: add resolution as a query option * Loki: Add min,max,exact as step options * Loki: add functionality for different step modes * Loki: fix bug where step function isn't working * Loki: fix bug where exact step isn't working * Loki: change width of step input field * Loki: add tests for adjustInterval function * Loki: add check for max step oprio to make sure it's not below the safe interval * Loki: fix bug with some tests * Loki: fix bug with tests * Explore: add tooltip to loki step function * Loki: remove resolution as a logs option * Loki: update snapshots * Fix failing tests * Loki: add select component for choosing resolution * Loki: add functionality for calculating correct interval with resolution applied * Loki: remove functionality for step mode * Loki: remove tests for step mode * Loki: add tooltip to line limit and resolution * Loki: add backend support for resolution * Loki: fixed backend bug where resolution was undefined * Loki: add check for resolution
This commit is contained in:
@@ -1,12 +1,11 @@
|
|||||||
|
|
||||||
<!-- 8.1.1 START -->
|
<!-- 8.1.1 START -->
|
||||||
|
|
||||||
# 8.1.1 (2021-08-09)
|
# 8.1.1 (2021-08-09)
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
* **CloudWatch Logs:** Fix crash when no region is selected. [#37639](https://github.com/grafana/grafana/pull/37639), [@aocenas](https://github.com/aocenas)
|
- **CloudWatch Logs:** Fix crash when no region is selected. [#37639](https://github.com/grafana/grafana/pull/37639), [@aocenas](https://github.com/aocenas)
|
||||||
* **Reporting:** Fix timezone parsing for scheduler (enterprise)
|
- **Reporting:** Fix timezone parsing for scheduler (enterprise)
|
||||||
|
|
||||||
<!-- 8.1.1 END -->
|
<!-- 8.1.1 END -->
|
||||||
<!-- 8.1.0 START -->
|
<!-- 8.1.0 START -->
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ weight = 150
|
|||||||
|
|
||||||
Grafana includes built-in support for Prometheus Alertmanager. It is presently in alpha and not accessible unless [alpha plugins are enabled in Grafana settings](https://grafana.com/docs/grafana/latest/administration/configuration/#enable_alpha). Once you add it as a data source, you can use the [Grafana alerting UI](https://grafana.com/docs/grafana/latest/alerting/) to manage silences, contact points as well as notification policies. A drop down option in these pages allows you to switch between Grafana and any configured Alertmanager data sources .
|
Grafana includes built-in support for Prometheus Alertmanager. It is presently in alpha and not accessible unless [alpha plugins are enabled in Grafana settings](https://grafana.com/docs/grafana/latest/administration/configuration/#enable_alpha). Once you add it as a data source, you can use the [Grafana alerting UI](https://grafana.com/docs/grafana/latest/alerting/) to manage silences, contact points as well as notification policies. A drop down option in these pages allows you to switch between Grafana and any configured Alertmanager data sources .
|
||||||
|
|
||||||
> **Note:** Currently, the [Cortex implementation of Prometheus Alertmanager](https://cortexmetrics.io/docs/proposals/scalable-alertmanager/) is required to edit rules.
|
> **Note:** Currently, the [Cortex implementation of Prometheus Alertmanager](https://cortexmetrics.io/docs/proposals/scalable-alertmanager/) is required to edit rules.
|
||||||
|
|
||||||
## Provision the Alertmanager data source
|
## Provision the Alertmanager data source
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ Returns an indicator to check if fine-grained access control is enabled or not.
|
|||||||
| -------------------- | ---------------------- |
|
| -------------------- | ---------------------- |
|
||||||
| status:accesscontrol | services:accesscontrol |
|
| status:accesscontrol | services:accesscontrol |
|
||||||
|
|
||||||
|
|
||||||
#### Example request
|
#### Example request
|
||||||
|
|
||||||
```http
|
```http
|
||||||
@@ -256,7 +255,6 @@ Content-Type: application/json; charset=UTF-8
|
|||||||
| action | string | Yes | Refer to [Permissions]({{< relref "../enterprise/access-control/permissions.md" >}}) for full list of available actions. |
|
| action | string | Yes | Refer to [Permissions]({{< relref "../enterprise/access-control/permissions.md" >}}) for full list of available actions. |
|
||||||
| scope | string | No | If not present, no scope will be mapped to the permission. Refer to [Permissions]({{< relref "../enterprise/access-control/permissions.md#scope-definitions" >}}) for full list of available scopes. |
|
| scope | string | No | If not present, no scope will be mapped to the permission. Refer to [Permissions]({{< relref "../enterprise/access-control/permissions.md#scope-definitions" >}}) for full list of available scopes. |
|
||||||
|
|
||||||
| scope | string | No | If not present, no scope will be mapped to the permission. Refer to [Permissions]({{< relref "../enterprise/access-control/permissions.md#scope-definitions" >}}) for full list of available scopes. |
|
|
||||||
#### Example response
|
#### Example response
|
||||||
|
|
||||||
```http
|
```http
|
||||||
@@ -279,7 +277,6 @@ For example, if a user does not have required permissions for creating users, th
|
|||||||
|
|
||||||
`DELETE /api/access-control/roles/:uid?force=false`
|
`DELETE /api/access-control/roles/:uid?force=false`
|
||||||
|
|
||||||
|
|
||||||
Delete a role with the given UID, and it's permissions. If the role is assigned to a built-in role, the deletion operation will fail, unless `force` query param is set to `true`, and in that case all assignments will also be deleted.
|
Delete a role with the given UID, and it's permissions. If the role is assigned to a built-in role, the deletion operation will fail, unless `force` query param is set to `true`, and in that case all assignments will also be deleted.
|
||||||
|
|
||||||
#### Required permissions
|
#### Required permissions
|
||||||
@@ -377,7 +374,6 @@ For example, if a user does not have required permissions for creating users, th
|
|||||||
| roles.builtin:add | permissions:delegate |
|
| roles.builtin:add | permissions:delegate |
|
||||||
|
|
||||||
#### Example request
|
#### Example request
|
||||||
#### Example request
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
POST /api/access-control/builtin-roles
|
POST /api/access-control/builtin-roles
|
||||||
|
|||||||
@@ -10,5 +10,4 @@ list = false
|
|||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
* **CloudWatch Logs:** Fix crash when no region is selected. [#37639](https://github.com/grafana/grafana/pull/37639), [@aocenas](https://github.com/aocenas)
|
- **CloudWatch Logs:** Fix crash when no region is selected. [#37639](https://github.com/grafana/grafana/pull/37639), [@aocenas](https://github.com/aocenas)
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ type ResponseModel struct {
|
|||||||
LegendFormat string `json:"legendFormat"`
|
LegendFormat string `json:"legendFormat"`
|
||||||
Interval string `json:"interval"`
|
Interval string `json:"interval"`
|
||||||
IntervalMS int `json:"intervalMS"`
|
IntervalMS int `json:"intervalMS"`
|
||||||
|
Resolution int64 `json:"resolution"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -210,7 +211,12 @@ func (s *Service) parseQuery(dsInfo *datasourceInfo, queryContext *backend.Query
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
step := time.Duration(int64(interval.Value))
|
var resolution int64 = 1
|
||||||
|
if model.Resolution >= 1 && model.Resolution <= 5 || model.Resolution == 10 {
|
||||||
|
resolution = model.Resolution
|
||||||
|
}
|
||||||
|
|
||||||
|
step := time.Duration(int64(interval.Value) * resolution)
|
||||||
|
|
||||||
qs = append(qs, &lokiQuery{
|
qs = append(qs, &lokiQuery{
|
||||||
Expr: model.Expr,
|
Expr: model.Expr,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito
|
|||||||
<LokiOptionFields
|
<LokiOptionFields
|
||||||
queryType={queryWithRefId.instant ? 'instant' : 'range'}
|
queryType={queryWithRefId.instant ? 'instant' : 'range'}
|
||||||
lineLimitValue={queryWithRefId?.maxLines?.toString() || ''}
|
lineLimitValue={queryWithRefId?.maxLines?.toString() || ''}
|
||||||
|
resolution={queryWithRefId.resolution || 1}
|
||||||
query={queryWithRefId}
|
query={queryWithRefId}
|
||||||
onRunQuery={() => {}}
|
onRunQuery={() => {}}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export function LokiExploreQueryEditor(props: Props) {
|
|||||||
<LokiOptionFields
|
<LokiOptionFields
|
||||||
queryType={query.instant ? 'instant' : 'range'}
|
queryType={query.instant ? 'instant' : 'range'}
|
||||||
lineLimitValue={query?.maxLines?.toString() || ''}
|
lineLimitValue={query?.maxLines?.toString() || ''}
|
||||||
|
resolution={query.resolution || 1}
|
||||||
query={query}
|
query={query}
|
||||||
onRunQuery={onRunQuery}
|
onRunQuery={onRunQuery}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { LokiQuery } from '../types';
|
import { map } from 'lodash';
|
||||||
import { SelectableValue } from '@grafana/data';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { InlineFormLabel, RadioButtonGroup, InlineField, Input } from '@grafana/ui';
|
import { InlineFormLabel, RadioButtonGroup, InlineField, Input, Select } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { LokiQuery } from '../types';
|
||||||
|
|
||||||
export interface LokiOptionFieldsProps {
|
export interface LokiOptionFieldsProps {
|
||||||
lineLimitValue: string;
|
lineLimitValue: string;
|
||||||
|
resolution: number;
|
||||||
queryType: LokiQueryType;
|
queryType: LokiQueryType;
|
||||||
query: LokiQuery;
|
query: LokiQuery;
|
||||||
onChange: (value: LokiQuery) => void;
|
onChange: (value: LokiQuery) => void;
|
||||||
@@ -27,8 +29,20 @@ const queryTypeOptions: Array<SelectableValue<LokiQueryType>> = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const DEFAULT_RESOLUTION: SelectableValue<number> = {
|
||||||
|
value: 1,
|
||||||
|
label: '1/1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const RESOLUTION_OPTIONS: Array<SelectableValue<number>> = [DEFAULT_RESOLUTION].concat(
|
||||||
|
map([2, 3, 4, 5, 10], (value: number) => ({
|
||||||
|
value,
|
||||||
|
label: '1/' + value,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
export function LokiOptionFields(props: LokiOptionFieldsProps) {
|
export function LokiOptionFields(props: LokiOptionFieldsProps) {
|
||||||
const { lineLimitValue, queryType, query, onRunQuery, runOnBlur, onChange } = props;
|
const { lineLimitValue, resolution, queryType, query, onRunQuery, runOnBlur, onChange } = props;
|
||||||
|
|
||||||
function onChangeQueryLimit(value: string) {
|
function onChangeQueryLimit(value: string) {
|
||||||
const nextQuery = { ...query, maxLines: preprocessMaxLines(value) };
|
const nextQuery = { ...query, maxLines: preprocessMaxLines(value) };
|
||||||
@@ -71,6 +85,11 @@ export function LokiOptionFields(props: LokiOptionFieldsProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onResolutionChange(option: SelectableValue<number>) {
|
||||||
|
const nextQuery = { ...query, resolution: option.value };
|
||||||
|
onChange(nextQuery);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div aria-label="Loki extra field" className="gf-form-inline">
|
<div aria-label="Loki extra field" className="gf-form-inline">
|
||||||
{/*Query type field*/}
|
{/*Query type field*/}
|
||||||
@@ -108,7 +127,7 @@ export function LokiOptionFields(props: LokiOptionFieldsProps) {
|
|||||||
)}
|
)}
|
||||||
aria-label="Line limit field"
|
aria-label="Line limit field"
|
||||||
>
|
>
|
||||||
<InlineField label="Line limit">
|
<InlineField label="Line limit" tooltip={'Upper limit for number of log lines returned by query.'}>
|
||||||
<Input
|
<Input
|
||||||
className="width-4"
|
className="width-4"
|
||||||
placeholder="auto"
|
placeholder="auto"
|
||||||
@@ -124,6 +143,14 @@ export function LokiOptionFields(props: LokiOptionFieldsProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
|
<InlineField
|
||||||
|
label="Resolution"
|
||||||
|
tooltip={
|
||||||
|
'Resolution 1/1 sets step parameter of Loki metrics 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.'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Select isSearchable={false} onChange={onResolutionChange} options={RESOLUTION_OPTIONS} value={resolution} />
|
||||||
|
</InlineField>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export function LokiQueryEditor(props: LokiQueryEditorProps) {
|
|||||||
<LokiOptionFields
|
<LokiOptionFields
|
||||||
queryType={query.instant ? 'instant' : 'range'}
|
queryType={query.instant ? 'instant' : 'range'}
|
||||||
lineLimitValue={query?.maxLines?.toString() || ''}
|
lineLimitValue={query?.maxLines?.toString() || ''}
|
||||||
|
resolution={query?.resolution || 1}
|
||||||
query={query}
|
query={query}
|
||||||
onRunQuery={onRunQuery}
|
onRunQuery={onRunQuery}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
queryType="range"
|
queryType="range"
|
||||||
|
resolution={1}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
data={
|
data={
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ exports[`Render LokiQueryEditor with legend should render 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
queryType="range"
|
queryType="range"
|
||||||
|
resolution={1}
|
||||||
runOnBlur={true}
|
runOnBlur={true}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -81,6 +82,7 @@ exports[`Render LokiQueryEditor with legend should update timerange 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
queryType="range"
|
queryType="range"
|
||||||
|
resolution={1}
|
||||||
runOnBlur={true}
|
runOnBlur={true}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ describe('LokiDatasource', () => {
|
|||||||
const req = ds.createRangeQuery(target, options as any, 1000);
|
const req = ds.createRangeQuery(target, options as any, 1000);
|
||||||
expect(req.start).toBeDefined();
|
expect(req.start).toBeDefined();
|
||||||
expect(req.end).toBeDefined();
|
expect(req.end).toBeDefined();
|
||||||
expect(adjustIntervalSpy).toHaveBeenCalledWith(1000, expect.anything());
|
expect(adjustIntervalSpy).toHaveBeenCalledWith(1000, 1, expect.anything());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use provided intervalMs', () => {
|
it('should use provided intervalMs', () => {
|
||||||
@@ -127,7 +127,7 @@ describe('LokiDatasource', () => {
|
|||||||
const req = ds.createRangeQuery(target, options as any, 1000);
|
const req = ds.createRangeQuery(target, options as any, 1000);
|
||||||
expect(req.start).toBeDefined();
|
expect(req.start).toBeDefined();
|
||||||
expect(req.end).toBeDefined();
|
expect(req.end).toBeDefined();
|
||||||
expect(adjustIntervalSpy).toHaveBeenCalledWith(2000, expect.anything());
|
expect(adjustIntervalSpy).toHaveBeenCalledWith(2000, 1, expect.anything());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set the minimal step to 1ms', () => {
|
it('should set the minimal step to 1ms', () => {
|
||||||
@@ -142,7 +142,7 @@ describe('LokiDatasource', () => {
|
|||||||
const req = ds.createRangeQuery(target, options as any, 1000);
|
const req = ds.createRangeQuery(target, options as any, 1000);
|
||||||
expect(req.start).toBeDefined();
|
expect(req.start).toBeDefined();
|
||||||
expect(req.end).toBeDefined();
|
expect(req.end).toBeDefined();
|
||||||
expect(adjustIntervalSpy).toHaveBeenCalledWith(0.0005, expect.anything());
|
expect(adjustIntervalSpy).toHaveBeenCalledWith(0.0005, expect.anything(), 1000);
|
||||||
// Step is in seconds (1 ms === 0.001 s)
|
// Step is in seconds (1 ms === 0.001 s)
|
||||||
expect(req.step).toEqual(0.001);
|
expect(req.step).toEqual(0.001);
|
||||||
});
|
});
|
||||||
@@ -399,7 +399,7 @@ describe('LokiDatasource', () => {
|
|||||||
|
|
||||||
describe('__range, __range_s and __range_ms variables', () => {
|
describe('__range, __range_s and __range_ms variables', () => {
|
||||||
const options = {
|
const options = {
|
||||||
targets: [{ expr: 'rate(process_cpu_seconds_total[$__range])', refId: 'A' }],
|
targets: [{ expr: 'rate(process_cpu_seconds_total[$__range])', refId: 'A', stepInterval: '2s' }],
|
||||||
range: {
|
range: {
|
||||||
from: rawRange.from,
|
from: rawRange.from,
|
||||||
to: rawRange.to,
|
to: rawRange.to,
|
||||||
@@ -581,7 +581,7 @@ describe('LokiDatasource', () => {
|
|||||||
status: 'success',
|
status: 'success',
|
||||||
},
|
},
|
||||||
} as unknown) as FetchResponse;
|
} as unknown) as FetchResponse;
|
||||||
const { promise } = getTestContext(response);
|
const { promise } = getTestContext(response, { stepInterval: '15s' });
|
||||||
|
|
||||||
const res = await promise;
|
const res = await promise;
|
||||||
|
|
||||||
@@ -613,7 +613,7 @@ describe('LokiDatasource', () => {
|
|||||||
} as unknown) as FetchResponse;
|
} as unknown) as FetchResponse;
|
||||||
describe('When tagKeys is set', () => {
|
describe('When tagKeys is set', () => {
|
||||||
it('should only include selected labels', async () => {
|
it('should only include selected labels', async () => {
|
||||||
const { promise } = getTestContext(response, { tagKeys: 'label2,label3' });
|
const { promise } = getTestContext(response, { tagKeys: 'label2,label3', stepInterval: '15s' });
|
||||||
|
|
||||||
const res = await promise;
|
const res = await promise;
|
||||||
|
|
||||||
@@ -624,7 +624,7 @@ describe('LokiDatasource', () => {
|
|||||||
});
|
});
|
||||||
describe('When textFormat is set', () => {
|
describe('When textFormat is set', () => {
|
||||||
it('should fromat the text accordingly', async () => {
|
it('should fromat the text accordingly', async () => {
|
||||||
const { promise } = getTestContext(response, { textFormat: 'hello {{label2}}' });
|
const { promise } = getTestContext(response, { textFormat: 'hello {{label2}}', stepInterval: '15s' });
|
||||||
|
|
||||||
const res = await promise;
|
const res = await promise;
|
||||||
|
|
||||||
@@ -634,7 +634,7 @@ describe('LokiDatasource', () => {
|
|||||||
});
|
});
|
||||||
describe('When titleFormat is set', () => {
|
describe('When titleFormat is set', () => {
|
||||||
it('should fromat the title accordingly', async () => {
|
it('should fromat the title accordingly', async () => {
|
||||||
const { promise } = getTestContext(response, { titleFormat: 'Title {{label2}}' });
|
const { promise } = getTestContext(response, { titleFormat: 'Title {{label2}}', stepInterval: '15s' });
|
||||||
|
|
||||||
const res = await promise;
|
const res = await promise;
|
||||||
|
|
||||||
@@ -781,6 +781,26 @@ describe('LokiDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('adjustInterval', () => {
|
||||||
|
const dynamicInterval = 15;
|
||||||
|
const range = 1642;
|
||||||
|
const resolution = 1;
|
||||||
|
const ds = createLokiDSForTests();
|
||||||
|
it('should return the interval as a factor of dynamicInterval and resolution', () => {
|
||||||
|
let interval = ds.adjustInterval(dynamicInterval, resolution, range);
|
||||||
|
expect(interval).toBe(resolution * dynamicInterval);
|
||||||
|
});
|
||||||
|
it('should not return a value less than the safe interval', () => {
|
||||||
|
let safeInterval = range / 11000;
|
||||||
|
if (safeInterval > 1) {
|
||||||
|
safeInterval = Math.ceil(safeInterval);
|
||||||
|
}
|
||||||
|
const unsafeInterval = safeInterval - 0.01;
|
||||||
|
let interval = ds.adjustInterval(unsafeInterval, resolution, range);
|
||||||
|
expect(interval).toBeGreaterThanOrEqual(safeInterval);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createLokiDSForTests(
|
function createLokiDSForTests(
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import LanguageProvider from './language_provider';
|
|||||||
import { serializeParams } from '../../../core/utils/fetch';
|
import { serializeParams } from '../../../core/utils/fetch';
|
||||||
import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider';
|
import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider';
|
||||||
import syntax from './syntax';
|
import syntax from './syntax';
|
||||||
|
import { DEFAULT_RESOLUTION } from './components/LokiOptionFields';
|
||||||
|
|
||||||
export type RangeQueryOptions = DataQueryRequest<LokiQuery> | AnnotationQueryRequest<LokiQuery>;
|
export type RangeQueryOptions = DataQueryRequest<LokiQuery> | AnnotationQueryRequest<LokiQuery>;
|
||||||
export const DEFAULT_MAX_LINES = 1000;
|
export const DEFAULT_MAX_LINES = 1000;
|
||||||
@@ -186,8 +187,11 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
const startNs = this.getTime(options.range.from, false);
|
const startNs = this.getTime(options.range.from, false);
|
||||||
const endNs = this.getTime(options.range.to, true);
|
const endNs = this.getTime(options.range.to, true);
|
||||||
const rangeMs = Math.ceil((endNs - startNs) / 1e6);
|
const rangeMs = Math.ceil((endNs - startNs) / 1e6);
|
||||||
|
|
||||||
|
const resolution = target.resolution || (DEFAULT_RESOLUTION.value as number);
|
||||||
|
|
||||||
const adjustedInterval =
|
const adjustedInterval =
|
||||||
this.adjustInterval((options as DataQueryRequest<LokiQuery>).intervalMs || 1000, rangeMs) / 1000;
|
this.adjustInterval((options as DataQueryRequest<LokiQuery>).intervalMs || 1000, resolution, rangeMs) / 1000;
|
||||||
// We want to ceil to 3 decimal places
|
// We want to ceil to 3 decimal places
|
||||||
const step = Math.ceil(adjustedInterval * 1000) / 1000;
|
const step = Math.ceil(adjustedInterval * 1000) / 1000;
|
||||||
|
|
||||||
@@ -558,14 +562,28 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async annotationQuery(options: any): Promise<AnnotationEvent[]> {
|
async annotationQuery(options: any): Promise<AnnotationEvent[]> {
|
||||||
const { expr, maxLines, instant, tagKeys = '', titleFormat = '', textFormat = '' } = options.annotation;
|
const {
|
||||||
|
expr,
|
||||||
|
maxLines,
|
||||||
|
instant,
|
||||||
|
stepInterval,
|
||||||
|
tagKeys = '',
|
||||||
|
titleFormat = '',
|
||||||
|
textFormat = '',
|
||||||
|
} = options.annotation;
|
||||||
|
|
||||||
if (!expr) {
|
if (!expr) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const interpolatedExpr = this.templateSrv.replace(expr, {}, this.interpolateQueryExpr);
|
const interpolatedExpr = this.templateSrv.replace(expr, {}, this.interpolateQueryExpr);
|
||||||
const query = { refId: `annotation-${options.annotation.name}`, expr: interpolatedExpr, maxLines, instant };
|
const query = {
|
||||||
|
refId: `annotation-${options.annotation.name}`,
|
||||||
|
expr: interpolatedExpr,
|
||||||
|
maxLines,
|
||||||
|
instant,
|
||||||
|
stepInterval,
|
||||||
|
};
|
||||||
const { data } = instant
|
const { data } = instant
|
||||||
? await this.runInstantQuery(query, options as any).toPromise()
|
? await this.runInstantQuery(query, options as any).toPromise()
|
||||||
: await this.runRangeQuery(query, options as any).toPromise();
|
: await this.runRangeQuery(query, options as any).toPromise();
|
||||||
@@ -634,14 +652,16 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustInterval(interval: number, range: number) {
|
adjustInterval(dynamicInterval: number, resolution: number, range: number) {
|
||||||
// Loki will drop queries that might return more than 11000 data points.
|
// Loki will drop queries that might return more than 11000 data points.
|
||||||
// Calibrate interval if it is too small.
|
// Calibrate interval if it is too small.
|
||||||
if (interval !== 0 && range / interval > 11000) {
|
let safeInterval = range / 11000;
|
||||||
interval = Math.ceil(range / 11000);
|
if (safeInterval > 1) {
|
||||||
|
safeInterval = Math.ceil(safeInterval);
|
||||||
}
|
}
|
||||||
// The min interval is set to 1ms
|
|
||||||
return Math.max(interval, 1);
|
let adjustedInterval = Math.max(resolution * dynamicInterval, safeInterval);
|
||||||
|
return adjustedInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
addAdHocFilters(queryExpr: string) {
|
addAdHocFilters(queryExpr: string) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface LokiQuery extends DataQuery {
|
|||||||
legendFormat?: string;
|
legendFormat?: string;
|
||||||
valueWithRefId?: boolean;
|
valueWithRefId?: boolean;
|
||||||
maxLines?: number;
|
maxLines?: number;
|
||||||
|
resolution?: number;
|
||||||
range?: boolean;
|
range?: boolean;
|
||||||
instant?: boolean;
|
instant?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user