Skip to content

Commit

Permalink
Merge pull request #59 from grafana/feature/event-refactor
Browse files Browse the repository at this point in the history
Feature/event refactor
  • Loading branch information
szkiba authored Sep 4, 2023
2 parents c1cc16a + bdf6a09 commit bb39a9b
Show file tree
Hide file tree
Showing 118 changed files with 4,879 additions and 2,791 deletions.
138 changes: 89 additions & 49 deletions .dashboard.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,95 @@
// helper for adding p(99) to existing chart
function addP99 (chart) {
chart.series = {
...chart.series,
'http_req_duration_trend_p(99)': { label: 'p(99)', format: 'duration' }
//@ts-check
/**
* @typedef {import('./config').dashboard.Config} Config
* @typedef {import('./config').dashboard.Tab} Tab
* @typedef {import('./config').dashboard.Panel} Panel
* @typedef {import('./config').dashboard.Chart} Chart
*/

/**
* Customize dashboard configuration.
* @param {Config} config default dashboard configuration
* @returns {Config} modified dashboard configuration
*/
export default function (config) {
/**
* Search for an array element that has a given id property value.
* @param {string} id the id for the search
* @returns the first element whose id property matches or is undefined if there are no results
*/
function getById(id) {
return this.filter((/** @type {{ id: string; }} */ element) => element.id == id).at(0)
}
}

// define request duration panel
function durationPanel (suffix) {
return {
id: `http_req_duration_${suffix}`,
title: `HTTP Request Duration ${suffix}`,
metric: `http_req_duration_trend_${suffix}`,
format: 'duration'
// add getById method to all array
Array.prototype["getById"] = getById

/**
* helper for adding p(99) to existing chart
* @param {Chart} chart
*/
function addP99 (chart) {
chart.series = Object.assign({}, chart.series)
chart.series['http_req_duration.p(99)'] = { label: 'p(99)', format: 'duration' }
}
}

// copy vus and http_reqs panel from default config
const overview = defaultConfig.tab('overview_snapshot')

// define custom panels
const customPanels = [
overview.panel('vus'),
overview.panel('http_reqs'),
durationPanel('avg'),
durationPanel('p(90)'),
durationPanel('p(95)'),
durationPanel('p(99)')
]

// copy http_req_duration chart form default config...
const durationChart = { ...overview.chart('http_req_duration') }

// ... and add p(99)
addP99(durationChart)

// uncomment to add cumulative tabs
// defaultConfig.tabs.push(defaultConfig.tabOverview('cumulative'))
// defaultConfig.tabs.push(defaultConfig.tabTimings('cumulative'))

// define custom tab
const customTab = {
id: 'custom',
title: 'Custom',
event: overview.event,
panels: customPanels,
charts: [overview.chart('http_reqs'), durationChart],
description: 'Example of customizing the display of metrics.'
}
/**
* define request duration panel
* @param {string} suffix
* @returns {Panel} panel
*/
function durationPanel (suffix) {
return {
id: `http_req_duration_${suffix}`,
title: `HTTP Request Duration ${suffix}`,
metric: `http_req_duration_trend_${suffix}`,
format: 'duration'
}
}

/**
* reference to overview tab from default config
* @type {Tab}
*/
const overview = config.tabs.getById('overview_snapshot')

// add custom tab to configuration
defaultConfig.tabs.push(customTab)
/**
* define custom panels
* @type {Panel[]}
*/
const customPanels = [
overview.panels.getById('vus'),
overview.panels.getById('http_reqs'),
durationPanel('avg'),
durationPanel('p(90)'),
durationPanel('p(95)'),
durationPanel('p(99)')
]

export default defaultConfig
/**
* copy of the http_req_duration chart form default config
* @type {Chart}
*/
const durationChart = Object.assign({}, overview.charts.getById('http_req_duration'))

// and add p(99)
addP99(durationChart)

/**
* custom tab definition
* @type {Tab}
*/
const customTab = {
id: 'custom',
title: 'Custom',
event: overview.event,
panels: customPanels,
charts: [overview.charts.getById('http_reqs'), durationChart],
description: 'Example of customizing the display of metrics.'
}

// add custom tab to configuration
config.tabs.push(customTab)

return config
}
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,18 @@ linters-settings:
- github.com/gorilla/schema
- github.com/tidwall/gjson
- github.com/r3labs/sse/v2
- github.com/dop251/goja
- github.com/grafana/xk6-dashboard/assets
- github.com/grafana/xk6-dashboard/dashboard
- github.com/grafana/xk6-dashboard/customize
test:
files:
- $test
allow:
- $gostd
- github.com/stretchr/testify/assert
- github.com/sirupsen/logrus
- github.com/dop251/goja
- github.com/tidwall/gjson
- github.com/grafana/xk6-dashboard/assets
- github.com/grafana/xk6-dashboard/dashboard
147 changes: 77 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ host | Hostname or IP address for HTTP endpoint (default: "", empty, listen
port | TCP port for HTTP endpoint (default: `5665`; `0` = random, `-1` = no HTTP), example: `8080`
period | Event emitting frequency (default: `10s`), example: `1m`
open | Set to `true` (or empty) to open the browser window automatically
config | UI configuration file location (default: `.dashboard.js`) (see [Customization](#customization))
report | File name to save the report (dafault: "", empty, the report will not be saved)
report | File name to save the report (default: "", empty, the report will not be saved)
tag | Precomputed metric tag name(s) (default: "group"), can be specified more than once
## Docker
Expand Down Expand Up @@ -190,26 +190,24 @@ The `/events` endpoint (default: http://127.0.0.1:5665/events) is a standard SSE
Events will be emitted periodically based on the `period` parameter (default: `10s`). The event's `data` is a JSON object with metric names as property names and metric values as property values. The format is similar to the [List Metrics](https://k6.io/docs/misc/k6-rest-api/#list-metrics) response format from the [k6 REST API](https://k6.io/docs/misc/k6-rest-api/).
Two kind of events will be emitted:
- `config` contains ui configuration
- `param` contains main extension parameters (period, scenarios, thresholds, etc)
- `start` contains start timestamp
- `stop` contains stop timestamp
- `metric` contains new metric definitions
- `snapshot` contains metric values from last period
- `cumulative` contains cumulative metric values from the test starting point
**Example events**
```plain
event: snapshot
id: 1
data: {"checks":{"type":"rate","contains":"default","tainted":null,"sample":{"rate":0}},"data_received":{"type":"counter","contains":"data","tainted":null,"sample":{"count":11839,"rate":5919.5}},"data_sent":{"type":"counter","contains":"data","tainted":null,"sample":{"count":202,"rate":101}},"http_req_blocked":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":0.0037155,"max":0.00485,"med":0.0037155,"min":0.002581,"p(90)":0.0046231,"p(95)":0.00473655}},"http_req_connecting":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":0,"max":0,"med":0,"min":0,"p(90)":0,"p(95)":0}},"http_req_duration":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":120.917558,"max":120.928988,"med":120.917558,"min":120.906128,"p(90)":120.926702,"p(95)":120.927845}},"http_req_failed":{"type":"rate","contains":"default","tainted":null,"sample":{"rate":0}},"http_req_receiving":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":0.0709745,"max":0.088966,"med":0.0709745,"min":0.052983,"p(90)":0.0853677,"p(95)":0.08716685}},"http_req_sending":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":0.022489500000000003,"max":0.033272,"med":0.022489500000000003,"min":0.011707,"p(90)":0.031115500000000004,"p(95)":0.03219375}},"http_req_tls_handshaking":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":0,"max":0,"med":0,"min":0,"p(90)":0,"p(95)":0}},"http_req_waiting":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":120.824094,"max":120.841438,"med":120.824094,"min":120.80675,"p(90)":120.8379692,"p(95)":120.83970359999999}},"http_reqs":{"type":"counter","contains":"default","tainted":null,"sample":{"count":2,"rate":1}},"iteration_duration":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":3244.614784,"max":3244.614784,"med":3244.614784,"min":3244.614784,"p(90)":3244.614784,"p(95)":3244.614784}},"iterations":{"type":"counter","contains":"default","tainted":null,"sample":{"count":1,"rate":0.5}},"time":{"type":"gauge","contains":"time","tainted":null,"sample":{"value":1679907081015}},"vus":{"type":"gauge","contains":"default","tainted":null,"sample":{"value":1}},"vus_max":{"type":"gauge","contains":"default","tainted":null,"sample":{"value":2}}}
event: cumulative
id: 1
data: {"checks":{"type":"rate","contains":"default","tainted":null,"sample":{"rate":0}},"data_received":{"type":"counter","contains":"data","tainted":null,"sample":{"count":46837,"rate":1115.1362807429666}},"data_sent":{"type":"counter","contains":"data","tainted":null,"sample":{"count":1653,"rate":39.35607045857172}},"http_req_blocked":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":88.12648020000002,"max":456.345376,"med":0.0056419999999999994,"min":0.00219,"p(90)":262.8713841999999,"p(95)":359.60838009999975}},"http_req_connecting":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":37.2988213,"max":131.097342,"med":0,"min":0,"p(90)":122.40998579999999,"p(95)":126.75366389999999}},"http_req_duration":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":123.92543040000001,"max":133.508481,"med":121.77833150000001,"min":120.412089,"p(90)":132.29845799999998,"p(95)":132.9034695}},"http_req_failed":{"type":"rate","contains":"default","tainted":null,"sample":{"rate":0.2}},"http_req_receiving":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":0.10157959999999999,"max":0.337678,"med":0.0826445,"min":0.052983,"p(90)":0.11383719999999992,"p(95)":0.22575759999999973}},"http_req_sending":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":0.035149900000000005,"max":0.096238,"med":0.0272325,"min":0.011707,"p(90)":0.06422679999999999,"p(95)":0.08023239999999997}},"http_req_tls_handshaking":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":38.9789687,"max":268.92473,"med":0,"min":0,"p(90)":135.67093429999994,"p(95)":202.29783214999986}},"http_req_waiting":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":123.78870090000001,"max":133.411013,"med":121.5094465,"min":120.326814,"p(90)":132.15912649999999,"p(95)":132.78506975}},"http_reqs":{"type":"counter","contains":"default","tainted":null,"sample":{"count":10,"rate":0.23808875050557607}},"iteration_duration":{"type":"trend","contains":"time","tainted":null,"sample":{"avg":3626.924762,"max":4258.763721,"med":3377.395781,"min":3244.614784,"p(90)":4082.4901330000002,"p(95)":4170.626927}},"iterations":{"type":"counter","contains":"default","tainted":null,"sample":{"count":3,"rate":0.07142662515167282}},"time":{"type":"gauge","contains":"time","tainted":null,"sample":{"value":1679907081015}},"vus":{"type":"gauge","contains":"default","tainted":null,"sample":{"value":1}},"vus_max":{"type":"gauge","contains":"default","tainted":null,"sample":{"value":2}}}
```
## Customization
The embedded user interface can be customized using a single JavaScript configuration file specified in the `config` parameter (default: `.dashboard.js` in the current directory). The configuration file is an ES6 module that is executed in the browser. The module's default export is a JavaScript configuration object.
The embedded user interface can be customized using a single JavaScript configuration file specified in the `XK6_DASHBOARD_CONFIG` environment variable (default: `.dashboard.js` in the current directory). The configuration file is an ES6 module. The module's default export is a JavaScript function which returns a configuration object. The default configuration is passed as argument to the exported function.
The default configuration is loaded from the [assets/packages/config/dist/config.json](assets/packages/config/dist/config.json) file, which can give you ideas for creating your own configuration.
Before executing the configuration file, the `window.defaultConfig` object is created with the default configuration. The default configuration is loaded from the [ui/assets/ui/public/boot.js](ui/assets/ui/public/boot.js) file, which can give you ideas for creating your own configuration.
> **Warning**
> The format of the custom configuration has changed!
> The stability of the configuration format is still not guaranteed, so you should check the changes before updating the version.
> In addition, it is possible that the custom configuration will be limited or phased out in the future.
### Examples
Expand All @@ -219,74 +217,83 @@ Before executing the configuration file, the `window.defaultConfig` object is cr
In this example, a tab called *Custom* is defined, which contains six panels and two charts. The first two panels are just a reference to the two panels of the built-in *Overview* tab.
```js
// helper for adding p(99) to existing chart
function addP99 (chart) {
chart.series = {
...chart.series,
'http_req_duration_trend_p(99)': { label: 'p(99)' }
export default function (config) {
Array.prototype.getById = function (id) {
return this.filter(element => element.id == id).at(0)
}
}
// define request duration panel
function durationPanel (suffix) {
return {
id: `http_req_duration_${suffix}`,
title: `Request Duration ${suffix}`,
metric: `http_req_duration_trend_${suffix}`,
format: 'duration'
// helper for adding p(99) to existing chart
function addP99 (chart) {
chart.series = Object.assign({}, chart.series)
chart.series['http_req_duration.p(99)'] = { label: 'p(99)', format: 'duration' }
}
}
// copy vus and http_reqs panel from default config
const overview = defaultConfig.tab('overview_snapshot')
// define custom panels
const customPanels = [
overview.panel('vus'),
overview.panel('http_reqs'),
durationPanel('avg'),
durationPanel('p(90)'),
durationPanel('p(95)'),
durationPanel('p(99)')
]
// copy http_req_duration chart form default config...
const durationChart = { ...overview.chart('http_req_duration') }
// ... and add p(99)
addP99(durationChart)
// define custom tab
const customTab = {
id: 'custom',
title: 'Custom',
event: overview.event,
panels: customPanels,
charts: [overview.chart('http_reqs'), durationChart],
description: 'Example of customizing the display of metrics.'
}
// define request duration panel
function durationPanel (suffix) {
return {
id: `http_req_duration_${suffix}`,
title: `HTTP Request Duration ${suffix}`,
metric: `http_req_duration.${suffix}`,
format: 'duration'
}
}
// copy vus and http_reqs panel from default config
const overview = config.tabs.getById('overview_snapshot')
// define custom panels
const customPanels = [
overview.panels.getById('vus'),
overview.panels.getById('http_reqs'),
durationPanel('avg'),
durationPanel('p(90)'),
durationPanel('p(95)'),
durationPanel('p(99)')
]
// copy http_req_duration chart form default config...
const durationChart = Object.assign({}, overview.charts.getById('http_req_duration'))
// ... and add p(99)
addP99(durationChart)
// define custom tab
const customTab = {
id: 'custom',
title: 'Custom',
event: overview.event,
panels: customPanels,
charts: [overview.charts.getById('http_reqs'), durationChart],
description: 'Example of customizing the display of metrics.'
}
// add custom tab to configuration
defaultConfig.tabs.push(customTab)
// add custom tab to configuration
config.tabs.push(customTab)
export default defaultConfig
return config
}
```
**p(99)**
In this example, the 99th percentile value is added to the *Request Duration* chart on the built-in *Overview* tabs.
```js
// helper for adding p(99) to existing chart
function addP99 (chart) {
chart.series['http_req_duration_trend_p(99)'] = { label: 'p(99)' }
}
export default function (config) {
Array.prototype.getById = function (id) {
return this.filter((element) => element.id == id).at(0);
};
// helper for adding p(99) to existing chart
function addP99(chart) {
chart.series["http_req_duration.p(99)"] = { label: "p(99)" };
}
// add p(99) to overview panels request duration charts
addP99(defaultConfig.tab('overview_snapshot').chart('http_req_duration'))
addP99(defaultConfig.tab('overview_cumulative').chart('http_req_duration'))
// add p(99) to overview panels request duration charts
addP99(config.tabs.getById("overview_snapshot").charts.getById("http_req_duration"));
export default defaultConfig
return config
}
```
## Command Line
Expand Down Expand Up @@ -322,12 +329,12 @@ Usage:
k6 dashboard replay file [flags]
Flags:
--config string UI configuration file location (default ".dashboard.js")
--host string Hostname or IP address for HTTP endpoint (default: '', empty, listen on all interfaces)
--open Open browser window automatically
--period 1m Event emitting frequency, example: 1m (default 10s)
--port int TCP port for HTTP endpoint (0=random, -1=no HTTP), example: 8080 (default 5665)
--report string Report file location (default: '', no report)
--tags strings Precomputed metric tags (default [group]) can be specified more than once
-h, --help help for replay
```
Expand Down
Loading

0 comments on commit bb39a9b

Please sign in to comment.