Skip to content

Commit

Permalink
Merge pull request #17 from szkiba/feature/customization
Browse files Browse the repository at this point in the history
Feature/customization
  • Loading branch information
szkiba authored May 3, 2023
2 parents 7515249 + 36d08da commit e6c446c
Show file tree
Hide file tree
Showing 36 changed files with 996 additions and 300 deletions.
50 changes: 50 additions & 0 deletions .dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// helper for adding p(99) to existing chart
function addP99 (chart) {
chart.series = {
...chart.series,
'http_req_duration_trend_p(99)': { label: 'p(99)' }
}
}

// 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'
}
}

// 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]
}

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

export default defaultConfig
Binary file added .github/xk6-dashboard-social.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
112 changes: 112 additions & 0 deletions .github/xk6-dashboard-social.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 98 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,20 @@ By using **xk6-dashboard** output extension you can access metrics from [k6](htt

**Screenshots**

![k6 dashboard snapshot](screenshot/k6-dashboard-snapshot.png)
*Overview*
![k6 dashboard overview snapshot](screenshot/k6-dashboard-overview-snapshot.png)

![k6 dashboard cumulative](screenshot/k6-dashboard-cumulative.png)
*Overview Cumulative*
![k6 dashboard overview cumulative](screenshot/k6-dashboard-overview-cumulative.png)

*Timings*
![k6 dashboard timings snapshot](screenshot/k6-dashboard-timings-snapshot.png)

*Timings Cumulative*
![k6 dashboard timings cumulative](screenshot/k6-dashboard-timings-cumulative.png)

*Custom Tab*
![k6 dashboard custom](screenshot/k6-dashboard-custom.png)


**Table of Contents**
Expand All @@ -24,6 +35,8 @@ By using **xk6-dashboard** output extension you can access metrics from [k6](htt
- [Parameters](#parameters)
- [Docker](#docker)
- [Events](#events)
- [Customization](#customization)
- [Examples](#examples)

## Download

Expand Down Expand Up @@ -86,6 +99,7 @@ host | Hostname or IP address for HTTP endpoint (default: "", empty, listen
port | TCP port for HTTP endpoint (default: `5665`), example: `8080`
period | Event emitting frequency (default: `10s`), example: `1m`
open | Set to `true` (or empty) for opening browser window automatically
config | UI configuration file location (default: `.dashboard.js`) (see [Customization](#customization))

## Docker

Expand Down Expand Up @@ -125,3 +139,85 @@ event: cumulative
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.

Before executing the configuration file, the `window.defaultConfig` object is created with the default configuration. The default configuration is loaded from the [public/boot.js](public/boot.js) file, which can give you ideas for creating your own configuration.

### Examples

**Custom tab**
![k6 dashboard custom](screenshot/k6-dashboard-custom.png)

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)' }
}
}

// 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'
}
}

// 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]
}

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

export default defaultConfig
```

**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)' }
}

// 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'))

export default defaultConfig
```
2 changes: 1 addition & 1 deletion dashboard/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (ext *Extension) Start() error {

ext.cumulative = newMeter(0)

ext.server = newWebServer(ext.uiFS, ext.logger)
ext.server = newWebServer(ext.uiFS, ext.options.Config, ext.logger)

go func() {
if err := ext.server.listenAndServe(ext.options.addr()); err != nil {
Expand Down
3 changes: 3 additions & 0 deletions dashboard/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ const (
defaultPort = 5665
defaultPeriod = time.Second * 10
defaultOpen = false
defaultConfig = ".dashboard.js"
)

type options struct {
Port int
Host string
Period time.Duration
Open bool
Config string
}

func getopts(query string) (opts *options, err error) { // nolint:nonamedreturns
Expand All @@ -35,6 +37,7 @@ func getopts(query string) (opts *options, err error) { // nolint:nonamedreturns
Host: defaultHost,
Period: defaultPeriod,
Open: defaultOpen,
Config: defaultConfig,
}

if query == "" {
Expand Down
4 changes: 3 additions & 1 deletion dashboard/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func Test_getopts_defaults(t *testing.T) {
assert.Equal(t, defaultPort, opts.Port)
assert.Equal(t, defaultPeriod, opts.Period)
assert.Equal(t, defaultOpen, opts.Open)
assert.Equal(t, defaultConfig, opts.Config)

assert.Equal(t, fmt.Sprintf("http://%s", net.JoinHostPort("127.0.0.1", strconv.Itoa(defaultPort))), opts.url())
}
Expand All @@ -37,12 +38,13 @@ func Test_getopts_error(t *testing.T) {
func Test_getopts(t *testing.T) {
t.Parallel()

opts, err := getopts("period=1s&port=1&host=localhost&open")
opts, err := getopts("period=1s&port=1&host=localhost&open&config=dashboard.js")

assert.NoError(t, err)
assert.Equal(t, time.Second, opts.Period)
assert.Equal(t, 1, opts.Port)
assert.True(t, opts.Open)
assert.Equal(t, "dashboard.js", opts.Config)
assert.Equal(t, "localhost", opts.Host)
assert.Equal(t, "http://localhost:1", opts.url())
assert.Equal(t, "localhost:1", opts.addr())
Expand Down
13 changes: 12 additions & 1 deletion dashboard/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,19 @@ func (reg *registry) format(dur time.Duration) map[string]v1.Metric {
continue
}

out[name] = v1.NewMetric(metric, dur)
v1metric := v1.NewMetric(metric, dur)

if sink, ok := metric.Sink.(*metrics.TrendSink); ok {
v1metric.Sample[pc99Name] = sink.P(pc99)
}

out[name] = v1metric
}

return out
}

const (
pc99 = 0.99
pc99Name = "p(99)"
)
Loading

0 comments on commit e6c446c

Please sign in to comment.