From 8a0c4109c5a1042ef2cca2420ac89f3fea978277 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Fri, 28 Apr 2023 16:53:49 +0200 Subject: [PATCH 01/14] build: Switch to mage build tool --- .github/workflows/build.yml | 22 ----- .github/workflows/release.yml | 22 ----- .github/workflows/test.yml | 50 +++++++++++ .gitignore | 14 +-- Taskfile.yml | 83 ------------------ magefiles/.gitignore | 5 ++ magefiles/go.mod | 22 +++++ magefiles/go.sum | 37 ++++++++ magefiles/magefile.go | 158 ++++++++++++++++++++++++++++++++++ scripts/test.js | 26 ++++++ 10 files changed, 301 insertions(+), 138 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 Taskfile.yml create mode 100644 magefiles/.gitignore create mode 100644 magefiles/go.mod create mode 100644 magefiles/go.sum create mode 100644 magefiles/magefile.go create mode 100644 scripts/test.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d6cf98..e723856 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,25 +1,3 @@ -# MIT License -# -# Copyright (c) 2021 Iván Szkiba -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - name: Build on: pull_request: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8cf5602..5f23cf4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,25 +1,3 @@ -# MIT License -# -# Copyright (c) 2021 Iván Szkiba -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - name: Release on: push: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1d69c1c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2023 Iván Szkiba +# +# SPDX-License-Identifier: MIT + +name: Test +on: + push: + branches: + - master + tags: + - "v*" + pull_request: +jobs: + test: + strategy: + matrix: + platform: + - ubuntu-latest + - macos-latest + - windows-latest + runs-on: ${{matrix.platform}} + steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: 1.20.x + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Lint + if: ${{ matrix.platform == 'ubuntu-latest' }} + uses: magefile/mage-action@v1 + with: + version: latest + args: lint + + - name: Test + uses: magefile/mage-action@v1 + with: + version: latest + args: test + + - name: Upload Coverage + if: ${{ matrix.platform == 'ubuntu-latest' }} + uses: codecov/codecov-action@v2 + + - name: Generate Go Report Card + if: ${{ matrix.platform == 'ubuntu-latest' }} + uses: creekorful/goreportcard-action@v1.0 diff --git a/.gitignore b/.gitignore index 15e17a2..50c8a35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,19 @@ -# custom binary k6 -# taskfile.io +coverage.txt + .task -# Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* +bin node_modules *.local -# Editor directories and files .vscode/* !.vscode/extensions.json !.vscode/settings.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/Taskfile.yml b/Taskfile.yml deleted file mode 100644 index 2fe72d9..0000000 --- a/Taskfile.yml +++ /dev/null @@ -1,83 +0,0 @@ -# MIT License -# -# Copyright (c) 2021 Iván Szkiba -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -version: "3" - -env: - K6_VERSION: v0.43.1 - -silent: true - -tasks: - default: - cmds: - - task: test - - clean: - desc: Clean up working directory - cmds: - - rm -f k6 - - license: - cmds: - - addlicense -ignore '**/node_modules/**' -ignore '.task/**' -ignore '**/dist/**' -f LICENSE . internal assets - - lint: - desc: Run linter - sources: - - "**/*.go" - - "*.go" - cmds: - - golangci-lint run - - ui: - sources: - - "src/**" - - "public/**" - - "index.html" - - "vite.config.js" - - "package.json" - - "yarn.lock" - dir: assets/ui - cmds: - - yarn build - - build: - deps: [lint, ui] - sources: - - "**/*.go" - - "*.go" - generates: - - k6 - cmds: - - xk6 build --with github.com/szkiba/xk6-dashboard=$(pwd) - - test: - deps: [build] - cmds: - - ./k6 run --out dashboard='period=10s' --no-usage-report script.js - - exif: - dir: screenshot - cmds: - - exiftool -all= -overwrite_original -ext png . - - exiftool -ext png -overwrite_original -XMP:Subject+="k6 dashboard xk6" -Title="k6 dashboard screenshot" -Description="Screenshot of xk6-dashboard extension that enables creating web based metrics dashboard for k6." -Author="Ivan SZKIBA" . diff --git a/magefiles/.gitignore b/magefiles/.gitignore new file mode 100644 index 0000000..3d68590 --- /dev/null +++ b/magefiles/.gitignore @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2023 Iván Szkiba +# +# SPDX-License-Identifier: MIT + +bin diff --git a/magefiles/go.mod b/magefiles/go.mod new file mode 100644 index 0000000..10a7600 --- /dev/null +++ b/magefiles/go.mod @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT + +module github.com/szkiba/xk6-mock/magefiles + +go 1.18 + +require ( + github.com/magefile/mage v1.14.0 + github.com/princjef/mageutil v1.0.0 +) + +require ( + github.com/VividCortex/ewma v1.1.1 // indirect + github.com/cheggaaa/pb/v3 v3.0.4 // indirect + github.com/fatih/color v1.9.0 // indirect + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-runewidth v0.0.7 // indirect + golang.org/x/sys v0.5.0 // indirect +) diff --git a/magefiles/go.sum b/magefiles/go.sum new file mode 100644 index 0000000..7ff8a90 --- /dev/null +++ b/magefiles/go.sum @@ -0,0 +1,37 @@ +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/cheggaaa/pb v2.0.7+incompatible/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/cheggaaa/pb/v3 v3.0.4 h1:QZEPYOj2ix6d5oEg63fbHmpolrnNiwjUsk+h74Yt4bM= +github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= +github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/matryer/is v1.3.0 h1:9qiso3jaJrOe6qBRJRBt2Ldht05qDiFP9le0JOIhRSI= +github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/princjef/mageutil v1.0.0 h1:1OfZcJUMsooPqieOz2ooLjI+uHUo618pdaJsbCXcFjQ= +github.com/princjef/mageutil v1.0.0/go.mod h1:mkShhaUomCYfAoVvTKRcbAs8YSVPdtezI5j6K+VXhrs= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710= +gopkg.in/cheggaaa/pb.v2 v2.0.7/go.mod h1:0CiZ1p8pvtxBlQpLXkHuUTpdJ1shm3OqCF1QugkjHL4= +gopkg.in/fatih/color.v1 v1.7.0/go.mod h1:P7yosIhqIl/sX8J8UypY5M+dDpD2KmyfP5IRs5v/fo0= +gopkg.in/mattn/go-colorable.v0 v0.1.0/go.mod h1:BVJlBXzARQxdi3nZo6f6bnl5yR20/tOL6p+V0KejgSY= +gopkg.in/mattn/go-isatty.v0 v0.0.4/go.mod h1:wt691ab7g0X4ilKZNmMII3egK0bTxl37fEn/Fwbd8gc= +gopkg.in/mattn/go-runewidth.v0 v0.0.4/go.mod h1:BmXejnxvhwdaATwiJbB1vZ2dtXkQKZGu9yLFCZb4msQ= diff --git a/magefiles/magefile.go b/magefiles/magefile.go new file mode 100644 index 0000000..e90a211 --- /dev/null +++ b/magefiles/magefile.go @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT + +//go:build mage +// +build mage + +package main + +import ( + "path/filepath" + "strings" + + "github.com/magefile/mage/sh" + "github.com/princjef/mageutil/bintool" + "github.com/princjef/mageutil/shellcmd" +) + +var Default = All + +var linter = bintool.Must(bintool.New( + "golangci-lint{{.BinExt}}", + "1.51.1", + "https://github.com/golangci/golangci-lint/releases/download/v{{.Version}}/golangci-lint-{{.Version}}-{{.GOOS}}-{{.GOARCH}}{{.ArchiveExt}}", +)) + +func Lint() error { + if err := linter.Ensure(); err != nil { + return err + } + + return linter.Command(`run`).Run() +} + +func Test() error { + return shellcmd.Command(`go test -count 1 -coverprofile=coverage.txt ./...`).Run() +} + +func Build() error { + return shellcmd.Command(`xk6 build --with github.com/szkiba/xk6-dashboard=.`).Run() +} + +func It() error { + all, err := filepath.Glob("scripts/*.js") + if err != nil { + return err + } + + for _, script := range all { + err := xk6run("--out dashboard='period=5s' " + script).Run() + if err != nil { + return err + } + } + + return nil +} + +func Coverage() error { + return shellcmd.Command(`go tool cover -html=coverage.txt`).Run() +} + +func glob(patterns ...string) (string, error) { + buff := new(strings.Builder) + + for _, p := range patterns { + m, err := filepath.Glob(p) + if err != nil { + return "", err + } + + _, err = buff.WriteString(strings.Join(m, " ") + " ") + if err != nil { + return "", err + } + } + + return buff.String(), nil +} + +func License() error { + all, err := glob( + "*.go", + "*/*.go", + ".*.yml", + ".gitignore", + "*/.gitignore", + "*.ts", + "*/*ts", + ".github/workflows/*", + "assets/ui/*.yml", + "assets/ui/*.js", + "assets/ui/*.html", + "assets/ui/.gitignore", + "assets/ui/src/*", + ) + if err != nil { + return err + } + + return shellcmd.Command( + `reuse annotate --copyright "Iván Szkiba" --merge-copyrights --license MIT --skip-unrecognised ` + all, + ).Run() +} + +func Clean() error { + sh.Rm("magefiles/bin") + sh.Rm("coverage.txt") + sh.Rm("bin") + sh.Rm("assets/ui/node_modules") + sh.Rm("assets/ui/node_modules") + sh.Rm("k6") + + return nil +} + +func All() error { + if err := Lint(); err != nil { + return err + } + + if err := Test(); err != nil { + return err + } + + if err := Build(); err != nil { + return err + } + + return It() +} + +func yarn(arg string) shellcmd.Command { + return shellcmd.Command("yarn --silent --cwd assets/ui " + arg) +} + +func Prepare() error { + return yarn("install").Run() +} + +func Ui() error { + return yarn("build").Run() +} + +func Exif() error { + return shellcmd.RunAll( + `exiftool -all= -overwrite_original -ext png screenshot`, + `exiftool -ext png -overwrite_original -XMP:Subject+="k6 dashboard xk6" -Title="k6 dashboard screenshot" -Description="Screenshot of xk6-dashboard extension that enables creating web based metrics dashboard for k6." -Author="Ivan SZKIBA" screenshot`, + ) +} + +func xk6run(arg string) shellcmd.Command { + return shellcmd.Command("xk6 run --quiet --no-summary --no-usage-report " + arg) +} + +func Run() error { + return xk6run(`--out dashboard='period=10s' script.js`).Run() +} diff --git a/scripts/test.js b/scripts/test.js new file mode 100644 index 0000000..8d53af4 --- /dev/null +++ b/scripts/test.js @@ -0,0 +1,26 @@ +import http from "k6/http"; +import { sleep } from "k6"; + +export let options = { + discardResponseBodies: true, + scenarios: { + contacts: { + executor: "ramping-vus", + startVUs: 1, + stages: [ + { duration: "10s", target: 2 }, + { duration: "30s", target: 10 }, + { duration: "20s", target: 2 }, + { duration: "30s", target: 10 }, + { duration: "20s", target: 3 }, + { duration: "10s", target: 1 }, + ], + gracefulRampDown: "0s", + }, + }, +}; + +export default function () { + http.get("http://test.k6.io"); + sleep(1); +} From 86d60a90859b80de3b744d19246d8a98b6058a4a Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Fri, 28 Apr 2023 16:54:16 +0200 Subject: [PATCH 02/14] docs: Small grammar corrections --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 55ab0ff..c0214cc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A [k6 extension](https://k6.io/docs/extensions/) that enables creating web based metrics dashboard for [k6](https://k6.io). -Using **xk6-dashboard** output extension you can access metrics from [k6](https://k6.io) process via [Server-sent events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). All custom [k6](https://k6.io) metrics ([Counter](https://k6.io/docs/javascript-api/k6-metrics/counter/),[Gauge](https://k6.io/docs/javascript-api/k6-metrics/gauge/),[Rate](https://k6.io/docs/javascript-api/k6-metrics/rate/),[Trend](https://k6.io/docs/javascript-api/k6-metrics/trend/)) and [build-in metrics](https://k6.io/docs/using-k6/metrics/#built-in-metrics) will be accessible in event stream. +By using **xk6-dashboard** output extension you can access metrics from [k6](https://k6.io) process via [Server-sent events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). All custom [k6](https://k6.io) metrics ([Counter](https://k6.io/docs/javascript-api/k6-metrics/counter/),[Gauge](https://k6.io/docs/javascript-api/k6-metrics/gauge/),[Rate](https://k6.io/docs/javascript-api/k6-metrics/rate/),[Trend](https://k6.io/docs/javascript-api/k6-metrics/trend/)) and [build-in metrics](https://k6.io/docs/using-k6/metrics/#built-in-metrics) will be accessible in the event stream. **Screenshots** @@ -45,7 +45,7 @@ Then: ## Usage -Without parameters the dashboard will accessible on port `5665` with any web browser: http://127.0.0.1:5665 +Without parameters the dashboard will be accessible on port `5665` with any web browser: http://127.0.0.1:5665 ```plain $ ./k6 run --out dashboard script.js @@ -63,7 +63,7 @@ $ ./k6 run --out dashboard script.js ## Parameters -The output extension accept parameters in a standard query string format: +The output extension accepts parameters in a standard query string format: ``` k6 run --out 'dashboard=param1=value1¶m2=value2¶m3=value3' @@ -81,7 +81,7 @@ period | Event emitting frequency (default: `10s`), example: `1m` ## Docker -You can also use pre-built k6 image within a Docker container. To do that, you'll need to execute something more-or-less like the following: +You can also use pre-built k6 image within a Docker container. In order to do that, you will need to execute something like the following: **Linux** @@ -103,9 +103,9 @@ The `/events` endpoint (default: http://127.0.0.1:5665/events) is a standard SSE Events will be emitted periodically, based on `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 [List Metrics](https://k6.io/docs/misc/k6-rest-api/#list-metrics) response format from [k6 REST API](https://k6.io/docs/misc/k6-rest-api/). -Two kind of events emitted: +Two kind of events will be emitted: - `snapshot` contains metric values from last period - - `cumulative` contains cumulative metric values from the test start + - `cumulative` contains cumulative metric values from the test starting point **Example events** From ef4ac1a6a9552b79888338355071fa209d9aa484 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Fri, 28 Apr 2023 16:57:11 +0200 Subject: [PATCH 03/14] chore: Simplify license header --- .github/workflows/build.yml | 4 ++++ .github/workflows/release.yml | 4 ++++ .gitignore | 4 ++++ .golangci.yml | 22 ++-------------------- LICENSES/MIT.txt | 9 +++++++++ assets/ui/.gitignore | 4 ++++ assets/ui/index.html | 23 +++-------------------- assets/ui/src/App.css | 25 +++---------------------- assets/ui/src/App.jsx | 26 +++----------------------- assets/ui/src/Chart.css | 24 +++--------------------- assets/ui/src/Chart.jsx | 26 +++----------------------- assets/ui/src/Charts.jsx | 26 +++----------------------- assets/ui/src/Panel.css | 24 +++--------------------- assets/ui/src/Panel.jsx | 26 +++----------------------- assets/ui/src/Panels.jsx | 26 +++----------------------- assets/ui/src/format.js | 26 +++----------------------- assets/ui/src/index.css | 24 +++--------------------- assets/ui/src/main.jsx | 26 +++----------------------- assets/ui/src/metrics-uplot.js | 26 +++----------------------- assets/ui/src/metrics.js | 26 +++----------------------- assets/ui/vite.config.js | 26 +++----------------------- extension.go | 22 ++-------------------- internal/dashboard.go | 22 ++-------------------- internal/meter.go | 22 ++-------------------- internal/options.go | 22 ++-------------------- internal/registry.go | 22 ++-------------------- internal/sse.go | 22 ++-------------------- internal/web.go | 22 ++-------------------- script.js | 24 ------------------------ 29 files changed, 86 insertions(+), 519 deletions(-) create mode 100644 LICENSES/MIT.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e723856..bfdd89e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 Iván Szkiba +# +# SPDX-License-Identifier: MIT + name: Build on: pull_request: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f23cf4..762fff7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 Iván Szkiba +# +# SPDX-License-Identifier: MIT + name: Release on: push: diff --git a/.gitignore b/.gitignore index 50c8a35..992dbfe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 Iván Szkiba +# +# SPDX-License-Identifier: MIT + k6 coverage.txt diff --git a/.golangci.yml b/.golangci.yml index e0e218b..d566728 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,24 +1,6 @@ -# MIT License +# SPDX-FileCopyrightText: 2021 - 2023 Iván Szkiba # -# Copyright (c) 2021 Iván Szkiba -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. +# SPDX-License-Identifier: MIT linters: presets: diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/assets/ui/.gitignore b/assets/ui/.gitignore index 9e8ffed..a1d4229 100644 --- a/assets/ui/.gitignore +++ b/assets/ui/.gitignore @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 Iván Szkiba +# +# SPDX-License-Identifier: MIT + # Logs logs *.log diff --git a/assets/ui/index.html b/assets/ui/index.html index 36f0b49..8176c9a 100644 --- a/assets/ui/index.html +++ b/assets/ui/index.html @@ -1,26 +1,9 @@ - diff --git a/assets/ui/src/App.css b/assets/ui/src/App.css index 6f5f944..c080d7b 100644 --- a/assets/ui/src/App.css +++ b/assets/ui/src/App.css @@ -1,24 +1,5 @@ -/** - * MIT License +/* + * SPDX-FileCopyrightText: 2023 Iván Szkiba * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * SPDX-License-Identifier: MIT */ - diff --git a/assets/ui/src/App.jsx b/assets/ui/src/App.jsx index 4d9310d..7605636 100644 --- a/assets/ui/src/App.jsx +++ b/assets/ui/src/App.jsx @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import React from 'react'; import './App.css' diff --git a/assets/ui/src/Chart.css b/assets/ui/src/Chart.css index 00b0d60..ab00698 100644 --- a/assets/ui/src/Chart.css +++ b/assets/ui/src/Chart.css @@ -1,25 +1,7 @@ -/** - * MIT License +/* + * SPDX-FileCopyrightText: 2023 Iván Szkiba * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * SPDX-License-Identifier: MIT */ .u-title, diff --git a/assets/ui/src/Chart.jsx b/assets/ui/src/Chart.jsx index 4f9e5ec..36fb23c 100644 --- a/assets/ui/src/Chart.jsx +++ b/assets/ui/src/Chart.jsx @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import React, { useContext, useRef } from 'react' import { MetricsContext } from './metrics' diff --git a/assets/ui/src/Charts.jsx b/assets/ui/src/Charts.jsx index 86168bb..2de6381 100644 --- a/assets/ui/src/Charts.jsx +++ b/assets/ui/src/Charts.jsx @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import { Chart } from './Chart'; diff --git a/assets/ui/src/Panel.css b/assets/ui/src/Panel.css index 7229e1a..7d7cbf5 100644 --- a/assets/ui/src/Panel.css +++ b/assets/ui/src/Panel.css @@ -1,25 +1,7 @@ -/** - * MIT License +/* + * SPDX-FileCopyrightText: 2023 Iván Szkiba * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * SPDX-License-Identifier: MIT */ .summary-panel .u-title { diff --git a/assets/ui/src/Panel.jsx b/assets/ui/src/Panel.jsx index 85084f4..ecebfa1 100644 --- a/assets/ui/src/Panel.jsx +++ b/assets/ui/src/Panel.jsx @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import React, { useContext, useRef } from 'react' import { MetricsContext } from './metrics' diff --git a/assets/ui/src/Panels.jsx b/assets/ui/src/Panels.jsx index bf91ef8..cc5327b 100644 --- a/assets/ui/src/Panels.jsx +++ b/assets/ui/src/Panels.jsx @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import { Panel } from './Panel'; diff --git a/assets/ui/src/format.js b/assets/ui/src/format.js index 6563867..530ab21 100644 --- a/assets/ui/src/format.js +++ b/assets/ui/src/format.js @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import numeral from 'numeral' import prettyBytes from 'pretty-bytes' diff --git a/assets/ui/src/index.css b/assets/ui/src/index.css index e1a8a3b..ebb5ac2 100644 --- a/assets/ui/src/index.css +++ b/assets/ui/src/index.css @@ -1,25 +1,7 @@ -/** - * MIT License +/* + * SPDX-FileCopyrightText: 2023 Iván Szkiba * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * SPDX-License-Identifier: MIT */ body { diff --git a/assets/ui/src/main.jsx b/assets/ui/src/main.jsx index d275cdd..a87fadb 100644 --- a/assets/ui/src/main.jsx +++ b/assets/ui/src/main.jsx @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import React from 'react' import ReactDOM from 'react-dom/client' diff --git a/assets/ui/src/metrics-uplot.js b/assets/ui/src/metrics-uplot.js index c57b035..45af553 100644 --- a/assets/ui/src/metrics-uplot.js +++ b/assets/ui/src/metrics-uplot.js @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import { propTime } from './metrics' diff --git a/assets/ui/src/metrics.js b/assets/ui/src/metrics.js index e5dbb92..cac603d 100644 --- a/assets/ui/src/metrics.js +++ b/assets/ui/src/metrics.js @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import React from 'react' import { useSSE } from 'react-hooks-sse' diff --git a/assets/ui/vite.config.js b/assets/ui/vite.config.js index ed96e93..16a3423 100644 --- a/assets/ui/vite.config.js +++ b/assets/ui/vite.config.js @@ -1,26 +1,6 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT import { defineConfig } from 'vite' import react from '@vitejs/plugin-react-swc' diff --git a/extension.go b/extension.go index 2927afe..6039253 100644 --- a/extension.go +++ b/extension.go @@ -1,24 +1,6 @@ -// MIT License +// SPDX-FileCopyrightText: 2021 - 2023 Iván Szkiba // -// Copyright (c) 2021 Iván Szkiba -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// SPDX-License-Identifier: MIT package dashboard diff --git a/internal/dashboard.go b/internal/dashboard.go index 913f674..29d02a6 100644 --- a/internal/dashboard.go +++ b/internal/dashboard.go @@ -1,24 +1,6 @@ -// MIT License +// SPDX-FileCopyrightText: 2021 - 2023 Iván Szkiba // -// Copyright (c) 2021 Iván Szkiba -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// SPDX-License-Identifier: MIT package internal diff --git a/internal/meter.go b/internal/meter.go index 4dd95d4..851c7a9 100644 --- a/internal/meter.go +++ b/internal/meter.go @@ -1,24 +1,6 @@ -// MIT License +// SPDX-FileCopyrightText: 2023 Iván Szkiba // -// Copyright (c) 2023 Iván Szkiba -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// SPDX-License-Identifier: MIT package internal diff --git a/internal/options.go b/internal/options.go index b0e43ec..c6d2f75 100644 --- a/internal/options.go +++ b/internal/options.go @@ -1,24 +1,6 @@ -// MIT License +// SPDX-FileCopyrightText: 2023 Iván Szkiba // -// Copyright (c) 2023 Iván Szkiba -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// SPDX-License-Identifier: MIT package internal diff --git a/internal/registry.go b/internal/registry.go index 3e14ab0..5642604 100644 --- a/internal/registry.go +++ b/internal/registry.go @@ -1,24 +1,6 @@ -// MIT License +// SPDX-FileCopyrightText: 2023 Iván Szkiba // -// Copyright (c) 2023 Iván Szkiba -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// SPDX-License-Identifier: MIT package internal diff --git a/internal/sse.go b/internal/sse.go index 0964639..01cd946 100644 --- a/internal/sse.go +++ b/internal/sse.go @@ -1,24 +1,6 @@ -// MIT License +// SPDX-FileCopyrightText: 2021 - 2023 Iván Szkiba // -// Copyright (c) 2021 Iván Szkiba -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// SPDX-License-Identifier: MIT package internal diff --git a/internal/web.go b/internal/web.go index 8630edc..ef36d4c 100644 --- a/internal/web.go +++ b/internal/web.go @@ -1,24 +1,6 @@ -// MIT License +// SPDX-FileCopyrightText: 2023 Iván Szkiba // -// Copyright (c) 2023 Iván Szkiba -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// SPDX-License-Identifier: MIT package internal diff --git a/script.js b/script.js index 6d376f8..7f6f195 100644 --- a/script.js +++ b/script.js @@ -1,27 +1,3 @@ -/** - * MIT License - * - * Copyright (c) 2023 Iván Szkiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - import http from "k6/http"; import { sleep } from "k6"; From a164b3ad5bc6f69665d116519063259a27cf3ac1 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Fri, 28 Apr 2023 17:39:02 +0200 Subject: [PATCH 04/14] refactor: Separate logic and registration --- .golangci.yml | 1 + dashboard/extension.go | 112 ++++++++++++++++++++++++++++ {internal => dashboard}/meter.go | 2 +- {internal => dashboard}/options.go | 2 +- {internal => dashboard}/registry.go | 2 +- {internal => dashboard}/sse.go | 2 +- {internal => dashboard}/web.go | 2 +- go.mod | 4 + go.sum | 5 ++ internal/dashboard.go | 112 ---------------------------- extension.go => register.go | 13 ++-- register_test.go | 29 +++++++ 12 files changed, 164 insertions(+), 122 deletions(-) create mode 100644 dashboard/extension.go rename {internal => dashboard}/meter.go (98%) rename {internal => dashboard}/options.go (98%) rename {internal => dashboard}/registry.go (98%) rename {internal => dashboard}/sse.go (98%) rename {internal => dashboard}/web.go (98%) delete mode 100644 internal/dashboard.go rename extension.go => register.go (56%) create mode 100644 register_test.go diff --git a/.golangci.yml b/.golangci.yml index d566728..a519621 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,6 +13,7 @@ linters: enable: - exportloopref disable: + - testpackage - nolintlint - gochecknoglobals - gochecknoinits diff --git a/dashboard/extension.go b/dashboard/extension.go new file mode 100644 index 0000000..f33c776 --- /dev/null +++ b/dashboard/extension.go @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2021 - 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT + +package dashboard + +import ( + "fmt" + "io/fs" + + "github.com/sirupsen/logrus" + "go.k6.io/k6/metrics" + "go.k6.io/k6/output" +) + +type Extension struct { + buffer *output.SampleBuffer + + flusher *output.PeriodicFlusher + logger logrus.FieldLogger + + uiFS fs.FS + server *WebServer + + options *Options + + cumulative *Meter + + description string +} + +var _ output.Output = (*Extension)(nil) + +func New(params output.Params, uiFS fs.FS) (*Extension, error) { + opts, err := ParseOptions(params.ConfigArgument) + if err != nil { + return nil, err + } + + dash := &Extension{ + uiFS: uiFS, + logger: params.Logger, + options: opts, + description: fmt.Sprintf("%s (%s) %s", params.OutputType, opts.Addr(), opts.URL()), + buffer: nil, + server: nil, + flusher: nil, + cumulative: nil, + } + + return dash, nil +} + +func (ext *Extension) Description() string { + return ext.description +} + +func (ext *Extension) Start() error { + var err error + + ext.cumulative = NewMeter(0) + + ext.server, err = NewWebServer(ext.uiFS, ext.logger) + if err != nil { + return err + } + + go func() { + if err := ext.server.ListenAndServe(ext.options.Addr()); err != nil { + ext.logger.Error(err) + } + }() + + ext.buffer = new(output.SampleBuffer) + + flusher, err := output.NewPeriodicFlusher(ext.options.Period, ext.flush) + if err != nil { + return err + } + + ext.flusher = flusher + + return nil +} + +func (ext *Extension) Stop() error { + ext.flusher.Stop() + + return nil +} + +func (ext *Extension) AddMetricSamples(samples []metrics.SampleContainer) { + ext.buffer.AddMetricSamples(samples) +} + +func (ext *Extension) flush() { + samples := ext.buffer.GetBufferedSamples() + + ext.updateAndSend(samples, NewMeter(ext.options.Period), snapshotEvent) + ext.updateAndSend(samples, ext.cumulative, cumulativeEvent) +} + +func (ext *Extension) updateAndSend(containers []metrics.SampleContainer, meter *Meter, event string) { + data, err := meter.Update(containers) + if err != nil { + ext.logger.WithError(err).Warn("Error while processing samples") + + return + } + + ext.server.SendEvent(event, data) +} diff --git a/internal/meter.go b/dashboard/meter.go similarity index 98% rename from internal/meter.go rename to dashboard/meter.go index 851c7a9..5049d5f 100644 --- a/internal/meter.go +++ b/dashboard/meter.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -package internal +package dashboard import ( "time" diff --git a/internal/options.go b/dashboard/options.go similarity index 98% rename from internal/options.go rename to dashboard/options.go index c6d2f75..341bc54 100644 --- a/internal/options.go +++ b/dashboard/options.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -package internal +package dashboard import ( "net" diff --git a/internal/registry.go b/dashboard/registry.go similarity index 98% rename from internal/registry.go rename to dashboard/registry.go index 5642604..67d4ec6 100644 --- a/internal/registry.go +++ b/dashboard/registry.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -package internal +package dashboard import ( "time" diff --git a/internal/sse.go b/dashboard/sse.go similarity index 98% rename from internal/sse.go rename to dashboard/sse.go index 01cd946..291f081 100644 --- a/internal/sse.go +++ b/dashboard/sse.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -package internal +package dashboard import ( "encoding/json" diff --git a/internal/web.go b/dashboard/web.go similarity index 98% rename from internal/web.go rename to dashboard/web.go index ef36d4c..171ad51 100644 --- a/internal/web.go +++ b/dashboard/web.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -package internal +package dashboard import ( "io/fs" diff --git a/go.mod b/go.mod index 18afd92..9d0499e 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,12 @@ require ( github.com/alexandrevicenzi/go-sse v1.6.0 github.com/gorilla/schema v1.2.0 github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.8.0 go.k6.io/k6 v0.43.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -17,9 +19,11 @@ require ( github.com/mattn/go-isatty v0.0.16 // indirect github.com/mstoykov/atlas v0.0.0-20220808085829-90340e9998bd // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.1.2 // indirect golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect gopkg.in/guregu/null.v3 v3.3.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ff2a79b..cde3617 100644 --- a/go.sum +++ b/go.sum @@ -55,9 +55,12 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= @@ -79,8 +82,10 @@ golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxb google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/guregu/null.v3 v3.3.0 h1:8j3ggqq+NgKt/O7mbFVUFKUMWN+l1AmT5jQmJ6nPh2c= gopkg.in/guregu/null.v3 v3.3.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/dashboard.go b/internal/dashboard.go deleted file mode 100644 index 29d02a6..0000000 --- a/internal/dashboard.go +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-FileCopyrightText: 2021 - 2023 Iván Szkiba -// -// SPDX-License-Identifier: MIT - -package internal - -import ( - "fmt" - "io/fs" - - "github.com/sirupsen/logrus" - "go.k6.io/k6/metrics" - "go.k6.io/k6/output" -) - -type Dashboard struct { - buffer *output.SampleBuffer - - flusher *output.PeriodicFlusher - logger logrus.FieldLogger - - uiFS fs.FS - server *WebServer - - options *Options - - cumulative *Meter - - description string -} - -var _ output.Output = (*Dashboard)(nil) - -func NewDashboard(params output.Params, uiFS fs.FS) (*Dashboard, error) { //nolint:ireturn - opts, err := ParseOptions(params.ConfigArgument) - if err != nil { - return nil, err - } - - dash := &Dashboard{ - uiFS: uiFS, - logger: params.Logger, - options: opts, - description: fmt.Sprintf("%s (%s) %s", params.OutputType, opts.Addr(), opts.URL()), - buffer: nil, - server: nil, - flusher: nil, - cumulative: nil, - } - - return dash, nil -} - -func (dash *Dashboard) Description() string { - return dash.description -} - -func (dash *Dashboard) Start() error { - var err error - - dash.cumulative = NewMeter(0) - - dash.server, err = NewWebServer(dash.uiFS, dash.logger) - if err != nil { - return err - } - - go func() { - if err := dash.server.ListenAndServe(dash.options.Addr()); err != nil { - dash.logger.Error(err) - } - }() - - dash.buffer = new(output.SampleBuffer) - - flusher, err := output.NewPeriodicFlusher(dash.options.Period, dash.flush) - if err != nil { - return err - } - - dash.flusher = flusher - - return nil -} - -func (dash *Dashboard) Stop() error { - dash.flusher.Stop() - - return nil -} - -func (dash *Dashboard) AddMetricSamples(samples []metrics.SampleContainer) { - dash.buffer.AddMetricSamples(samples) -} - -func (dash *Dashboard) flush() { - samples := dash.buffer.GetBufferedSamples() - - dash.updateAndSend(samples, NewMeter(dash.options.Period), snapshotEvent) - dash.updateAndSend(samples, dash.cumulative, cumulativeEvent) -} - -func (dash *Dashboard) updateAndSend(containers []metrics.SampleContainer, meter *Meter, event string) { - data, err := meter.Update(containers) - if err != nil { - dash.logger.WithError(err).Warn("Error while processing samples") - - return - } - - dash.server.SendEvent(event, data) -} diff --git a/extension.go b/register.go similarity index 56% rename from extension.go rename to register.go index 6039253..576dbd0 100644 --- a/extension.go +++ b/register.go @@ -8,7 +8,7 @@ import ( "embed" "io/fs" - "github.com/szkiba/xk6-dashboard/internal" + "github.com/szkiba/xk6-dashboard/dashboard" "go.k6.io/k6/output" ) @@ -17,16 +17,19 @@ const distDir = "assets/ui/dist" //go:embed assets/ui/dist var distFS embed.FS -// Register the extensions on module initialization. func init() { - output.RegisterExtension("dashboard", New) + register() } -func New(params output.Params) (output.Output, error) { //nolint:ireturn +func register() { + output.RegisterExtension("dashboard", ctor) +} + +func ctor(params output.Params) (output.Output, error) { //nolint:ireturn uiFS, err := fs.Sub(distFS, distDir) if err != nil { return nil, err } - return internal.NewDashboard(params, uiFS) + return dashboard.New(params, uiFS) } diff --git a/register_test.go b/register_test.go new file mode 100644 index 0000000..fdb26dc --- /dev/null +++ b/register_test.go @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT + +package dashboard + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.k6.io/k6/output" +) + +func TestRegister(t *testing.T) { + t.Parallel() + + assert.Panics(t, register) // already registered +} + +func Test_ctor(t *testing.T) { + t.Parallel() + + var params output.Params + + ext, err := ctor(params) + + assert.NoError(t, err) + assert.NotNil(t, ext) +} From f486f8cd7944faab880cc7b2b627e6641a2f8978 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Fri, 28 Apr 2023 17:49:43 +0200 Subject: [PATCH 05/14] refactor: Change visibility of internal symbols --- dashboard/extension.go | 29 +++++++++++++++-------------- dashboard/meter.go | 28 ++++++++++++++-------------- dashboard/options.go | 10 +++++----- dashboard/registry.go | 24 ++++++++++++------------ dashboard/sse.go | 8 ++++---- dashboard/web.go | 14 +++++++------- 6 files changed, 57 insertions(+), 56 deletions(-) diff --git a/dashboard/extension.go b/dashboard/extension.go index f33c776..bc851dd 100644 --- a/dashboard/extension.go +++ b/dashboard/extension.go @@ -20,35 +20,36 @@ type Extension struct { logger logrus.FieldLogger uiFS fs.FS - server *WebServer + server *webServer - options *Options + options *options - cumulative *Meter + cumulative *meter description string } var _ output.Output = (*Extension)(nil) +// New creates Exension instance using the passwd uiFS as source of web UI. func New(params output.Params, uiFS fs.FS) (*Extension, error) { - opts, err := ParseOptions(params.ConfigArgument) + opts, err := getopts(params.ConfigArgument) if err != nil { return nil, err } - dash := &Extension{ + ext := &Extension{ uiFS: uiFS, logger: params.Logger, options: opts, - description: fmt.Sprintf("%s (%s) %s", params.OutputType, opts.Addr(), opts.URL()), + description: fmt.Sprintf("%s (%s) %s", params.OutputType, opts.addr(), opts.url()), buffer: nil, server: nil, flusher: nil, cumulative: nil, } - return dash, nil + return ext, nil } func (ext *Extension) Description() string { @@ -58,15 +59,15 @@ func (ext *Extension) Description() string { func (ext *Extension) Start() error { var err error - ext.cumulative = NewMeter(0) + ext.cumulative = newMeter(0) - ext.server, err = NewWebServer(ext.uiFS, ext.logger) + ext.server, err = newWebServer(ext.uiFS, ext.logger) if err != nil { return err } go func() { - if err := ext.server.ListenAndServe(ext.options.Addr()); err != nil { + if err := ext.server.listenAndServe(ext.options.addr()); err != nil { ext.logger.Error(err) } }() @@ -96,17 +97,17 @@ func (ext *Extension) AddMetricSamples(samples []metrics.SampleContainer) { func (ext *Extension) flush() { samples := ext.buffer.GetBufferedSamples() - ext.updateAndSend(samples, NewMeter(ext.options.Period), snapshotEvent) + ext.updateAndSend(samples, newMeter(ext.options.Period), snapshotEvent) ext.updateAndSend(samples, ext.cumulative, cumulativeEvent) } -func (ext *Extension) updateAndSend(containers []metrics.SampleContainer, meter *Meter, event string) { - data, err := meter.Update(containers) +func (ext *Extension) updateAndSend(containers []metrics.SampleContainer, m *meter, event string) { + data, err := m.update(containers) if err != nil { ext.logger.WithError(err).Warn("Error while processing samples") return } - ext.server.SendEvent(event, data) + ext.server.sendEvent(event, data) } diff --git a/dashboard/meter.go b/dashboard/meter.go index 5049d5f..7d5852e 100644 --- a/dashboard/meter.go +++ b/dashboard/meter.go @@ -11,23 +11,23 @@ import ( "go.k6.io/k6/metrics" ) -type Meter struct { - registry *Registry +type meter struct { + registry *registry clock *metrics.GaugeSink period time.Duration start time.Time } -func NewMeter(period time.Duration) *Meter { - registry := NewRegistry() - metric := registry.MustGetOrNew("time", metrics.Gauge, metrics.Time) +func newMeter(period time.Duration) *meter { + registry := newRegistry() + metric := registry.mustGetOrNew("time", metrics.Gauge, metrics.Time) clock, _ := metric.Sink.(*metrics.GaugeSink) start := time.Now() clock.Value = float64(start.UnixMilli()) - return &Meter{ + return &meter{ registry: registry, start: start, clock: clock, @@ -35,29 +35,29 @@ func NewMeter(period time.Duration) *Meter { } } -func (meter *Meter) Update(containers []metrics.SampleContainer) (map[string]v1.Metric, error) { +func (m *meter) update(containers []metrics.SampleContainer) (map[string]v1.Metric, error) { now := time.Now() - dur := meter.period + dur := m.period if dur == 0 { - dur = now.Sub(meter.start) + dur = now.Sub(m.start) } - meter.clock.Value = float64(now.UnixMilli()) + m.clock.Value = float64(now.UnixMilli()) for _, container := range containers { for _, sample := range container.GetSamples() { - if err := meter.add(sample); err != nil { + if err := m.add(sample); err != nil { return nil, err } } } - return meter.registry.Format(dur), nil + return m.registry.format(dur), nil } -func (meter *Meter) add(sample metrics.Sample) error { - metric, err := meter.registry.GetOrNew(sample.Metric.Name, sample.Metric.Type, sample.Metric.Contains) +func (m *meter) add(sample metrics.Sample) error { + metric, err := m.registry.getOrNew(sample.Metric.Name, sample.Metric.Type, sample.Metric.Contains) if err != nil { return err } diff --git a/dashboard/options.go b/dashboard/options.go index 341bc54..d60fe22 100644 --- a/dashboard/options.go +++ b/dashboard/options.go @@ -20,14 +20,14 @@ const ( defaultPeriod = time.Second * 10 ) -type Options struct { +type options struct { Port int Host string Period time.Duration } -func ParseOptions(query string) (*Options, error) { - opts := &Options{ +func getopts(query string) (*options, error) { + opts := &options{ Port: defaultPort, Host: defaultHost, Period: defaultPeriod, @@ -60,11 +60,11 @@ func ParseOptions(query string) (*Options, error) { return opts, nil } -func (opts *Options) Addr() string { +func (opts *options) addr() string { return net.JoinHostPort(opts.Host, strconv.Itoa(opts.Port)) } -func (opts *Options) URL() string { +func (opts *options) url() string { host := opts.Host if host == "" { host = "127.0.0.1" diff --git a/dashboard/registry.go b/dashboard/registry.go index 67d4ec6..b8ecc99 100644 --- a/dashboard/registry.go +++ b/dashboard/registry.go @@ -11,22 +11,22 @@ import ( "go.k6.io/k6/metrics" ) -// Registry is what can create metrics and make them iterable. -type Registry struct { +// registry is what can create metrics and make them iterable. +type registry struct { *metrics.Registry names []string } -// NewRegistry returns a new Registry. -func NewRegistry() *Registry { - return &Registry{ +// newRegistry returns a new registry. +func newRegistry() *registry { + return ®istry{ Registry: metrics.NewRegistry(), names: make([]string, 0), } } -// GetOrNew returns existing metric or create new metric registered to this Registry. -func (reg *Registry) GetOrNew(name string, typ metrics.MetricType, valTyp ...metrics.ValueType) (*metrics.Metric, error) { +// getOrNew returns existing metric or create new metric registered to this registry. +func (reg *registry) getOrNew(name string, typ metrics.MetricType, valTyp ...metrics.ValueType) (*metrics.Metric, error) { if metric := reg.Registry.Get(name); metric != nil { return metric, nil } @@ -41,9 +41,9 @@ func (reg *Registry) GetOrNew(name string, typ metrics.MetricType, valTyp ...met return metric, nil } -// MustGetOrNew is like GetOrNew, but will panic if there is an error. -func (reg *Registry) MustGetOrNew(name string, typ metrics.MetricType, valTyp ...metrics.ValueType) *metrics.Metric { - metric, err := reg.GetOrNew(name, typ, valTyp...) +// mustGetOrNew is like getOrNew, but will panic if there is an error. +func (reg *registry) mustGetOrNew(name string, typ metrics.MetricType, valTyp ...metrics.ValueType) *metrics.Metric { + metric, err := reg.getOrNew(name, typ, valTyp...) if err != nil { panic(err) } @@ -51,8 +51,8 @@ func (reg *Registry) MustGetOrNew(name string, typ metrics.MetricType, valTyp .. return metric } -// Format creates k6 REST API v1 compatible output map from all registered metrics. -func (reg *Registry) Format(dur time.Duration) map[string]v1.Metric { +// format creates k6 REST API v1 compatible output map from all registered metrics. +func (reg *registry) format(dur time.Duration) map[string]v1.Metric { out := make(map[string]v1.Metric, len(reg.names)) for _, name := range reg.names { diff --git a/dashboard/sse.go b/dashboard/sse.go index 291f081..07728d8 100644 --- a/dashboard/sse.go +++ b/dashboard/sse.go @@ -12,14 +12,14 @@ import ( "github.com/sirupsen/logrus" ) -type EventSource struct { +type eventSource struct { *sse.Server logger logrus.FieldLogger channel string } -func NewEventSource(channel string, logger logrus.FieldLogger) *EventSource { - esrc := &EventSource{ +func newEventSource(channel string, logger logrus.FieldLogger) *eventSource { + esrc := &eventSource{ channel: channel, logger: logger, Server: sse.NewServer(&sse.Options{ @@ -35,7 +35,7 @@ func NewEventSource(channel string, logger logrus.FieldLogger) *EventSource { return esrc } -func (esrc *EventSource) SendEvent(name string, data interface{}) { +func (esrc *eventSource) sendEvent(name string, data interface{}) { if !esrc.HasChannel(esrc.channel) { return } diff --git a/dashboard/web.go b/dashboard/web.go index 171ad51..f159d76 100644 --- a/dashboard/web.go +++ b/dashboard/web.go @@ -23,27 +23,27 @@ const ( cumulativeEvent = "cumulative" ) -type WebServer struct { - *EventSource +type webServer struct { + *eventSource *http.ServeMux } -func NewWebServer(uiFS fs.FS, logger logrus.FieldLogger) (*WebServer, error) { //nolint:ireturn - srv := &WebServer{ - EventSource: NewEventSource(eventChannel, logger), +func newWebServer(uiFS fs.FS, logger logrus.FieldLogger) (*webServer, error) { //nolint:ireturn + srv := &webServer{ + eventSource: newEventSource(eventChannel, logger), ServeMux: http.NewServeMux(), } uiHandler := http.StripPrefix(pathUI, http.FileServer(http.FS(uiFS))) - srv.Handle(pathEvents, srv.EventSource) + srv.Handle(pathEvents, srv.eventSource) srv.Handle(pathUI, uiHandler) srv.HandleFunc("/", rootHandler(pathUI)) return srv, nil } -func (srv *WebServer) ListenAndServe(addr string) error { +func (srv *webServer) listenAndServe(addr string) error { listener, err := net.Listen("tcp", addr) if err != nil { return err From 17ebd829d474d07b45c42fb9d8c40c2d1cf6022b Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Mon, 1 May 2023 11:55:46 +0200 Subject: [PATCH 06/14] refactor: move embedded ui to its own package --- assets/ui/dist/index.html | 39 ------------------- magefiles/magefile.go | 16 ++++---- register.go | 16 +------- {assets => ui/assets}/ui/.gitignore | 0 .../assets}/ui/dist/assets/index-73e04efd.css | 0 .../assets}/ui/dist/assets/index-a61e8bc7.js | 0 ui/assets/ui/dist/index.html | 22 +++++++++++ .../assets}/ui/dist/xk6-dashboard.svg | 0 {assets => ui/assets}/ui/index.html | 0 {assets => ui/assets}/ui/package.json | 0 .../assets}/ui/public/xk6-dashboard.svg | 0 {assets => ui/assets}/ui/src/App.css | 0 {assets => ui/assets}/ui/src/App.jsx | 0 {assets => ui/assets}/ui/src/Chart.css | 0 {assets => ui/assets}/ui/src/Chart.jsx | 0 {assets => ui/assets}/ui/src/Charts.jsx | 0 {assets => ui/assets}/ui/src/Panel.css | 0 {assets => ui/assets}/ui/src/Panel.jsx | 0 {assets => ui/assets}/ui/src/Panels.jsx | 0 {assets => ui/assets}/ui/src/format.js | 0 {assets => ui/assets}/ui/src/index.css | 0 {assets => ui/assets}/ui/src/main.jsx | 0 {assets => ui/assets}/ui/src/metrics-uplot.js | 0 {assets => ui/assets}/ui/src/metrics.js | 0 {assets => ui/assets}/ui/vite.config.js | 0 {assets => ui/assets}/ui/yarn.lock | 0 ui/ui.go | 20 ++++++++++ ui/ui_test.go | 22 +++++++++++ 28 files changed, 74 insertions(+), 61 deletions(-) delete mode 100644 assets/ui/dist/index.html rename {assets => ui/assets}/ui/.gitignore (100%) rename {assets => ui/assets}/ui/dist/assets/index-73e04efd.css (100%) rename {assets => ui/assets}/ui/dist/assets/index-a61e8bc7.js (100%) create mode 100644 ui/assets/ui/dist/index.html rename {assets => ui/assets}/ui/dist/xk6-dashboard.svg (100%) rename {assets => ui/assets}/ui/index.html (100%) rename {assets => ui/assets}/ui/package.json (100%) rename {assets => ui/assets}/ui/public/xk6-dashboard.svg (100%) rename {assets => ui/assets}/ui/src/App.css (100%) rename {assets => ui/assets}/ui/src/App.jsx (100%) rename {assets => ui/assets}/ui/src/Chart.css (100%) rename {assets => ui/assets}/ui/src/Chart.jsx (100%) rename {assets => ui/assets}/ui/src/Charts.jsx (100%) rename {assets => ui/assets}/ui/src/Panel.css (100%) rename {assets => ui/assets}/ui/src/Panel.jsx (100%) rename {assets => ui/assets}/ui/src/Panels.jsx (100%) rename {assets => ui/assets}/ui/src/format.js (100%) rename {assets => ui/assets}/ui/src/index.css (100%) rename {assets => ui/assets}/ui/src/main.jsx (100%) rename {assets => ui/assets}/ui/src/metrics-uplot.js (100%) rename {assets => ui/assets}/ui/src/metrics.js (100%) rename {assets => ui/assets}/ui/vite.config.js (100%) rename {assets => ui/assets}/ui/yarn.lock (100%) create mode 100644 ui/ui.go create mode 100644 ui/ui_test.go diff --git a/assets/ui/dist/index.html b/assets/ui/dist/index.html deleted file mode 100644 index edb4323..0000000 --- a/assets/ui/dist/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - k6 dashboard - - - - -
- - - diff --git a/magefiles/magefile.go b/magefiles/magefile.go index e90a211..f1b2c35 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -88,11 +88,11 @@ func License() error { "*.ts", "*/*ts", ".github/workflows/*", - "assets/ui/*.yml", - "assets/ui/*.js", - "assets/ui/*.html", - "assets/ui/.gitignore", - "assets/ui/src/*", + "ui/assets/ui/*.yml", + "ui/assets/ui/*.js", + "ui/assets/ui/*.html", + "ui/assets/ui/.gitignore", + "ui/assets/ui/src/*", ) if err != nil { return err @@ -107,8 +107,8 @@ func Clean() error { sh.Rm("magefiles/bin") sh.Rm("coverage.txt") sh.Rm("bin") - sh.Rm("assets/ui/node_modules") - sh.Rm("assets/ui/node_modules") + sh.Rm("ui/assets/ui/node_modules") + sh.Rm("ui/assets/ui/node_modules") sh.Rm("k6") return nil @@ -131,7 +131,7 @@ func All() error { } func yarn(arg string) shellcmd.Command { - return shellcmd.Command("yarn --silent --cwd assets/ui " + arg) + return shellcmd.Command("yarn --silent --cwd ui/assets/ui " + arg) } func Prepare() error { diff --git a/register.go b/register.go index 576dbd0..0dd2bf1 100644 --- a/register.go +++ b/register.go @@ -5,18 +5,11 @@ package dashboard import ( - "embed" - "io/fs" - "github.com/szkiba/xk6-dashboard/dashboard" + "github.com/szkiba/xk6-dashboard/ui" "go.k6.io/k6/output" ) -const distDir = "assets/ui/dist" - -//go:embed assets/ui/dist -var distFS embed.FS - func init() { register() } @@ -26,10 +19,5 @@ func register() { } func ctor(params output.Params) (output.Output, error) { //nolint:ireturn - uiFS, err := fs.Sub(distFS, distDir) - if err != nil { - return nil, err - } - - return dashboard.New(params, uiFS) + return dashboard.New(params, ui.GetFS()) } diff --git a/assets/ui/.gitignore b/ui/assets/ui/.gitignore similarity index 100% rename from assets/ui/.gitignore rename to ui/assets/ui/.gitignore diff --git a/assets/ui/dist/assets/index-73e04efd.css b/ui/assets/ui/dist/assets/index-73e04efd.css similarity index 100% rename from assets/ui/dist/assets/index-73e04efd.css rename to ui/assets/ui/dist/assets/index-73e04efd.css diff --git a/assets/ui/dist/assets/index-a61e8bc7.js b/ui/assets/ui/dist/assets/index-a61e8bc7.js similarity index 100% rename from assets/ui/dist/assets/index-a61e8bc7.js rename to ui/assets/ui/dist/assets/index-a61e8bc7.js diff --git a/ui/assets/ui/dist/index.html b/ui/assets/ui/dist/index.html new file mode 100644 index 0000000..b802bef --- /dev/null +++ b/ui/assets/ui/dist/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + k6 dashboard + + + + +
+ + + diff --git a/assets/ui/dist/xk6-dashboard.svg b/ui/assets/ui/dist/xk6-dashboard.svg similarity index 100% rename from assets/ui/dist/xk6-dashboard.svg rename to ui/assets/ui/dist/xk6-dashboard.svg diff --git a/assets/ui/index.html b/ui/assets/ui/index.html similarity index 100% rename from assets/ui/index.html rename to ui/assets/ui/index.html diff --git a/assets/ui/package.json b/ui/assets/ui/package.json similarity index 100% rename from assets/ui/package.json rename to ui/assets/ui/package.json diff --git a/assets/ui/public/xk6-dashboard.svg b/ui/assets/ui/public/xk6-dashboard.svg similarity index 100% rename from assets/ui/public/xk6-dashboard.svg rename to ui/assets/ui/public/xk6-dashboard.svg diff --git a/assets/ui/src/App.css b/ui/assets/ui/src/App.css similarity index 100% rename from assets/ui/src/App.css rename to ui/assets/ui/src/App.css diff --git a/assets/ui/src/App.jsx b/ui/assets/ui/src/App.jsx similarity index 100% rename from assets/ui/src/App.jsx rename to ui/assets/ui/src/App.jsx diff --git a/assets/ui/src/Chart.css b/ui/assets/ui/src/Chart.css similarity index 100% rename from assets/ui/src/Chart.css rename to ui/assets/ui/src/Chart.css diff --git a/assets/ui/src/Chart.jsx b/ui/assets/ui/src/Chart.jsx similarity index 100% rename from assets/ui/src/Chart.jsx rename to ui/assets/ui/src/Chart.jsx diff --git a/assets/ui/src/Charts.jsx b/ui/assets/ui/src/Charts.jsx similarity index 100% rename from assets/ui/src/Charts.jsx rename to ui/assets/ui/src/Charts.jsx diff --git a/assets/ui/src/Panel.css b/ui/assets/ui/src/Panel.css similarity index 100% rename from assets/ui/src/Panel.css rename to ui/assets/ui/src/Panel.css diff --git a/assets/ui/src/Panel.jsx b/ui/assets/ui/src/Panel.jsx similarity index 100% rename from assets/ui/src/Panel.jsx rename to ui/assets/ui/src/Panel.jsx diff --git a/assets/ui/src/Panels.jsx b/ui/assets/ui/src/Panels.jsx similarity index 100% rename from assets/ui/src/Panels.jsx rename to ui/assets/ui/src/Panels.jsx diff --git a/assets/ui/src/format.js b/ui/assets/ui/src/format.js similarity index 100% rename from assets/ui/src/format.js rename to ui/assets/ui/src/format.js diff --git a/assets/ui/src/index.css b/ui/assets/ui/src/index.css similarity index 100% rename from assets/ui/src/index.css rename to ui/assets/ui/src/index.css diff --git a/assets/ui/src/main.jsx b/ui/assets/ui/src/main.jsx similarity index 100% rename from assets/ui/src/main.jsx rename to ui/assets/ui/src/main.jsx diff --git a/assets/ui/src/metrics-uplot.js b/ui/assets/ui/src/metrics-uplot.js similarity index 100% rename from assets/ui/src/metrics-uplot.js rename to ui/assets/ui/src/metrics-uplot.js diff --git a/assets/ui/src/metrics.js b/ui/assets/ui/src/metrics.js similarity index 100% rename from assets/ui/src/metrics.js rename to ui/assets/ui/src/metrics.js diff --git a/assets/ui/vite.config.js b/ui/assets/ui/vite.config.js similarity index 100% rename from assets/ui/vite.config.js rename to ui/assets/ui/vite.config.js diff --git a/assets/ui/yarn.lock b/ui/assets/ui/yarn.lock similarity index 100% rename from assets/ui/yarn.lock rename to ui/assets/ui/yarn.lock diff --git a/ui/ui.go b/ui/ui.go new file mode 100644 index 0000000..8c9c5e8 --- /dev/null +++ b/ui/ui.go @@ -0,0 +1,20 @@ +package ui + +import ( + "embed" + "io/fs" +) + +const distDir = "assets/ui/dist" + +//go:embed assets/ui/dist +var distFS embed.FS + +func GetFS() fs.FS { + uiFS, err := fs.Sub(distFS, distDir) + if err != nil { + panic(err) + } + + return uiFS +} diff --git a/ui/ui_test.go b/ui/ui_test.go new file mode 100644 index 0000000..c544dae --- /dev/null +++ b/ui/ui_test.go @@ -0,0 +1,22 @@ +package ui + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetFS(t *testing.T) { + t.Parallel() + + fs := GetFS() + + assert.NotNil(t, fs) + + file, err := fs.Open("index.html") + + assert.NoError(t, err) + assert.NotNil(t, file) + + assert.NoError(t, file.Close()) +} From e15277fa37aaaf400be6afed4da91e114fcd643e Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Mon, 1 May 2023 16:31:02 +0200 Subject: [PATCH 07/14] fix: return error on invalid period value (instead of panic) --- dashboard/options.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/dashboard/options.go b/dashboard/options.go index d60fe22..2336451 100644 --- a/dashboard/options.go +++ b/dashboard/options.go @@ -5,6 +5,7 @@ package dashboard import ( + "errors" "net" "net/url" "reflect" @@ -26,8 +27,8 @@ type options struct { Period time.Duration } -func getopts(query string) (*options, error) { - opts := &options{ +func getopts(query string) (opts *options, err error) { // nolint:nonamedreturns + opts = &options{ Port: defaultPort, Host: defaultHost, Period: defaultPeriod, @@ -44,6 +45,8 @@ func getopts(query string) (*options, error) { decoder := schema.NewDecoder() + decoder.IgnoreUnknownKeys(true) + decoder.RegisterConverter(time.Second, func(s string) reflect.Value { v, err := time.ParseDuration(s) if err != nil { @@ -53,11 +56,17 @@ func getopts(query string) (*options, error) { return reflect.ValueOf(v) }) - if err = decoder.Decode(opts, value); err != nil { - return nil, err + defer func() { + if r := recover(); r != nil { + err = errInvalidDuration + } + }() + + if e := decoder.Decode(opts, value); e != nil { + err = e } - return opts, nil + return opts, err } func (opts *options) addr() string { @@ -72,3 +81,5 @@ func (opts *options) url() string { return "http://" + net.JoinHostPort(host, strconv.Itoa(opts.Port)) } + +var errInvalidDuration = errors.New("invalid duration") From 118d13018b3a60d52c30c388dc5a49c1c3804254 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Mon, 1 May 2023 16:31:45 +0200 Subject: [PATCH 08/14] test: adding some tests --- dashboard/extension.go | 11 ++-- dashboard/extension_test.go | 55 ++++++++++++++++++ dashboard/helper_test.go | 41 +++++++++++++ dashboard/meter_test.go | 112 ++++++++++++++++++++++++++++++++++++ dashboard/options_test.go | 47 +++++++++++++++ dashboard/registry_test.go | 71 +++++++++++++++++++++++ dashboard/web.go | 4 +- 7 files changed, 334 insertions(+), 7 deletions(-) create mode 100644 dashboard/extension_test.go create mode 100644 dashboard/helper_test.go create mode 100644 dashboard/meter_test.go create mode 100644 dashboard/options_test.go create mode 100644 dashboard/registry_test.go diff --git a/dashboard/extension.go b/dashboard/extension.go index bc851dd..5f87c9a 100644 --- a/dashboard/extension.go +++ b/dashboard/extension.go @@ -31,7 +31,7 @@ type Extension struct { var _ output.Output = (*Extension)(nil) -// New creates Exension instance using the passwd uiFS as source of web UI. +// New creates Extension instance using the passwd uiFS as source of web UI. func New(params output.Params, uiFS fs.FS) (*Extension, error) { opts, err := getopts(params.ConfigArgument) if err != nil { @@ -61,10 +61,7 @@ func (ext *Extension) Start() error { ext.cumulative = newMeter(0) - ext.server, err = newWebServer(ext.uiFS, ext.logger) - if err != nil { - return err - } + ext.server = newWebServer(ext.uiFS, ext.logger) go func() { if err := ext.server.listenAndServe(ext.options.addr()); err != nil { @@ -111,3 +108,7 @@ func (ext *Extension) updateAndSend(containers []metrics.SampleContainer, m *met ext.server.sendEvent(event, data) } + +func (ext *Extension) URL() string { + return ext.options.url() +} diff --git a/dashboard/extension_test.go b/dashboard/extension_test.go new file mode 100644 index 0000000..ae90de6 --- /dev/null +++ b/dashboard/extension_test.go @@ -0,0 +1,55 @@ +package dashboard + +import ( + "embed" + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "go.k6.io/k6/metrics" + "go.k6.io/k6/output" +) + +func TestNewExtension(t *testing.T) { + t.Parallel() + + var params output.Params + + params.ConfigArgument = "port=1&host=localhost" + params.OutputType = "dashboard" + + ext, err := New(params, embed.FS{}) + + assert.NoError(t, err) + assert.NotNil(t, ext) + + assert.Equal(t, "dashboard (localhost:1) http://localhost:1", ext.Description()) + assert.Equal(t, "http://localhost:1", ext.URL()) + + params.ConfigArgument = "period=2" + + _, err = New(params, embed.FS{}) + + assert.Error(t, err) +} + +func TestExtension(t *testing.T) { + t.Parallel() + + var params output.Params + + params.Logger = logrus.StandardLogger() + + ext, err := New(params, embed.FS{}) + + assert.NoError(t, err) + assert.NotNil(t, ext) + + assert.NoError(t, ext.Start()) + + sample := testSample(t, "foo", metrics.Counter, 1) + + ext.AddMetricSamples(testSampleContainer(t, sample).toArray()) + + assert.NoError(t, ext.Stop()) +} diff --git a/dashboard/helper_test.go b/dashboard/helper_test.go new file mode 100644 index 0000000..f3e3ccc --- /dev/null +++ b/dashboard/helper_test.go @@ -0,0 +1,41 @@ +package dashboard + +import ( + "testing" + "time" + + "go.k6.io/k6/metrics" +) + +func testSample(t *testing.T, name string, typ metrics.MetricType, value float64) metrics.Sample { + t.Helper() + + return metrics.Sample{ // nolint:exhaustruct + Time: time.Now(), + Value: value, + TimeSeries: metrics.TimeSeries{ // nolint:exhaustruct + Metric: &metrics.Metric{ // nolint:exhaustruct + Name: name, + Type: typ, + }, + }, + } +} + +type testSamples struct { + samples []metrics.Sample +} + +func testSampleContainer(t *testing.T, samples ...metrics.Sample) *testSamples { + t.Helper() + + return &testSamples{samples: samples} +} + +func (ts *testSamples) GetSamples() []metrics.Sample { + return ts.samples +} + +func (ts *testSamples) toArray() []metrics.SampleContainer { + return []metrics.SampleContainer{ts} +} diff --git a/dashboard/meter_test.go b/dashboard/meter_test.go new file mode 100644 index 0000000..8993176 --- /dev/null +++ b/dashboard/meter_test.go @@ -0,0 +1,112 @@ +package dashboard + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.k6.io/k6/metrics" +) + +func Test_newMeter(t *testing.T) { + t.Parallel() + + met := newMeter(time.Second) + + assert.NotNil(t, met) + assert.NotNil(t, met.registry) + assert.NotNil(t, met.clock) + assert.Equal(t, time.Second, met.period) + assert.InDelta(t, time.Now().UnixMilli(), met.start.UnixMilli(), float64(time.Millisecond)) +} + +func Test_meter_add_error(t *testing.T) { + t.Parallel() + + met := newMeter(time.Second) + + sample := metrics.Sample{ // nolint:exhaustruct + TimeSeries: metrics.TimeSeries{ // nolint:exhaustruct + Metric: &metrics.Metric{ // nolint:exhaustruct + Type: metrics.MetricType(-1), + }, + }, + } + + assert.Error(t, met.add(sample)) +} + +func Test_meter_add(t *testing.T) { + t.Parallel() + + met := newMeter(time.Second) + + sample := testSample(t, "foo", metrics.Counter, 1) + + assert.NoError(t, met.add(sample)) + + metric := met.registry.Get("foo") + + assert.NotNil(t, metric) + assert.Equal(t, 1.0, metric.Sink.(*metrics.CounterSink).Value) // nolint:forcetypeassert +} + +func Test_meter_update_error(t *testing.T) { + t.Parallel() + + met := newMeter(time.Second) + + sample := testSample(t, "", metrics.Gauge, 0) + data, err := met.update(testSampleContainer(t, sample).toArray()) + + assert.Error(t, err) + assert.Nil(t, data) +} + +func Test_meter_update(t *testing.T) { + t.Parallel() + + met := newMeter(time.Second) + + foo := testSample(t, "foo", metrics.Counter, 1) + bar := testSample(t, "bar", metrics.Counter, 1) + data, err := met.update(testSampleContainer(t, foo, bar).toArray()) + + assert.NoError(t, err) + assert.NotNil(t, data) + + assert.Equal(t, 3, len(data)) + assert.Contains(t, data, "foo") + assert.Contains(t, data, "bar") + assert.Contains(t, data, "time") + + metric, ok := data["foo"] + + assert.True(t, ok) + assert.Contains(t, metric.Sample, "count") + assert.Contains(t, metric.Sample, "rate") + assert.Equal(t, 1.0, metric.Sample["count"]) + assert.Equal(t, 1.0, metric.Sample["rate"]) +} + +func Test_meter_update_no_period(t *testing.T) { + t.Parallel() + + met := newMeter(0) + + sample := testSample(t, "foo", metrics.Counter, 1) + data, err := met.update(testSampleContainer(t, sample).toArray()) + + assert.NoError(t, err) + assert.NotNil(t, data) + + assert.Equal(t, 2, len(data)) + assert.Contains(t, data, "foo") + assert.Contains(t, data, "time") + + metric, ok := data["foo"] + + assert.True(t, ok) + assert.Contains(t, metric.Sample, "count") + assert.Contains(t, metric.Sample, "rate") +} diff --git a/dashboard/options_test.go b/dashboard/options_test.go new file mode 100644 index 0000000..221ed1d --- /dev/null +++ b/dashboard/options_test.go @@ -0,0 +1,47 @@ +package dashboard + +import ( + "fmt" + "net" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func Test_getopts_defaults(t *testing.T) { + t.Parallel() + + opts, err := getopts("") + + assert.NoError(t, err) + assert.NotNil(t, opts) + + assert.Equal(t, defaultHost, opts.Host) + assert.Equal(t, defaultPort, opts.Port) + assert.Equal(t, defaultPeriod, opts.Period) + + assert.Equal(t, fmt.Sprintf("http://%s", net.JoinHostPort("127.0.0.1", strconv.Itoa(defaultPort))), opts.url()) +} + +func Test_getopts_error(t *testing.T) { + t.Parallel() + + _, err := getopts("period=s") + + assert.Error(t, err) +} + +func Test_getopts(t *testing.T) { + t.Parallel() + + opts, err := getopts("period=1s&port=1&host=localhost") + + assert.NoError(t, err) + assert.Equal(t, time.Second, opts.Period) + assert.Equal(t, 1, opts.Port) + assert.Equal(t, "localhost", opts.Host) + assert.Equal(t, "http://localhost:1", opts.url()) + assert.Equal(t, "localhost:1", opts.addr()) +} diff --git a/dashboard/registry_test.go b/dashboard/registry_test.go new file mode 100644 index 0000000..5e46d7a --- /dev/null +++ b/dashboard/registry_test.go @@ -0,0 +1,71 @@ +package dashboard + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.k6.io/k6/metrics" +) + +func Test_newRegistry(t *testing.T) { + t.Parallel() + + reg := newRegistry() + + assert.NotNil(t, reg) + assert.NotNil(t, reg.Registry) + assert.NotNil(t, reg.names) +} + +func Test_registry_getOrNew(t *testing.T) { + t.Parallel() + + reg := newRegistry() + + met, err := reg.getOrNew("foo", metrics.Counter, metrics.Data) + + assert.NoError(t, err) + assert.NotNil(t, met) + assert.Equal(t, []string{"foo"}, reg.names) + + met2, err := reg.getOrNew("foo", metrics.Counter, metrics.Data) + + assert.NoError(t, err) + assert.NotNil(t, met) + assert.Same(t, met, met2) + assert.Equal(t, []string{"foo"}, reg.names) + + met3, err := reg.getOrNew("bar", metrics.Counter, metrics.Data) + + assert.NoError(t, err) + assert.NotNil(t, met3) + assert.Equal(t, []string{"foo", "bar"}, reg.names) + + _, err = reg.getOrNew("", metrics.Counter, metrics.Data) + + assert.Error(t, err) + + assert.Panics(t, func() { reg.mustGetOrNew("", metrics.Counter, metrics.Data) }) +} + +func Test_registry_format(t *testing.T) { + t.Parallel() + + reg := newRegistry() + + reg.getOrNew("foo", metrics.Counter, metrics.Data) // nolint:errcheck + reg.getOrNew("bar", metrics.Counter, metrics.Data) // nolint:errcheck + + reg.names = append(reg.names, "dummy") + + assert.Equal(t, []string{"foo", "bar", "dummy"}, reg.names) + + data := reg.format(time.Second) + + assert.NotNil(t, data) + + assert.Equal(t, 2, len(data)) + assert.Contains(t, data, "foo") + assert.Contains(t, data, "bar") +} diff --git a/dashboard/web.go b/dashboard/web.go index f159d76..3468903 100644 --- a/dashboard/web.go +++ b/dashboard/web.go @@ -28,7 +28,7 @@ type webServer struct { *http.ServeMux } -func newWebServer(uiFS fs.FS, logger logrus.FieldLogger) (*webServer, error) { //nolint:ireturn +func newWebServer(uiFS fs.FS, logger logrus.FieldLogger) *webServer { //nolint:ireturn srv := &webServer{ eventSource: newEventSource(eventChannel, logger), ServeMux: http.NewServeMux(), @@ -40,7 +40,7 @@ func newWebServer(uiFS fs.FS, logger logrus.FieldLogger) (*webServer, error) { / srv.Handle(pathUI, uiHandler) srv.HandleFunc("/", rootHandler(pathUI)) - return srv, nil + return srv } func (srv *webServer) listenAndServe(addr string) error { From 6c53f668fd4c428a549c292881143f42b6519a43 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Mon, 1 May 2023 16:37:44 +0200 Subject: [PATCH 09/14] ci: remove lint from build and release workflow (already run in test workflow) --- .github/workflows/build.yml | 5 ----- .github/workflows/release.yml | 5 ----- 2 files changed, 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bfdd89e..c0e1232 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,11 +21,6 @@ jobs: with: fetch-depth: 0 - - name: Lint Go Code - uses: golangci/golangci-lint-action@v3 - with: - version: v1.50.1 - - name: Build id: build uses: szkiba/xk6bundler@v0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 762fff7..7a65b41 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,11 +24,6 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - - name: Lint Go Code - uses: golangci/golangci-lint-action@v3 - with: - version: v1.50.1 - name: Build id: build From 0acd83ad79b7f2b6293d9870543603068b922a2a Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Mon, 1 May 2023 17:44:38 +0200 Subject: [PATCH 10/14] docs: fix screenshot uri --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0214cc..5f192a5 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ By using **xk6-dashboard** output extension you can access metrics from [k6](htt **Screenshots** -![k6 dashboard snapshot](screenshots/../screenshot/k6-dashboard-snapshot.png) +![k6 dashboard snapshot](screenshot/k6-dashboard-snapshot.png) -![k6 dashboard cumulative](screenshots/../screenshot/k6-dashboard-cumulative.png) +![k6 dashboard cumulative](screenshot/k6-dashboard-cumulative.png) **Table of Contents** From 04bda26d32566b0183a8b3e620cccdfefb3db676 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Mon, 1 May 2023 18:33:18 +0200 Subject: [PATCH 11/14] feat: add 'open' parameter for opening browser window automatically --- README.md | 3 +++ dashboard/extension.go | 9 +++++---- dashboard/extension_test.go | 1 - dashboard/options.go | 7 +++++++ dashboard/options_test.go | 4 +++- go.mod | 1 + go.sum | 3 +++ 7 files changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5f192a5..83e1239 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ $ ./k6 run --out dashboard script.js output: dashboard (:5665) http://127.0.0.1:5665 ``` +> Using `--out dashboard=open` will automatically open a new browser window. + ## Parameters The output extension accepts parameters in a standard query string format: @@ -78,6 +80,7 @@ parameter | description host | Hostname or IP address for HTTP endpoint (default: "", empty, listen on all interfaces) 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 ## Docker diff --git a/dashboard/extension.go b/dashboard/extension.go index 5f87c9a..7e5dc3c 100644 --- a/dashboard/extension.go +++ b/dashboard/extension.go @@ -8,6 +8,7 @@ import ( "fmt" "io/fs" + "github.com/pkg/browser" "github.com/sirupsen/logrus" "go.k6.io/k6/metrics" "go.k6.io/k6/output" @@ -78,6 +79,10 @@ func (ext *Extension) Start() error { ext.flusher = flusher + if ext.options.Open { + browser.OpenURL(ext.options.url()) + } + return nil } @@ -108,7 +113,3 @@ func (ext *Extension) updateAndSend(containers []metrics.SampleContainer, m *met ext.server.sendEvent(event, data) } - -func (ext *Extension) URL() string { - return ext.options.url() -} diff --git a/dashboard/extension_test.go b/dashboard/extension_test.go index ae90de6..b9a8d22 100644 --- a/dashboard/extension_test.go +++ b/dashboard/extension_test.go @@ -24,7 +24,6 @@ func TestNewExtension(t *testing.T) { assert.NotNil(t, ext) assert.Equal(t, "dashboard (localhost:1) http://localhost:1", ext.Description()) - assert.Equal(t, "http://localhost:1", ext.URL()) params.ConfigArgument = "period=2" diff --git a/dashboard/options.go b/dashboard/options.go index 2336451..4cac4b6 100644 --- a/dashboard/options.go +++ b/dashboard/options.go @@ -19,12 +19,14 @@ const ( defaultHost = "" defaultPort = 5665 defaultPeriod = time.Second * 10 + defaultOpen = false ) type options struct { Port int Host string Period time.Duration + Open bool } func getopts(query string) (opts *options, err error) { // nolint:nonamedreturns @@ -32,6 +34,7 @@ func getopts(query string) (opts *options, err error) { // nolint:nonamedreturns Port: defaultPort, Host: defaultHost, Period: defaultPeriod, + Open: defaultOpen, } if query == "" { @@ -66,6 +69,10 @@ func getopts(query string) (opts *options, err error) { // nolint:nonamedreturns err = e } + if value.Has("open") && len(value.Get("open")) == 0 { + opts.Open = true + } + return opts, err } diff --git a/dashboard/options_test.go b/dashboard/options_test.go index 221ed1d..0099b1e 100644 --- a/dashboard/options_test.go +++ b/dashboard/options_test.go @@ -21,6 +21,7 @@ func Test_getopts_defaults(t *testing.T) { assert.Equal(t, defaultHost, opts.Host) assert.Equal(t, defaultPort, opts.Port) assert.Equal(t, defaultPeriod, opts.Period) + assert.Equal(t, defaultOpen, opts.Open) assert.Equal(t, fmt.Sprintf("http://%s", net.JoinHostPort("127.0.0.1", strconv.Itoa(defaultPort))), opts.url()) } @@ -36,11 +37,12 @@ func Test_getopts_error(t *testing.T) { func Test_getopts(t *testing.T) { t.Parallel() - opts, err := getopts("period=1s&port=1&host=localhost") + opts, err := getopts("period=1s&port=1&host=localhost&open") 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, "localhost", opts.Host) assert.Equal(t, "http://localhost:1", opts.url()) assert.Equal(t, "localhost:1", opts.addr()) diff --git a/go.mod b/go.mod index 9d0499e..6411eb0 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( github.com/alexandrevicenzi/go-sse v1.6.0 github.com/gorilla/schema v1.2.0 + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.0 go.k6.io/k6 v0.43.1 diff --git a/go.sum b/go.sum index cde3617..7ffd4b3 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ github.com/mstoykov/k6-taskqueue-lib v0.1.0 h1:M3eww1HSOLEN6rIkbNOJHhOVhlqnqkhYj github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e h1:zWKUYT07mGmVBH+9UgnHXd/ekCK99C8EbDSAt5qsjXE= @@ -70,6 +72,7 @@ golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCzt golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 5c4a77d2a49b9d56676af03c029284e231b92079 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Tue, 2 May 2023 18:07:34 +0200 Subject: [PATCH 12/14] style: fix linter warning --- dashboard/extension.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/extension.go b/dashboard/extension.go index 7e5dc3c..17f0de7 100644 --- a/dashboard/extension.go +++ b/dashboard/extension.go @@ -80,7 +80,7 @@ func (ext *Extension) Start() error { ext.flusher = flusher if ext.options.Open { - browser.OpenURL(ext.options.url()) + browser.OpenURL(ext.options.url()) // nolint:errcheck } return nil From 153f6867b9486296847ce19b877a333c9f8c4e61 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Tue, 2 May 2023 18:08:05 +0200 Subject: [PATCH 13/14] refactor: remove unneeded favicon.ico redirect --- dashboard/web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/web.go b/dashboard/web.go index 3468903..ff5170a 100644 --- a/dashboard/web.go +++ b/dashboard/web.go @@ -62,7 +62,7 @@ func (srv *webServer) listenAndServe(addr string) error { func rootHandler(uiPath string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { //nolint:varnamelen - if r.URL.Path == "/" || r.URL.Path == "/favicon.ico" { + if r.URL.Path == "/" { http.Redirect(w, r, path.Join(uiPath, r.URL.Path)+"?endpoint=/", http.StatusTemporaryRedirect) return From b7999fab7b21cf8e4c77523e9dcc191fae59ba95 Mon Sep 17 00:00:00 2001 From: Ivan SZKIBA Date: Tue, 2 May 2023 18:08:37 +0200 Subject: [PATCH 14/14] test: add more tests --- dashboard/extension_test.go | 27 +++++++++++++-- dashboard/helper_test.go | 66 +++++++++++++++++++++++++++++++++++++ dashboard/sse_test.go | 58 ++++++++++++++++++++++++++++++++ dashboard/web_test.go | 53 +++++++++++++++++++++++++++++ magefiles/magefile.go | 2 +- scripts/test.js | 16 ++------- 6 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 dashboard/sse_test.go create mode 100644 dashboard/web_test.go diff --git a/dashboard/extension_test.go b/dashboard/extension_test.go index b9a8d22..7be34e9 100644 --- a/dashboard/extension_test.go +++ b/dashboard/extension_test.go @@ -2,7 +2,11 @@ package dashboard import ( "embed" + "net" + "strconv" + "strings" "testing" + "time" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -35,9 +39,13 @@ func TestNewExtension(t *testing.T) { func TestExtension(t *testing.T) { t.Parallel() + port := getRandomPort(t) + addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) + var params output.Params params.Logger = logrus.StandardLogger() + params.ConfigArgument = "period=10ms&port=" + strconv.Itoa(port) ext, err := New(params, embed.FS{}) @@ -46,9 +54,24 @@ func TestExtension(t *testing.T) { assert.NoError(t, ext.Start()) - sample := testSample(t, "foo", metrics.Counter, 1) + time.Sleep(time.Millisecond) + + go func() { + sample := testSample(t, "foo", metrics.Counter, 1) + + ext.AddMetricSamples(testSampleContainer(t, sample).toArray()) + }() + + lines := readSSE(t, 5, "http://"+addr+"/events") + + assert.NotNil(t, lines) + assert.Equal(t, "event: snapshot", lines[0]) + assert.Equal(t, "event: cumulative", lines[3]) + + dataPrefix := `data: {"foo":{"type":"counter","contains":"default","tainted":null,"sample":{"count":1,"rate":` - ext.AddMetricSamples(testSampleContainer(t, sample).toArray()) + assert.True(t, strings.HasPrefix(lines[1], dataPrefix)) + assert.True(t, strings.HasPrefix(lines[4], dataPrefix)) assert.NoError(t, ext.Stop()) } diff --git a/dashboard/helper_test.go b/dashboard/helper_test.go index f3e3ccc..5f8095c 100644 --- a/dashboard/helper_test.go +++ b/dashboard/helper_test.go @@ -1,9 +1,15 @@ package dashboard import ( + "bufio" + "context" + "net" + "net/http" + "strconv" "testing" "time" + "github.com/stretchr/testify/assert" "go.k6.io/k6/metrics" ) @@ -39,3 +45,63 @@ func (ts *testSamples) GetSamples() []metrics.Sample { func (ts *testSamples) toArray() []metrics.SampleContainer { return []metrics.SampleContainer{ts} } + +func getRandomPort(t *testing.T) int { + t.Helper() + + listener, err := net.Listen("tcp", "127.0.0.1:0") + + assert.NoError(t, err) + + port := listener.Addr().(*net.TCPAddr).Port //nolint:forcetypeassert + + assert.NoError(t, listener.Close()) + + return port +} + +func getRandomAddr(t *testing.T) string { + t.Helper() + + return net.JoinHostPort("127.0.0.1", strconv.Itoa(getRandomPort(t))) +} + +func readSSE(t *testing.T, nlines int, loc string) []string { + t.Helper() + + req, err := http.NewRequest(http.MethodGet, loc, nil) + + req.Header.Set("Accept", "text/event-stream") + req.Header.Set("Connection", "keep-alive") + + assert.NoError(t, err) + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + req = req.WithContext(ctx) + + res, err := http.DefaultClient.Do(req) // nolint:bodyclose + + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + assert.Equal(t, "text/event-stream", res.Header.Get("Content-Type")) + + scanner := bufio.NewScanner(res.Body) + + scanner.Split(bufio.ScanLines) + + lines := make([]string, 0, nlines) + + for idx := 0; idx < nlines; idx++ { + if !scanner.Scan() { + break + } + + lines = append(lines, scanner.Text()) + } + + assert.Equal(t, nlines, len(lines)) + + return lines +} diff --git a/dashboard/sse_test.go b/dashboard/sse_test.go new file mode 100644 index 0000000..bf8a35f --- /dev/null +++ b/dashboard/sse_test.go @@ -0,0 +1,58 @@ +package dashboard + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func Test_sendEvent(t *testing.T) { + t.Parallel() + + src := newEventSource("events", logrus.StandardLogger()) + rec := httptest.NewRecorder() + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1/events", nil) + ctx, cancel := context.WithCancel(context.TODO()) + + req = req.WithContext(ctx) + + assert.NoError(t, err) + + req.Header.Set("Accept", "text/event-stream") + req.Header.Set("Connection", "keep-alive") + + started := make(chan struct{}) + + go func() { + started <- struct{}{} + + src.ServeHTTP(rec, req) + }() + + <-started + + time.Sleep(time.Millisecond) + + assert.True(t, src.Server.HasChannel("events")) + src.sendEvent("foo", map[string]interface{}{"answer": 42}) + + time.Sleep(time.Millisecond) + + res := rec.Result() // nolint:bodyclose + + assert.Equal(t, "text/event-stream", res.Header.Get("Content-Type")) + + data, err := io.ReadAll(res.Body) // nolint:bodyclose + + assert.NoError(t, err) + + assert.Equal(t, "event: foo\ndata: {\"answer\":42}\n\n", string(data)) + + cancel() +} diff --git a/dashboard/web_test.go b/dashboard/web_test.go new file mode 100644 index 0000000..896e823 --- /dev/null +++ b/dashboard/web_test.go @@ -0,0 +1,53 @@ +package dashboard + +import ( + "net/http" + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/szkiba/xk6-dashboard/ui" +) + +func Test_newWebServer(t *testing.T) { + t.Parallel() + + srv := newWebServer(ui.GetFS(), logrus.StandardLogger()) + + assert.NotNil(t, srv) + assert.NotNil(t, srv.ServeMux) + assert.NotNil(t, srv.eventSource) + + addr := getRandomAddr(t) + + assert.NoError(t, srv.listenAndServe(addr)) + + base := "http://" + addr + + testLoc := func(loc string) { + res, err := http.Get(base + loc) // nolint:bodyclose,noctx + + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + } + + testLoc("/ui/index.html") + testLoc("/events") + testLoc("/") + + res, err := http.Get(base + "/no_such_path") // nolint:bodyclose,noctx + + assert.NoError(t, err) + assert.Equal(t, http.StatusNotFound, res.StatusCode) +} + +func Test_webServer_used_addr(t *testing.T) { + t.Parallel() + + srv := newWebServer(ui.GetFS(), logrus.StandardLogger()) + + addr := getRandomAddr(t) + + assert.NoError(t, srv.listenAndServe(addr)) + assert.Error(t, srv.listenAndServe(addr)) +} diff --git a/magefiles/magefile.go b/magefiles/magefile.go index f1b2c35..b994543 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -47,7 +47,7 @@ func It() error { } for _, script := range all { - err := xk6run("--out dashboard='period=5s' " + script).Run() + err := xk6run("--out dashboard='period=100ms' " + script).Run() if err != nil { return err } diff --git a/scripts/test.js b/scripts/test.js index 8d53af4..8a3ff17 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -1,26 +1,16 @@ import http from "k6/http"; -import { sleep } from "k6"; export let options = { discardResponseBodies: true, scenarios: { contacts: { - executor: "ramping-vus", - startVUs: 1, - stages: [ - { duration: "10s", target: 2 }, - { duration: "30s", target: 10 }, - { duration: "20s", target: 2 }, - { duration: "30s", target: 10 }, - { duration: "20s", target: 3 }, - { duration: "10s", target: 1 }, - ], - gracefulRampDown: "0s", + executor: "constant-vus", + vus: 2, + duration: '2s', }, }, }; export default function () { http.get("http://test.k6.io"); - sleep(1); }