Skip to content

Commit

Permalink
Add Machine Learning section to docs & example page to demo app (#787)
Browse files Browse the repository at this point in the history
  • Loading branch information
sd2k authored Jul 11, 2024
1 parent fa03a49 commit af902ec
Show file tree
Hide file tree
Showing 18 changed files with 582 additions and 6 deletions.
16 changes: 16 additions & 0 deletions docusaurus/docs/scenes-ml/advanced-callbacks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
id: advanced-callbacks
title: Using callbacks
---

Many of the components in `scenes-ml` allow a callback to be passed in the constructor. For example, in `SceneOutlierDetector`:

```ts
const outlierDetector = new SceneOutlierDetector({
onOutlierDetected: (outlier: Outlier) => {},
});
```

This callback can be used to create more customised experiences specific to your Scene.

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.
55 changes: 55 additions & 0 deletions docusaurus/docs/scenes-ml/baselines-and-forecasts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
id: baselines-and-forecasts
title: Baselines and forecasts
---

import DiscoverSeasonalitiesSvg from '/static/img/discover-seasonalities.svg';
import PinnedSvg from '/static/img/pinned.svg';

**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.

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.

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.

![Panel with baselines added](/img/baseliner.png)

## Usage

The code example below demonstrates how to add baselines to a time series panel.

```ts
import { SceneBaseliner } from '@grafana/scenes-ml';

// Default values are shown here, all are optional.
const baseliner = new SceneBaseliner({
interval: 0.95,
discoverSeasonalities: false,
pinned: false,
});
const panel = PanelBuilders.timeseries()
.setHeaderActions([baseliner])
.build();
```

:::note
Make sure you only add baselines to **time series** panels, as they rarely make sense for other panel types.
:::

### Pinning results

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.

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.

## Technical details

`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.

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).

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.

[mstl]: https://arxiv.org/abs/2107.13462
[ets]: https://otexts.com/fpp3/ets-forecasting.html
[periodogram]: https://www.sktime.net/en/latest/api_reference/auto_generated/sktime.param_est.seasonality.SeasonalityPeriodogram.html
50 changes: 50 additions & 0 deletions docusaurus/docs/scenes-ml/changepoint-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
id: changepoint-detection
title: Changepoint detection
---

import PinnedSvg from '/static/img/pinned.svg';

**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.

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.

![Panel with changepoints added](/img/changepoints.png)

:::warning
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.
:::

## Usage

The code example below demonstrates how to add changepoint detection to a time series panel.

```ts
import { SceneChangepointDetector } from '@grafana/scenes-ml';

// Default values are shown here, all are optional.
const changepointDetector = new SceneChangepointDetector({
enabled: false,
pinned: false,
onChangepointDetected: (changepoint: Changepoint) => {},
});
const panel = PanelBuilders.timeseries()
.setHeaderActions([outlierDetector])
.build();
```

:::note
Make sure you only add changepoint detection to **time series** panels, as it rarely makes sense for other panel types.
:::

### Pinning results

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.

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.

## Technical details

`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.

[argpcp]: https://redpoll.ai/blog/changepoint/#autoregressive-gaussian-process-change-point-detector-argpcp
85 changes: 85 additions & 0 deletions docusaurus/docs/scenes-ml/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
id: getting-started
title: Getting started
---

`@grafana/scenes-ml` is a separate npm package which lets you add Machine Learning powered functionality to your scenes.

This topic explains hows to install Scenes ML and use it within a Grafana App plugin.

## Installation

If you're adding Scenes ML to an existing Scenes app plugin, first make sure your plugin config is up-to-date by running:

```bash
npx @grafana/create-plugin@latest --update
```

Then add `@grafana/scenes-ml` to your plugin by running the following commands in your project:

```bash
yarn add @grafana/scenes-ml
```

## Add ML features to a scene

### 1. Create a scene

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.

```ts
// helloMLScene.ts

import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneQueryRunner, PanelBuilders, sceneUtils } from '@grafana/scenes';
import { SceneBaseliner, MLDemoDS } from '@grafana/scenes-ml';

// Register the demo datasource from `scenes-ml`.
// This isn't required for normal usage, it just gives us some sensible demo data.
sceneUtils.registerRuntimeDataSource({ dataSource: new MLDemoDS('ml-test', 'ml-test') })

function getForecastQueryRunner() {
return new SceneQueryRunner({
queries: [
{ refId: 'A', datasource: { uid: 'ml-test', type: 'ml-test', }, type: 'forecasts' },
],
});
}

export function getScene() {
return new EmbeddedScene({
body: new SceneFlexLayout({
children: [
new SceneFlexItem({
width: '50%',
height: 300,
body: PanelBuilders.timeseries()
.setTitle('Forecast demo')
.setData(getForecastQueryRunner())
// Add the `SceneBaseliner` to the panel.
.setHeaderActions([new SceneBaseliner({ interval: 0.95 })])
.build()
}),
],
}),
});
}
```

### 2. Render the scene

Use the following code in your Grafana app plugin page to render the "Hello ML" scene:

```tsx
import React from 'react';
import { getScene } from './helloMLScene';

export const HelloMLPluginPage = () => {
const scene = getScene();

return <scene.Component model={scene} />;
};
```

## Source code

[View the example source code](https://github.com/grafana/scenes/tree/main/docusaurus/docs/scenes-ml/getting-started.tsx)
34 changes: 34 additions & 0 deletions docusaurus/docs/scenes-ml/getting-started.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneQueryRunner, PanelBuilders, sceneUtils } from '@grafana/scenes';
import { SceneBaseliner, MLDemoDS } from '@grafana/scenes-ml';

// Register the demo datasource from `scenes-ml`.
// This isn't required for normal usage, it just gives us some sensible demo data.
sceneUtils.registerRuntimeDataSource({ dataSource: new MLDemoDS('ml-test', 'ml-test') })

function getForecastQueryRunner() {
return new SceneQueryRunner({
queries: [
{ refId: 'A', datasource: { uid: 'ml-test', type: 'ml-test', }, type: 'forecasts' },
],
});
}

export function getScene() {
return new EmbeddedScene({
body: new SceneFlexLayout({
children: [
new SceneFlexItem({
width: '50%',
height: 300,
body: PanelBuilders.timeseries()
.setTitle('Forecast demo')
.setData(getForecastQueryRunner())
// Add the `SceneBaseliner` to the panel.
.setHeaderActions([new SceneBaseliner({ interval: 0.95 })])
.build()
}),
],
}),
});
}

47 changes: 47 additions & 0 deletions docusaurus/docs/scenes-ml/outlier-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
id: outlier-detection
title: Outlier detection
---

import PinnedSvg from '/static/img/pinned.svg';

**Outlier detection** is the problem of identifying when one or more series within a group is behaving differently to the rest.

`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.

![Panel with outliers added](/img/outliers.png)

## Usage

The code example below demonstrates how to add outlier detection to a time series panel.

```ts
import { SceneOutlierDetector } from '@grafana/scenes-ml';

// Default values are shown here, all are optional.
const outlierDetector = new SceneOutlierDetector({
sensitivity: 0.5,
addAnnotations: false,
pinned: false,
onOutlierDetected: (outlier: Outlier) => {},
});
const panel = PanelBuilders.timeseries()
.setHeaderActions([outlierDetector])
.build();
```

:::note
Make sure you only add outlier detection to **time series** panels, as it rarely makes sense for other panel types.
:::

### Pinning results

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.

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.

## Technical details

`scenes-ml` currently uses a variant of the [DBSCAN][dbscan] algorithm to detect outliers. Additional algorithms may be added in future.

[dbscan]: https://en.wikipedia.org/wiki/DBSCAN
12 changes: 12 additions & 0 deletions docusaurus/website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ const sidebars = {
'advanced-time-range-comparison',
],
},
{
type: 'category',
label: '@grafana/scenes-ml',
collapsible: true,
collapsed: false,
items: [
'getting-started',
'baselines-and-forecasts',
'outlier-detection',
'changepoint-detection',
].map(id => `scenes-ml/${id}`),
}
],
};
module.exports = sidebars;
10 changes: 7 additions & 3 deletions docusaurus/website/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,7 @@ html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before
color: #63676d;
}

.algolia-autocomplete.algolia-autocomplete
.algolia-docsearch-suggestion--text
.algolia-docsearch-suggestion--highlight {
.algolia-autocomplete.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight {
box-shadow: inset 0 -2px 0 0 var(--primary-border);
}

Expand All @@ -237,3 +235,9 @@ html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before
.algolia-autocomplete.algolia-autocomplete .algolia-docsearch-suggestion--content:before {
background: var(--ifm-toc-border-color);
}

/* icons */
.ml-icon {
height: 16px;
fill: #FFFFFF;
}
Binary file added docusaurus/website/static/img/baseliner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docusaurus/website/static/img/changepoints.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions docusaurus/website/static/img/discover-seasonalities.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docusaurus/website/static/img/outliers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docusaurus/website/static/img/pinned.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions packages/scenes-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"license": "AGPL-3.0-only",
"description": "A basic grafana app plugin",
"scripts": {
"build": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -c ./.config/webpack/webpack.config.ts --env production",
"dev": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -w -c ./.config/webpack/webpack.config.ts --env development",
"build": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -c ./webpack.config.ts --env production",
"dev": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -w -c ./webpack.config.ts --env development",
"e2e": "yarn cypress install && TZ=UTC yarn grafana-e2e run",
"test": "jest --passWithNoTests",
"test:ci": "jest --maxWorkers 4",
Expand Down Expand Up @@ -63,6 +63,7 @@
"@grafana/data": "^11.0.0",
"@grafana/runtime": "^11.0.0",
"@grafana/scenes": "workspace:*",
"@grafana/scenes-ml": "^0.2.0",
"@grafana/scenes-react": "workspace:*",
"@grafana/schema": "^11.0.0",
"@grafana/ui": "^11.0.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/scenes-app/src/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { getQueryControllerDemo } from './queryController';
import { getDynamicDataLayersDemo } from './dynamicDataLayers';
import { getInteropDemo } from './interopDemo';
import { getUrlSyncTest } from './urlSyncTest';
import { getMlDemo } from './ml';

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

0 comments on commit af902ec

Please sign in to comment.