Skip to content

Commit af902ec

Browse files
authored
Add Machine Learning section to docs & example page to demo app (#787)
1 parent fa03a49 commit af902ec

18 files changed

+582
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
id: advanced-callbacks
3+
title: Using callbacks
4+
---
5+
6+
Many of the components in `scenes-ml` allow a callback to be passed in the constructor. For example, in `SceneOutlierDetector`:
7+
8+
```ts
9+
const outlierDetector = new SceneOutlierDetector({
10+
onOutlierDetected: (outlier: Outlier) => {},
11+
});
12+
```
13+
14+
This callback can be used to create more customised experiences specific to your Scene.
15+
16+
For example, you may have a custom scene showing all pods for a given Kubernetes deployment. By enabling the outlier detector, you could use the callback to store all pods and timestamps which appear to be behaving differently, and render a second panel to show the logs for those pods and timestamps.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
id: baselines-and-forecasts
3+
title: Baselines and forecasts
4+
---
5+
6+
import DiscoverSeasonalitiesSvg from '/static/img/discover-seasonalities.svg';
7+
import PinnedSvg from '/static/img/pinned.svg';
8+
9+
**Baselines** provide smoothed estimates of time series data along with lower and upper bounds for the data over time. They can also be used as **forecasts**, using historical data to predict the behaviour of future data.
10+
11+
This functionality can also be used for **anomaly detection** by highlighting timestamps where the true value strayed outside of the lower and upper predicted bounds.
12+
13+
Baselines can be added to panels by using the `SceneBaseliner` component of `scenes-ml`, which will add a control to enable/disable the calculation, adjust the prediction intervals, discover seasonalities, and pin the results.
14+
15+
![Panel with baselines added](/img/baseliner.png)
16+
17+
## Usage
18+
19+
The code example below demonstrates how to add baselines to a time series panel.
20+
21+
```ts
22+
import { SceneBaseliner } from '@grafana/scenes-ml';
23+
24+
// Default values are shown here, all are optional.
25+
const baseliner = new SceneBaseliner({
26+
interval: 0.95,
27+
discoverSeasonalities: false,
28+
pinned: false,
29+
});
30+
const panel = PanelBuilders.timeseries()
31+
.setHeaderActions([baseliner])
32+
.build();
33+
```
34+
35+
:::note
36+
Make sure you only add baselines to **time series** panels, as they rarely make sense for other panel types.
37+
:::
38+
39+
### Pinning results
40+
41+
By default, baselines are recalculated on every state change, i.e. whenever the time range, query or interval changes. This isn't always desirable: for example, the user may want to zoom out and view the current forecasts in a future time range.
42+
43+
Enabling the **pinned <PinnedSvg className="ml-icon" />** setting will freeze the current results, so they won't be recalculated as the time range or other settings are changed.
44+
45+
## Technical details
46+
47+
`scenes-ml` uses the [MSTL][mstl] algorithm to produce in-sample and out-of-sample forecasts. This algorithm decomposes the data into **trend**, **seasonality** and **residuals**, then uses an an [ETS][ets] algorithm to model the trend series.
48+
49+
By default, the algorithm assumes **hourly**, **daily**, **weekly** and **yearly** seasonality (if the data spans at least two of the given season length, i.e. at least two hours for hourly or at least two days for daily).
50+
51+
If the **discover seasonalities <DiscoverSeasonalitiesSvg className="ml-icon"/>** setting is enabled, the baseliner will first attempt to detect any non-standard seasonality in the data using a [periodogram] and account for these seasonalities when modeling the data.
52+
53+
[mstl]: https://arxiv.org/abs/2107.13462
54+
[ets]: https://otexts.com/fpp3/ets-forecasting.html
55+
[periodogram]: https://www.sktime.net/en/latest/api_reference/auto_generated/sktime.param_est.seasonality.SeasonalityPeriodogram.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
id: changepoint-detection
3+
title: Changepoint detection
4+
---
5+
6+
import PinnedSvg from '/static/img/pinned.svg';
7+
8+
**Changepoint detection** attempts to identify timestamps where a time series has changed behaviour. For example, it could be used to identify sudden changes in the **magnitude** or the **variance** of a time series.
9+
10+
The `SceneChangepointDetector` component from `scenes-ml` can be used to add this functionality to all series in a panel. This component will add an annotation at every detected changepoint.
11+
12+
![Panel with changepoints added](/img/changepoints.png)
13+
14+
:::warning
15+
Changepoint detection is currently a beta feature. The underlying algorithm may perform slowly for certain panels, so be sure to test it thoroughly before using it.
16+
:::
17+
18+
## Usage
19+
20+
The code example below demonstrates how to add changepoint detection to a time series panel.
21+
22+
```ts
23+
import { SceneChangepointDetector } from '@grafana/scenes-ml';
24+
25+
// Default values are shown here, all are optional.
26+
const changepointDetector = new SceneChangepointDetector({
27+
enabled: false,
28+
pinned: false,
29+
onChangepointDetected: (changepoint: Changepoint) => {},
30+
});
31+
const panel = PanelBuilders.timeseries()
32+
.setHeaderActions([outlierDetector])
33+
.build();
34+
```
35+
36+
:::note
37+
Make sure you only add changepoint detection to **time series** panels, as it rarely makes sense for other panel types.
38+
:::
39+
40+
### Pinning results
41+
42+
By default, baselines are recalculated on every state change, i.e. whenever the time range, query or interval changes. This isn't always desirable: for example, the user may want to zoom out and view the current forecasts in a future time range.
43+
44+
Enabling the **pinned <PinnedSvg className="ml-icon" />** setting will freeze the current results, so they won't be recalculated as the time range or other settings are changed.
45+
46+
## Technical details
47+
48+
`scenes-ml` currently uses the [AutoRegressive Gaussian Process Change Point detection][argpcp] (ARGPCP) algorithm, which can be slow in some cases. Alternative algorithms may be added in future.
49+
50+
[argpcp]: https://redpoll.ai/blog/changepoint/#autoregressive-gaussian-process-change-point-detector-argpcp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
id: getting-started
3+
title: Getting started
4+
---
5+
6+
`@grafana/scenes-ml` is a separate npm package which lets you add Machine Learning powered functionality to your scenes.
7+
8+
This topic explains hows to install Scenes ML and use it within a Grafana App plugin.
9+
10+
## Installation
11+
12+
If you're adding Scenes ML to an existing Scenes app plugin, first make sure your plugin config is up-to-date by running:
13+
14+
```bash
15+
npx @grafana/create-plugin@latest --update
16+
```
17+
18+
Then add `@grafana/scenes-ml` to your plugin by running the following commands in your project:
19+
20+
```bash
21+
yarn add @grafana/scenes-ml
22+
```
23+
24+
## Add ML features to a scene
25+
26+
### 1. Create a scene
27+
28+
Create a scene using the snippet below. This will add a time series panel to the scene with built-in controls to add trend, lower and upper bounds to all series in the panel.
29+
30+
```ts
31+
// helloMLScene.ts
32+
33+
import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneQueryRunner, PanelBuilders, sceneUtils } from '@grafana/scenes';
34+
import { SceneBaseliner, MLDemoDS } from '@grafana/scenes-ml';
35+
36+
// Register the demo datasource from `scenes-ml`.
37+
// This isn't required for normal usage, it just gives us some sensible demo data.
38+
sceneUtils.registerRuntimeDataSource({ dataSource: new MLDemoDS('ml-test', 'ml-test') })
39+
40+
function getForecastQueryRunner() {
41+
return new SceneQueryRunner({
42+
queries: [
43+
{ refId: 'A', datasource: { uid: 'ml-test', type: 'ml-test', }, type: 'forecasts' },
44+
],
45+
});
46+
}
47+
48+
export function getScene() {
49+
return new EmbeddedScene({
50+
body: new SceneFlexLayout({
51+
children: [
52+
new SceneFlexItem({
53+
width: '50%',
54+
height: 300,
55+
body: PanelBuilders.timeseries()
56+
.setTitle('Forecast demo')
57+
.setData(getForecastQueryRunner())
58+
// Add the `SceneBaseliner` to the panel.
59+
.setHeaderActions([new SceneBaseliner({ interval: 0.95 })])
60+
.build()
61+
}),
62+
],
63+
}),
64+
});
65+
}
66+
```
67+
68+
### 2. Render the scene
69+
70+
Use the following code in your Grafana app plugin page to render the "Hello ML" scene:
71+
72+
```tsx
73+
import React from 'react';
74+
import { getScene } from './helloMLScene';
75+
76+
export const HelloMLPluginPage = () => {
77+
const scene = getScene();
78+
79+
return <scene.Component model={scene} />;
80+
};
81+
```
82+
83+
## Source code
84+
85+
[View the example source code](https://github.com/grafana/scenes/tree/main/docusaurus/docs/scenes-ml/getting-started.tsx)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneQueryRunner, PanelBuilders, sceneUtils } from '@grafana/scenes';
2+
import { SceneBaseliner, MLDemoDS } from '@grafana/scenes-ml';
3+
4+
// Register the demo datasource from `scenes-ml`.
5+
// This isn't required for normal usage, it just gives us some sensible demo data.
6+
sceneUtils.registerRuntimeDataSource({ dataSource: new MLDemoDS('ml-test', 'ml-test') })
7+
8+
function getForecastQueryRunner() {
9+
return new SceneQueryRunner({
10+
queries: [
11+
{ refId: 'A', datasource: { uid: 'ml-test', type: 'ml-test', }, type: 'forecasts' },
12+
],
13+
});
14+
}
15+
16+
export function getScene() {
17+
return new EmbeddedScene({
18+
body: new SceneFlexLayout({
19+
children: [
20+
new SceneFlexItem({
21+
width: '50%',
22+
height: 300,
23+
body: PanelBuilders.timeseries()
24+
.setTitle('Forecast demo')
25+
.setData(getForecastQueryRunner())
26+
// Add the `SceneBaseliner` to the panel.
27+
.setHeaderActions([new SceneBaseliner({ interval: 0.95 })])
28+
.build()
29+
}),
30+
],
31+
}),
32+
});
33+
}
34+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
id: outlier-detection
3+
title: Outlier detection
4+
---
5+
6+
import PinnedSvg from '/static/img/pinned.svg';
7+
8+
**Outlier detection** is the problem of identifying when one or more series within a group is behaving differently to the rest.
9+
10+
`scenes-ml` provides a `SceneOutlierDetector` component which will perform outlier detection and highlight any misbehaving series. It will also add a grey band indicating the 'cluster range' (the range of data that can be considered non-outlying), and (optionally) add annotations at time ranges where an outlier was detected.
11+
12+
![Panel with outliers added](/img/outliers.png)
13+
14+
## Usage
15+
16+
The code example below demonstrates how to add outlier detection to a time series panel.
17+
18+
```ts
19+
import { SceneOutlierDetector } from '@grafana/scenes-ml';
20+
21+
// Default values are shown here, all are optional.
22+
const outlierDetector = new SceneOutlierDetector({
23+
sensitivity: 0.5,
24+
addAnnotations: false,
25+
pinned: false,
26+
onOutlierDetected: (outlier: Outlier) => {},
27+
});
28+
const panel = PanelBuilders.timeseries()
29+
.setHeaderActions([outlierDetector])
30+
.build();
31+
```
32+
33+
:::note
34+
Make sure you only add outlier detection to **time series** panels, as it rarely makes sense for other panel types.
35+
:::
36+
37+
### Pinning results
38+
39+
By default, baselines are recalculated on every state change, i.e. whenever the time range, query or interval changes. This isn't always desirable: for example, the user may want to zoom out and view the current forecasts in a future time range.
40+
41+
Enabling the **pinned <PinnedSvg className="ml-icon" />** setting will freeze the current results, so they won't be recalculated as the time range or other settings are changed.
42+
43+
## Technical details
44+
45+
`scenes-ml` currently uses a variant of the [DBSCAN][dbscan] algorithm to detect outliers. Additional algorithms may be added in future.
46+
47+
[dbscan]: https://en.wikipedia.org/wiki/DBSCAN

docusaurus/website/sidebars.js

+12
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ const sidebars = {
6060
'advanced-time-range-comparison',
6161
],
6262
},
63+
{
64+
type: 'category',
65+
label: '@grafana/scenes-ml',
66+
collapsible: true,
67+
collapsed: false,
68+
items: [
69+
'getting-started',
70+
'baselines-and-forecasts',
71+
'outlier-detection',
72+
'changepoint-detection',
73+
].map(id => `scenes-ml/${id}`),
74+
}
6375
],
6476
};
6577
module.exports = sidebars;

docusaurus/website/src/css/custom.css

+7-3
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,7 @@ html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before
219219
color: #63676d;
220220
}
221221

222-
.algolia-autocomplete.algolia-autocomplete
223-
.algolia-docsearch-suggestion--text
224-
.algolia-docsearch-suggestion--highlight {
222+
.algolia-autocomplete.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight {
225223
box-shadow: inset 0 -2px 0 0 var(--primary-border);
226224
}
227225

@@ -237,3 +235,9 @@ html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before
237235
.algolia-autocomplete.algolia-autocomplete .algolia-docsearch-suggestion--content:before {
238236
background: var(--ifm-toc-border-color);
239237
}
238+
239+
/* icons */
240+
.ml-icon {
241+
height: 16px;
242+
fill: #FFFFFF;
243+
}
131 KB
Loading
40.4 KB
Loading
Loading
65.9 KB
Loading
+4
Loading

packages/scenes-app/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"license": "AGPL-3.0-only",
77
"description": "A basic grafana app plugin",
88
"scripts": {
9-
"build": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -c ./.config/webpack/webpack.config.ts --env production",
10-
"dev": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -w -c ./.config/webpack/webpack.config.ts --env development",
9+
"build": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -c ./webpack.config.ts --env production",
10+
"dev": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -w -c ./webpack.config.ts --env development",
1111
"e2e": "yarn cypress install && TZ=UTC yarn grafana-e2e run",
1212
"test": "jest --passWithNoTests",
1313
"test:ci": "jest --maxWorkers 4",
@@ -63,6 +63,7 @@
6363
"@grafana/data": "^11.0.0",
6464
"@grafana/runtime": "^11.0.0",
6565
"@grafana/scenes": "workspace:*",
66+
"@grafana/scenes-ml": "^0.2.0",
6667
"@grafana/scenes-react": "workspace:*",
6768
"@grafana/schema": "^11.0.0",
6869
"@grafana/ui": "^11.0.0",

packages/scenes-app/src/demos/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { getQueryControllerDemo } from './queryController';
3737
import { getDynamicDataLayersDemo } from './dynamicDataLayers';
3838
import { getInteropDemo } from './interopDemo';
3939
import { getUrlSyncTest } from './urlSyncTest';
40+
import { getMlDemo } from './ml';
4041

4142
export interface DemoDescriptor {
4243
title: string;
@@ -83,5 +84,6 @@ export function getDemos(): DemoDescriptor[] {
8384
{ title: 'Query controller demo', getPage: getQueryControllerDemo },
8485
{ title: 'Interop with hooks and context', getPage: getInteropDemo },
8586
{ title: 'Url sync test', getPage: getUrlSyncTest },
87+
{ title: 'Machine Learning', getPage: getMlDemo },
8688
].sort((a, b) => a.title.localeCompare(b.title));
8789
}

0 commit comments

Comments
 (0)