├── .gitattributes ├── .github ├── FUNDING.yml ├── scripts │ └── modtidy-check.sh └── workflows │ ├── coverage.yml │ ├── tests-linux.yml │ └── tests-others.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _example ├── .gitignore ├── README.md ├── chi │ ├── go.mod │ ├── go.sum │ └── main.go ├── default │ ├── go.mod │ ├── go.sum │ └── main.go ├── echo │ ├── go.mod │ ├── go.sum │ └── main.go ├── fasthttp │ ├── go.mod │ ├── go.sum │ └── main.go ├── fiber │ ├── go.mod │ ├── go.sum │ └── main.go ├── gin │ ├── go.mod │ ├── go.sum │ └── main.go ├── gorilla │ ├── go.mod │ ├── go.sum │ └── main.go ├── https │ ├── cert.pem │ ├── example_com.conf │ ├── go.mod │ ├── go.sum │ ├── key.pem │ └── main.go ├── iris │ ├── go.mod │ ├── go.sum │ └── main.go ├── middleware │ ├── go.mod │ ├── go.sum │ └── main.go ├── mux │ ├── go.mod │ ├── go.sum │ └── main.go ├── options │ ├── go.mod │ ├── go.sum │ └── main.go ├── userplots │ ├── go.mod │ ├── go.sum │ └── main.go └── work.go ├── codecov.yml ├── examples_test.go ├── go.mod ├── go.sum ├── internal ├── plot │ ├── color.go │ ├── hist.go │ ├── hist_test.go │ ├── layout.go │ ├── plots.go │ ├── plots_list.go │ ├── plots_test.go │ ├── user.go │ └── user_test.go └── static │ ├── css │ └── statsviz.css │ ├── fs.go │ ├── index.html │ ├── js │ ├── app.js │ ├── buffer.js │ ├── plot.js │ ├── stats.js │ └── theme.js │ └── libs │ ├── css │ ├── bootstrap-toggle.min.css │ ├── bootstrap.min.css │ └── font.awesome-6.1.2-all.min.css │ ├── js │ ├── bootstrap-toggle.min.js │ ├── bootstrap.bundle.min.js │ ├── font.awesome-6.1.2-all.min.js │ ├── jquery-3.6.0.min.js │ ├── plotly-basic-2.12.1.min.js │ ├── plotly-cartesian-2.12.1.min.js │ ├── popperjs-core2 │ └── tippy.js@6 │ └── webfonts │ └── fa-solid-900.woff2 ├── statsviz.go ├── statsviz_test.go ├── testdata ├── chi.txt ├── default.txt ├── echo.txt ├── fasthttp.txt ├── fiber.txt ├── gin.txt ├── gorilla.txt ├── https.txt ├── iris.txt ├── middleware.txt ├── mux.txt ├── options.txt └── userplots.txt ├── userplot.go └── userplot_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | internal/static/** linguist-detectable=false 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [arl] 2 | -------------------------------------------------------------------------------- /.github/scripts/modtidy-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | TMPDIR="$(mktemp -d)" 3 | trap 'rm -rf -- "$TMPDIR"' EXIT 4 | 5 | # Copy files before 'go mod tidy' potentially modifies them. 6 | cp go.{mod,sum} "${TMPDIR}" 7 | 8 | go mod tidy 9 | 10 | diff go.mod "${TMPDIR}/go.mod" 11 | diff go.sum "${TMPDIR}/go.sum" 12 | 13 | if ! git diff --no-index --quiet go.mod "${TMPDIR}/go.mod"; then 14 | echo -ne "\n\nRunning 'go mod tidy' modified go.mod" 15 | exit 1 16 | fi 17 | if ! git diff --no-index --quiet go.sum "${TMPDIR}/go.sum"; then 18 | echo -ne "\n\nRunning 'go mod tidy' modified go.sum" 19 | exit 1 20 | fi 21 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | name: Coverage 3 | jobs: 4 | run: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - uses: actions/setup-go@v3 9 | with: 10 | go-version: 1.21.x 11 | - run: go test -coverprofile=coverage.txt 12 | - name: Upload coverage to Codecov 13 | uses: codecov/codecov-action@v3 14 | with: 15 | fail_ci_if_error: false 16 | files: coverage.txt 17 | name: codecov-umbrella 18 | verbose: true 19 | -------------------------------------------------------------------------------- /.github/workflows/tests-linux.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Tests-linux 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.20.x, 1.21.x] 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-go@v3 12 | with: 13 | go-version: ${{ matrix.go-version }} 14 | - name: go mod tidy check 15 | run: .github/scripts/modtidy-check.sh 16 | - name: Tests 17 | run: go test -race -shuffle=on ./... 18 | -------------------------------------------------------------------------------- /.github/workflows/tests-others.yml: -------------------------------------------------------------------------------- 1 | on: [pull_request] 2 | name: Tests-others 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | os: [macos-latest, windows-latest] 8 | runs-on: ${{ matrix.os }} 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-go@v3 12 | with: 13 | go-version: 1.21.x 14 | - name: Tests 15 | run: go test -race -shuffle=on ./... 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | out 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Unreleased 2 | ============== 3 | * Make all examples use statsviz@latest (#112) 4 | 5 | v0.6.0 / 2023-10-08 6 | ============== 7 | * New plots showing new go1.20/go1.21 runtime/metrics: (#111) 8 | + GC Cycles 9 | + GC Scan 10 | + Memory classes 11 | + CPU classes 12 | + Mutex wait 13 | * Users can now add their own plots (#111) 14 | * Add light/dark mode selector (#108) 15 | 16 | v0.5.2 / 2023-03-29 17 | ============== 18 | * Ensure all files have a correct Content-Type (#106) 19 | 20 | v0.5.1 / 2022-09-30 21 | ============== 22 | * Fix UI on Firefox (#83) 23 | 24 | v0.5.0 / 2022-09-05 25 | ============== 26 | * Switch to runtime/metrics as source, major refactor (#75) 27 | + New heatmap UI component 28 | + Dynamic plots definition based on server side generated config 29 | + Add many new plots (scheduler latency, scheduling events, and more) 30 | + Add play/pause switch button 31 | + Add show/hide GC events switch button 32 | + Add time range selector (1m, 5m, 10m) 33 | * Switch javascript code to ES6 (#65) 34 | * Build and test all examples (#63) 35 | 36 | v0.4.1 / 2021-12-12 37 | ============== 38 | * Assets are `go:embed`ed, so the minimum go version is now go1.16 (#55) 39 | * Polishing (README, small UI improvements) (#54) 40 | * Small ui improvements: link to go.dev rather than golang.org 41 | 42 | v0.4.0 / 2021-05-08 43 | ================== 44 | 45 | * Auto-reconnect to new server from GUI after closed websocket connection (#49) 46 | * Reorganize examples (#51) 47 | * Make `IndexAtRoot` returns an `http.HandlerFunc` instead of `http.Handler` (#52) 48 | 49 | v0.3.0 / 2021-02-14 50 | ================== 51 | 52 | * Enable 'save as png' button on plots (#44) 53 | 54 | v0.2.2 / 2020-12-13 55 | ================== 56 | 57 | * Use Go Modules for 'github.com/gorilla/websocket' (#39) 58 | * Support custom frequency (#37) 59 | * Added fixed go-chi example (#38) 60 | * `_example`: add echo (#22) 61 | * `_example`: add gin example (#34) 62 | * ci: track coverage 63 | * RegisterDefault returns an error now 64 | * Ensure send frequency is a strictly positive integer 65 | * Don't log if we can't upgrade to websocket 66 | * `_example`_example: add chi router (#38) 67 | * `_example`_example: change structure to have one example per directory 68 | 69 | v0.2.1 / 2020-10-29 70 | =================== 71 | 72 | * Fix websocket handler now working with https (#25) 73 | 74 | v0.2.0 / 2020-10-25 75 | =================== 76 | 77 | * `Register` now accepts options (functional options API) (#20) 78 | + `Root` allows to root statsviz at a path different than `/debug/statsviz` 79 | + `SendFrequency` allows to set the frequency at which stats are emitted. 80 | 81 | v0.1.1 / 2020-10-12 82 | =================== 83 | 84 | * Do not leak timer in sendStats 85 | 86 | v0.1.0 / 2020-10-10 87 | =================== 88 | 89 | * First released version 90 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | First of all, thank you for considering to contribute to Statsviz! 5 | 6 | Pull-requests are welcome! 7 | 8 | 9 | ## Go library 10 | 11 | The Statsviz Go public API is relatively light so there's not much to do and at 12 | the moment it's unlikely that the API will change. However new options can be 13 | added to `statsviz.Register` and `statsviz.NewServer` without breaking 14 | compatibility. 15 | 16 | That being said, there may be things to improve in the implementation, any 17 | contribution is very welcome! 18 | 19 | Big changes should be discussed on the issue tracker prior to start working on 20 | the code. 21 | 22 | If you've decided to contribute, thank you so much, please comment on the existing 23 | issue or create one stating what you want to tackle and why. 24 | 25 | 26 | ## User interface (html/css/javascript) 27 | 28 | The user interface aims to be simple, light and minimal. 29 | 30 | Assets are located in the `internal/static` directory and are embedded with 31 | [`go:embed`](https://pkg.go.dev/embed). 32 | 33 | Depending on what your modifications are, it's always a good idea to check that 34 | some of the examples in [./_example](./_example/) work with your modifications 35 | to Statsviz. To do so `cd` to the directory of the example and run: 36 | 37 | go mod edit -replace=github.com/arl/statsviz=../../ 38 | 39 | 40 | ## Documentation 41 | 42 | No contribution is too small, improvements to code comments and/or README 43 | are welcome! 44 | 45 | 46 | ## Examples 47 | 48 | There are many Go libraries to handle HTTP requests, routing, etc.. 49 | 50 | Feel free to add an example to show how to register Statsviz with your favourite 51 | library. 52 | 53 | To do so, please add a directory under `./_example`. For instance, if you want to add an 54 | example showing how to register Statsviz within library `foobar`: 55 | 56 | - create a directory `./_example/foobar/` 57 | - create a file `./_example/foobar/main.go` 58 | - call `go example.Work()` as the first line of your example (see other 59 | examples). This forces the garbage collector to _do something_ so that 60 | Statsviz interface won't remain static when an user runs your example. 61 | - the code should be `gofmt`ed 62 | - the example should compile and run 63 | - when ran, Statsviz interface should be accessible at http://localhost:8080/debug/statsviz 64 | 65 | 66 | Thank you! 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Aurélien Rainone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=round-square)](https://pkg.go.dev/github.com/arl/statsviz) 2 | [![Latest tag](https://img.shields.io/github/tag/arl/statsviz.svg)](https://github.com/arl/statsviz/tag/) 3 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) 4 | 5 | [![Test Actions Status](https://github.com/arl/statsviz/workflows/Tests-linux/badge.svg)](https://github.com/arl/statsviz/actions) 6 | [![Test Actions Status](https://github.com/arl/statsviz/workflows/Tests-others/badge.svg)](https://github.com/arl/statsviz/actions) 7 | [![codecov](https://codecov.io/gh/arl/statsviz/branch/main/graph/badge.svg)](https://codecov.io/gh/arl/statsviz) 8 | 9 | # Statsviz 10 | 11 |

12 | Statsviz Gopher Logo 13 | statsviz ui 14 |

15 |
16 | 17 | Visualize real time plots of your Go program runtime metrics, including heap, objects, goroutines, GC pauses, scheduler and more, in your browser. 18 | 19 |
20 | 21 | - [Statsviz](#statsviz) 22 | - [Install](#install) 23 | - [Usage](#usage) 24 | - [Advanced Usage](#advanced-usage) 25 | - [How Does That Work?](#how-does-that-work) 26 | - [Documentation](#documentation) 27 | - [Go API](#go-api) 28 | - [User interface](#user-interface) 29 | - [Plots](#plots) 30 | - [User Plots](#user-plots) 31 | - [Examples](#examples) 32 | - [Questions / Troubleshooting](#questions--troubleshooting) 33 | - [Contributing](#contributing) 34 | - [Changelog](#changelog) 35 | - [License: MIT](#license-mit) 36 | 37 | ## Install 38 | 39 | Download the latest version: 40 | 41 | ``` 42 | go get github.com/arl/statsviz@latest 43 | ``` 44 | 45 | Please note that, as new metrics are added to the `/runtime/metrics` package, new plots are added to Statsviz. 46 | This also means that the presence of some plots on the dashboard depends on the Go version you're using. 47 | 48 | When in doubt, use the latest ;-) 49 | 50 | 51 | ## Usage 52 | 53 | Register `Statsviz` HTTP handlers with your application `http.ServeMux`. 54 | 55 | ```go 56 | mux := http.NewServeMux() 57 | statsviz.Register(mux) 58 | 59 | go func() { 60 | log.Println(http.ListenAndServe("localhost:8080", mux)) 61 | }() 62 | ``` 63 | 64 | Open your browser at http://localhost:8080/debug/statsviz 65 | 66 | 67 | ## Advanced Usage 68 | 69 | If you want more control over Statsviz HTTP handlers, examples are: 70 | - you're using some HTTP framework 71 | - you want to place Statsviz handler behind some middleware 72 | 73 | then use `statsviz.NewServer` to obtain a `Server` instance. Both the `Index()` and `Ws()` methods return `http.HandlerFunc`. 74 | 75 | ```go 76 | srv, err := statsviz.NewServer(); // Create server or handle error 77 | srv.Index() // UI (dashboard) http.HandlerFunc 78 | srv.Ws() // Websocket http.HandlerFunc 79 | ``` 80 | 81 | Please look at examples of usage in the [Examples](_example) directory. 82 | 83 | 84 | ## How Does That Work? 85 | 86 | `statsviz.Register` registers 2 HTTP handlers within the given `http.ServeMux`: 87 | 88 | - the `Index` handler serves Statsviz user interface at `/debug/statsviz` at the address served by your program. 89 | 90 | - The `Ws` serves a Websocket endpoint. When the browser connects to that endpoint, [runtime/metrics](https://pkg.go.dev/runtime/metrics) are sent to the browser, once per second. 91 | 92 | Data points are in a browser-side circular-buffer. 93 | 94 | 95 | ## Documentation 96 | 97 | ### Go API 98 | 99 | Check out the API reference on [pkg.go.dev](https://pkg.go.dev/github.com/arl/statsviz#section-documentation). 100 | 101 | ### User interface 102 | 103 | Controls at the top of the page act on all plots: 104 | 105 | menu 106 | 107 | - the groom shows/hides the vertical lines representing garbage collections. 108 | - the time range selector defines the visualized time span. 109 | - the play/pause icons stops and resume the refresh of the plots. 110 | - the light/dark selector switches between light and dark modes. 111 | 112 | On top of each plot there are 2 icons: 113 | 114 | menu 115 | 116 | - the camera downloads a PNG image of the plot. 117 | - the info icon shows details about the metrics displayed. 118 | 119 | ### Plots 120 | 121 | Depending on your go version, some plots may not be available. 122 | 123 | #### Heap (global) 124 | 125 | heap-global 126 | 127 | #### Heap (details) 128 | 129 | heap-details 130 | 131 | #### Live Objects in Heap 132 | 133 | live-objects 134 | 135 | #### Live Bytes in Heap 136 | 137 | live-bytes 138 | 139 | #### MSpan/MCache 140 | 141 | mspan-mcache 142 | 143 | #### Memory classes 144 | 145 | memory-classes 146 | 147 | #### Goroutines 148 | 149 | goroutines 150 | 151 | #### Size Classes 152 | 153 | size-classes 154 | 155 | #### GC Scan 156 | 157 | gc-scan 158 | 159 | #### GC Cycles 160 | 161 | gc-cycles 162 | 163 | #### Stop-the-world Pause Latencies 164 | 165 | gc-pauses 166 | 167 | #### CPU Classes (GC) 168 | 169 | cpu-classes-gc 170 | 171 | #### Time Goroutines Spend in 'Runnable' state 172 | 173 | runnable-time 174 | 175 | #### Time Goroutines Spend Blocked on Mutexes 176 | 177 | mutex-wait 178 | 179 | #### Starting Size of Goroutines Stacks 180 | 181 | gc-stack-size 182 | 183 | #### Goroutine Scheduling Events 184 | 185 | sched-events 186 | 187 | #### CGO Calls 188 | 189 | cgo 190 | 191 | 192 | ### User Plots 193 | 194 | Since `v0.6` you can add your own plots to Statsviz dashboard, in order to easily 195 | visualize your application metrics next to runtime metrics. 196 | 197 | Please see the [userplots example](_example/userplots/main.go). 198 | 199 | ## Examples 200 | 201 | Check out the [\_example](./_example/README.md) directory to see various ways to use Statsviz, such as: 202 | 203 | - use of `http.DefaultServeMux` or your own `http.ServeMux` 204 | - wrap HTTP handler behind a middleware 205 | - register the web page at `/foo/bar` instead of `/debug/statsviz` 206 | - use `https://` rather than `http://` 207 | - register Statsviz handlers with various Go HTTP libraries/frameworks: 208 | - [echo](https://github.com/labstack/echo/) 209 | - [fasthttp](https://github.com/valyala/fasthttp) 210 | - [fiber](https://github.com/gofiber/fiber/) 211 | - [gin](https://github.com/gin-gonic/gin) 212 | - and many others thanks to many contributors! 213 | 214 | ## Questions / Troubleshooting 215 | 216 | Either use GitHub's [discussions](https://github.com/arl/statsviz/discussions) or come to say hi and ask a live question on [#statsviz channel on Gopher's slack](https://gophers.slack.com/archives/C043DU4NZ9D). 217 | 218 | ## Contributing 219 | 220 | Please use [issues](https://github.com/arl/statsviz/issues/new/choose) for bugs and feature requests. 221 | Pull-requests are always welcome! 222 | More details in [CONTRIBUTING.md](CONTRIBUTING.md). 223 | 224 | ## Changelog 225 | 226 | See [CHANGELOG.md](./CHANGELOG.md). 227 | 228 | ## License: MIT 229 | 230 | See [LICENSE](LICENSE) 231 | -------------------------------------------------------------------------------- /_example/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.test 2 | -------------------------------------------------------------------------------- /_example/README.md: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | ## Using [net/http](https://pkg.go.dev/net/http) 5 | 6 | Using `http.DefaultServeMux`: 7 | - [default/main.go](./default/main.go) 8 | 9 | Using your own `http.ServeMux`: 10 | - [mux/main.go](./mux/main.go) 11 | 12 | Use statsviz options API to serve Statsviz web UI on `/foo/bar` (instead of default 13 | `/debug/statsviz`) and send metrics with a frequency of _250ms_ (rather than _1s_): 14 | - [options/main.go](./options/main.go) 15 | 16 | Serve the the web UI via `https` and Websocket via `wss`: 17 | - [https/main.go](./https/main.go) 18 | 19 | Wrap statsviz handlers behind a middleware (HTTP Basic Authentication for example): 20 | - [middleware/main.go](./middleware/main.go) 21 | 22 | 23 | ## Using various Go libraries 24 | 25 | With [gorilla/mux](https://github.com/gorilla/mux) router: 26 | - [gorilla/main.go](./gorilla/main.go) 27 | 28 | Using [valyala/fasthttp](https://github.com/valyala/fasthttp) and [soheilhy/cmux](https://github.com/soheilhy/cmux): 29 | - [fasthttp/main.go](./fasthttp/main.go) 30 | 31 | Using [labstack/echo](https://github.com/labstack/echo) router: 32 | - [echo/main.go](./echo/main.go) 33 | 34 | With [gin-gonic/gin](https://github.com/gin-gonic/gin) web framework: 35 | - [gin/main.go](./gin/main.go) 36 | 37 | With [go-chi/chi](https://github.com/go-chi/chi) router: 38 | - [chi/main.go](./chi/main.go) 39 | 40 | With [gofiber/fiber](https://github.com/gofiber/fiber) web framework: 41 | - [fiber/main.go](./fiber/main.go) 42 | 43 | With [kataras/iris](https://github.com/kataras/iris) web framework: 44 | - [iris/main.go](./iris/main.go) 45 | 46 | -------------------------------------------------------------------------------- /_example/chi/go.mod: -------------------------------------------------------------------------------- 1 | module example/chi 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/arl/statsviz v0.6.0 7 | github.com/go-chi/chi v1.5.4 8 | ) 9 | 10 | require github.com/gorilla/websocket v1.5.0 // indirect 11 | 12 | replace github.com/arl/statsviz => ../../ 13 | -------------------------------------------------------------------------------- /_example/chi/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 2 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 3 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 4 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 6 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 7 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 8 | -------------------------------------------------------------------------------- /_example/chi/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/arl/statsviz" 9 | "github.com/go-chi/chi" 10 | 11 | example "github.com/arl/statsviz/_example" 12 | ) 13 | 14 | func main() { 15 | // Force the GC to work to make the plots "move". 16 | go example.Work() 17 | 18 | // Create statsviz server. 19 | srv, _ := statsviz.NewServer() 20 | 21 | // Create a chi router and register statsviz http handlers. 22 | r := chi.NewRouter() 23 | r.Get("/debug/statsviz/ws", srv.Ws()) 24 | r.Get("/debug/statsviz", func(w http.ResponseWriter, r *http.Request) { 25 | http.Redirect(w, r, "/debug/statsviz/", 301) 26 | }) 27 | r.Handle("/debug/statsviz/*", srv.Index()) 28 | 29 | mux := http.NewServeMux() 30 | mux.Handle("/", r) 31 | 32 | fmt.Println("Point your browser to http://localhost:8081/debug/statsviz/") 33 | if err := http.ListenAndServe(":8081", mux); err != nil { 34 | log.Fatalf("failed to start server: %s", err) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /_example/default/go.mod: -------------------------------------------------------------------------------- 1 | module example/default 2 | 3 | go 1.19 4 | 5 | require github.com/arl/statsviz v0.6.0 6 | 7 | require github.com/gorilla/websocket v1.5.0 // indirect 8 | 9 | replace github.com/arl/statsviz => ../../ 10 | -------------------------------------------------------------------------------- /_example/default/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 2 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 3 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 4 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 5 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 6 | -------------------------------------------------------------------------------- /_example/default/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/arl/statsviz" 9 | example "github.com/arl/statsviz/_example" 10 | ) 11 | 12 | func main() { 13 | // Force the GC to work to make the plots "move". 14 | go example.Work() 15 | 16 | // Register a Statsviz server on the default mux. 17 | statsviz.Register(http.DefaultServeMux) 18 | 19 | fmt.Println("Point your browser to http://localhost:8080/debug/statsviz/") 20 | log.Fatal(http.ListenAndServe(":8080", nil)) 21 | } 22 | -------------------------------------------------------------------------------- /_example/echo/go.mod: -------------------------------------------------------------------------------- 1 | module example/echo 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/arl/statsviz v0.6.0 7 | github.com/labstack/echo/v4 v4.11.1 8 | ) 9 | 10 | require ( 11 | github.com/gorilla/websocket v1.5.0 // indirect 12 | github.com/labstack/gommon v0.4.0 // indirect 13 | github.com/mattn/go-colorable v0.1.13 // indirect 14 | github.com/mattn/go-isatty v0.0.19 // indirect 15 | github.com/valyala/bytebufferpool v1.0.0 // indirect 16 | github.com/valyala/fasttemplate v1.2.2 // indirect 17 | golang.org/x/crypto v0.13.0 // indirect 18 | golang.org/x/net v0.15.0 // indirect 19 | golang.org/x/sys v0.12.0 // indirect 20 | golang.org/x/text v0.13.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /_example/echo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 2 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 7 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 8 | github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= 9 | github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= 10 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= 11 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 12 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 13 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 14 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 15 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 16 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 17 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 18 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 19 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 20 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 22 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 23 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 24 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 25 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 26 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 27 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 28 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 29 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 30 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 31 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 32 | golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= 33 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 34 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 40 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 42 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 43 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 44 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 45 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 46 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 48 | -------------------------------------------------------------------------------- /_example/echo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/labstack/echo/v4" 8 | 9 | "github.com/arl/statsviz" 10 | example "github.com/arl/statsviz/_example" 11 | ) 12 | 13 | func main() { 14 | // Force the GC to work to make the plots "move". 15 | go example.Work() 16 | 17 | // Echo instance 18 | e := echo.New() 19 | 20 | mux := http.NewServeMux() 21 | 22 | // Register statsviz handlerson the mux. 23 | statsviz.Register(mux) 24 | 25 | // Use echo WrapHandler to wrap statsviz ServeMux as echo HandleFunc 26 | e.GET("/debug/statsviz/", echo.WrapHandler(mux)) 27 | // Serve static content for statsviz UI 28 | e.GET("/debug/statsviz/*", echo.WrapHandler(mux)) 29 | 30 | // Start server 31 | fmt.Println("Point your browser to http://localhost:8082/debug/statsviz/") 32 | e.Logger.Fatal(e.Start(":8082")) 33 | } 34 | -------------------------------------------------------------------------------- /_example/fasthttp/go.mod: -------------------------------------------------------------------------------- 1 | module example/fasthttp 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/arl/statsviz v0.6.0 7 | github.com/fasthttp/router v1.4.12 8 | github.com/soheilhy/cmux v0.1.5 9 | github.com/valyala/fasthttp v1.44.0 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.0.4 // indirect 14 | github.com/gorilla/websocket v1.5.0 // indirect 15 | github.com/klauspost/compress v1.15.9 // indirect 16 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect 17 | github.com/valyala/bytebufferpool v1.0.0 // indirect 18 | golang.org/x/net v0.7.0 // indirect 19 | golang.org/x/text v0.7.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /_example/fasthttp/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 2 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 4 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 5 | github.com/fasthttp/router v1.4.12 h1:QEgK+UKARaC1bAzJgnIhdUMay6nwp+YFq6VGPlyKN1o= 6 | github.com/fasthttp/router v1.4.12/go.mod h1:41Qdc4Z4T2pWVVtATHCnoUnOtxdBoeKEYJTXhHwbxCQ= 7 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 8 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 9 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 10 | github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 11 | github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= 12 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 13 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 14 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= 15 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= 16 | github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= 17 | github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= 18 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 19 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 20 | github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= 21 | github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q= 22 | github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= 23 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 24 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 25 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 26 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 27 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 28 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 29 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 30 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 31 | golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 32 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 33 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 44 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 45 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 46 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 47 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 48 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 49 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 50 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 51 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 52 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 53 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 54 | -------------------------------------------------------------------------------- /_example/fasthttp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | 8 | "github.com/fasthttp/router" 9 | "github.com/soheilhy/cmux" 10 | "github.com/valyala/fasthttp" 11 | "github.com/valyala/fasthttp/fasthttpadaptor" 12 | 13 | "github.com/arl/statsviz" 14 | example "github.com/arl/statsviz/_example" 15 | ) 16 | 17 | func main() { 18 | // Force the GC to work to make the plots "move". 19 | go example.Work() 20 | 21 | // Create the main listener and mux 22 | l, _ := net.Listen("tcp", ":8083") 23 | m := cmux.New(l) 24 | ws := http.NewServeMux() 25 | 26 | // fasthttp routers 27 | r := router.New() 28 | r.GET("/", func(ctx *fasthttp.RequestCtx) { 29 | fmt.Fprintf(ctx, "Hello, world!") 30 | }) 31 | 32 | // Create statsviz server. 33 | srv, _ := statsviz.NewServer() 34 | 35 | // Register Statsviz server on the fasthttp router. 36 | r.GET("/debug/statsviz/{filepath:*}", fasthttpadaptor.NewFastHTTPHandler(srv.Index())) 37 | ws.HandleFunc("/debug/statsviz/ws", srv.Ws()) 38 | 39 | // Server start 40 | go http.Serve(m.Match(cmux.HTTP1HeaderField("Upgrade", "websocket")), ws) 41 | go fasthttp.Serve(m.Match(cmux.Any()), r.Handler) 42 | fmt.Println("Point your browser to http://localhost:8083/debug/statsviz/") 43 | m.Serve() 44 | } 45 | -------------------------------------------------------------------------------- /_example/fiber/go.mod: -------------------------------------------------------------------------------- 1 | module example/fiber 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/arl/statsviz v0.6.0 7 | github.com/gofiber/adaptor/v2 v2.2.1 8 | github.com/gofiber/fiber/v2 v2.49.2 9 | github.com/soheilhy/cmux v0.1.5 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.0.5 // indirect 14 | github.com/google/uuid v1.3.1 // indirect 15 | github.com/gorilla/websocket v1.5.0 // indirect 16 | github.com/klauspost/compress v1.16.7 // indirect 17 | github.com/mattn/go-colorable v0.1.13 // indirect 18 | github.com/mattn/go-isatty v0.0.19 // indirect 19 | github.com/mattn/go-runewidth v0.0.15 // indirect 20 | github.com/rivo/uniseg v0.2.0 // indirect 21 | github.com/valyala/bytebufferpool v1.0.0 // indirect 22 | github.com/valyala/fasthttp v1.49.0 // indirect 23 | github.com/valyala/tcplisten v1.0.0 // indirect 24 | golang.org/x/net v0.8.0 // indirect 25 | golang.org/x/sys v0.12.0 // indirect 26 | golang.org/x/text v0.8.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /_example/fiber/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= 2 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 4 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 5 | github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4= 6 | github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc= 7 | github.com/gofiber/fiber/v2 v2.49.2 h1:ONEN3/Vc+dUCxxDgZZwpqvhISgHqb+bu+isBiEyKEQs= 8 | github.com/gofiber/fiber/v2 v2.49.2/go.mod h1:gNsKnyrmfEWFpJxQAV0qvW6l70K1dZGno12oLtukcts= 9 | github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= 10 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 12 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 13 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= 14 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 15 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 16 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 17 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 18 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 19 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 20 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 21 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 22 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 23 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 24 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 25 | github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= 26 | github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= 27 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 28 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 29 | github.com/valyala/fasthttp v1.49.0 h1:9FdvCpmxB74LH4dPb7IJ1cOSsluR07XG3I1txXWwJpE= 30 | github.com/valyala/fasthttp v1.49.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= 31 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 32 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 33 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 34 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 35 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 36 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 37 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= 38 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 39 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 40 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 45 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 47 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 48 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 49 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 50 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 51 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 52 | -------------------------------------------------------------------------------- /_example/fiber/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/gofiber/adaptor/v2" 10 | "github.com/gofiber/fiber/v2" 11 | "github.com/soheilhy/cmux" 12 | 13 | "github.com/arl/statsviz" 14 | example "github.com/arl/statsviz/_example" 15 | ) 16 | 17 | func main() { 18 | // Force the GC to work to make the plots "move". 19 | go example.Work() 20 | 21 | // Create the main listener and mux 22 | l, err := net.Listen("tcp", ":8093") 23 | if err != nil { 24 | fmt.Println(err) 25 | os.Exit(1) 26 | } 27 | m := cmux.New(l) 28 | ws := http.NewServeMux() 29 | 30 | // Fiber instance 31 | app := fiber.New() 32 | app.Get("/", func(c *fiber.Ctx) error { 33 | return c.SendString("Hello, World 👋!") 34 | }) 35 | 36 | // Create statsviz server. 37 | srv, err := statsviz.NewServer() 38 | 39 | // Register Statsviz server on the fasthttp router. 40 | app.Use("/debug/statsviz", adaptor.HTTPHandler(srv.Index())) 41 | ws.HandleFunc("/debug/statsviz/ws", srv.Ws()) 42 | 43 | fmt.Println("Point your browser to http://localhost:8093/debug/statsviz/") 44 | 45 | // Server start 46 | go http.Serve(m.Match(cmux.HTTP1HeaderField("Upgrade", "websocket")), ws) 47 | go app.Listener(m.Match(cmux.Any())) 48 | m.Serve() 49 | } 50 | -------------------------------------------------------------------------------- /_example/gin/go.mod: -------------------------------------------------------------------------------- 1 | module example/gin 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/arl/statsviz v0.6.0 7 | github.com/gin-gonic/gin v1.9.1 8 | ) 9 | 10 | require ( 11 | github.com/bytedance/sonic v1.9.1 // indirect 12 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 13 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 14 | github.com/gin-contrib/sse v0.1.0 // indirect 15 | github.com/go-playground/locales v0.14.1 // indirect 16 | github.com/go-playground/universal-translator v0.18.1 // indirect 17 | github.com/go-playground/validator/v10 v10.14.0 // indirect 18 | github.com/goccy/go-json v0.10.2 // indirect 19 | github.com/gorilla/websocket v1.5.0 // indirect 20 | github.com/json-iterator/go v1.1.12 // indirect 21 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 22 | github.com/kr/pretty v0.1.0 // indirect 23 | github.com/leodido/go-urn v1.2.4 // indirect 24 | github.com/mattn/go-isatty v0.0.19 // indirect 25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 26 | github.com/modern-go/reflect2 v1.0.2 // indirect 27 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 28 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 29 | github.com/ugorji/go/codec v1.2.11 // indirect 30 | golang.org/x/arch v0.3.0 // indirect 31 | golang.org/x/crypto v0.9.0 // indirect 32 | golang.org/x/net v0.10.0 // indirect 33 | golang.org/x/sys v0.8.0 // indirect 34 | golang.org/x/text v0.9.0 // indirect 35 | google.golang.org/protobuf v1.30.0 // indirect 36 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 37 | gopkg.in/yaml.v3 v3.0.1 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /_example/gin/go.sum: -------------------------------------------------------------------------------- 1 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 2 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 3 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 4 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 5 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 6 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 7 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 8 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 13 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 14 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 15 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 16 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 17 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 19 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 20 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 21 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 22 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 23 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 24 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 25 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 26 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 27 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 28 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 32 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 33 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 34 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 35 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 36 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 37 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 38 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 39 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 40 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 41 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 42 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 43 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 44 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 45 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 46 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 47 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 49 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 50 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 51 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 52 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 53 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 54 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 55 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 56 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 57 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 58 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 59 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 60 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 61 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 62 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 63 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 64 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 65 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 66 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 67 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 68 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 69 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 70 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 71 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 72 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 73 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 74 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 75 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 76 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 77 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 78 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 79 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 80 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 81 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 82 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 83 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 84 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 85 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 86 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 87 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 88 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 89 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 90 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 91 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 92 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 93 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 94 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 95 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 96 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 97 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 98 | -------------------------------------------------------------------------------- /_example/gin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gin-gonic/gin" 7 | 8 | "github.com/arl/statsviz" 9 | example "github.com/arl/statsviz/_example" 10 | ) 11 | 12 | func main() { 13 | // Force the GC to work to make the plots "move". 14 | go example.Work() 15 | 16 | // Create statsviz server. 17 | srv, _ := statsviz.NewServer() 18 | 19 | ws := srv.Ws() 20 | index := srv.Index() 21 | 22 | // Register Statsviz server on the gin router. 23 | router := gin.New() 24 | router.GET("/debug/statsviz/*filepath", func(context *gin.Context) { 25 | if context.Param("filepath") == "/ws" { 26 | ws(context.Writer, context.Request) 27 | return 28 | } 29 | index(context.Writer, context.Request) 30 | }) 31 | 32 | fmt.Printf("Point your browser to http://localhost:8085/debug/statsviz/\n\n") 33 | router.Run(":8085") 34 | } 35 | -------------------------------------------------------------------------------- /_example/gorilla/go.mod: -------------------------------------------------------------------------------- 1 | module example/gorilla 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/arl/statsviz v0.6.0 7 | github.com/gorilla/mux v1.8.0 8 | ) 9 | 10 | require github.com/gorilla/websocket v1.5.0 // indirect 11 | -------------------------------------------------------------------------------- /_example/gorilla/go.sum: -------------------------------------------------------------------------------- 1 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 2 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 3 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 4 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 5 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 6 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 7 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 8 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 9 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 10 | -------------------------------------------------------------------------------- /_example/gorilla/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | 9 | "github.com/arl/statsviz" 10 | example "github.com/arl/statsviz/_example" 11 | ) 12 | 13 | func main() { 14 | // Force the GC to work to make the plots "move". 15 | go example.Work() 16 | 17 | // Create a Gorilla router. 18 | r := mux.NewRouter() 19 | 20 | // Create statsviz server and register the handlers on the router. 21 | srv, _ := statsviz.NewServer() 22 | r.Methods("GET").Path("/debug/statsviz/ws").Name("GET /debug/statsviz/ws").HandlerFunc(srv.Ws()) 23 | r.Methods("GET").PathPrefix("/debug/statsviz/").Name("GET /debug/statsviz/").Handler(srv.Index()) 24 | 25 | mux := http.NewServeMux() 26 | mux.Handle("/", r) 27 | 28 | fmt.Println("Point your browser to http://localhost:8086/debug/statsviz/") 29 | http.ListenAndServe(":8086", mux) 30 | } 31 | -------------------------------------------------------------------------------- /_example/https/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEojCCA4qgAwIBAgIUfNis9Twx3ilhjEyeGXHhC1iHzoowDQYJKoZIhvcNAQEL 3 | BQAwfzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMREwDwYDVQQHDAhOZXcgWW9y 4 | azEVMBMGA1UECgwMRXhhbXBsZSwgTExDMRgwFgYDVQQDDA9FeGFtcGxlIENvbXBh 5 | bnkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wIBcNMjIwMjEzMTcy 6 | MDExWhgPMjEyMjAxMjAxNzIwMTFaMH8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJO 7 | WTERMA8GA1UEBwwITmV3IFlvcmsxFTATBgNVBAoMDEV4YW1wbGUsIExMQzEYMBYG 8 | A1UEAwwPRXhhbXBsZSBDb21wYW55MR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1w 9 | bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvf+VXipWa+YG 10 | HjQ2jxuhpbfTzECHI4qkRudbTQLNpjkTX/ZxuZz67DMudXgw2jhlcFoimmo4piRt 11 | 9hdqFENZhr9Jr3iUYVEhrPMpKxuOTDIsUTnN5+O5Obg0sWpfWMu+UffOF88//ao+ 12 | XAQPfEsq08zxarJpLf1NA7WxCslFA0Jcb49BHEot083D3OieCueRr1Yn9Q3wDaU2 13 | mLH58tlBIgY6mqOFtT0gmZLkwCPiXCFsnCtOsJVxlBmd+YmD1PKqrEAu4i3a9sFR 14 | ViBgpHfhHP6FsooW6tLVCDzupW/iRuVwbolYqOFeVDBdVPQ6RGfH3+Jdi1Frpr8A 15 | W8tlFOJNSwIDAQABo4IBEjCCAQ4wHQYDVR0OBBYEFDk+6W8t+Mk5LhpEfDfL6WD8 16 | +veQMB8GA1UdIwQYMBaAFDk+6W8t+Mk5LhpEfDfL6WD8+veQMAkGA1UdEwQCMAAw 17 | CwYDVR0PBAQDAgWgMIGFBgNVHREEfjB8ggtleGFtcGxlLmNvbYIPd3d3LmV4YW1w 18 | bGUuY29tghBtYWlsLmV4YW1wbGUuY29tgg9mdHAuZXhhbXBsZS5jb22CCWxvY2Fs 19 | aG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggkxMjcuMC4wLjGCAzo6MYIHZmU4 20 | MDo6MTAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNh 21 | dGUwDQYJKoZIhvcNAQELBQADggEBAH+5ATKO0/VG40S1Bgtp+iDDG5QuVwPq7whi 22 | 4wdaezkFYSBmczSt9UFXxuAysVe8S5KT3MT+oMfkmQQe2tZmxW2nuSIlTsm3IyLG 23 | OjQOhgiIXDk9w0nI65yt23fVDRS/z2jJ+uovm5Ss5scYJmhiBJpMYMxqN3klleyt 24 | 7Ma+rlsPTlw4+LhZd2URZr0beYzP13TVzLkA4Nrkd1XbnDRtV2JAGy8KphHMCOtH 25 | ZmY+izBjRgfzJ/mEkb8CGoWk9SXLNsru2GEbOUUOc54pQHzlR4/lmadD4DJlp4mt 26 | Jckk5FujRuDjYhcS+pJ4mCEjGOtpfBUPBmfHqgzeetuuEOuEcBc= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /_example/https/example_com.conf: -------------------------------------------------------------------------------- 1 | # Self Signed (note the addition of -x509): 2 | # openssl req -config example_com.conf -new -x509 -sha256 -newkey rsa:2048 -nodes -keyout example_com.key.pem -days 36500 -out example_com.cert.pem 3 | # Signing Request (note the lack of -x509): 4 | # openssl req -config example_com.conf -new -newkey rsa:2048 -nodes -keyout example_com.key.pem -days 36500 -out example_com.req.pem 5 | # Print it: 6 | # openssl x509 -in example_com.cert.pem -text -noout 7 | # openssl req -in example_com.req.pem -text -noout 8 | 9 | [ req ] 10 | default_bits = 2048 11 | default_keyfile = server-key.pem 12 | distinguished_name = subject 13 | req_extensions = req_ext 14 | x509_extensions = x509_ext 15 | string_mask = utf8only 16 | 17 | # The Subject DN can be formed using X501 or RFC 4514 (see RFC 4519 for a description). 18 | # It's sort of a mashup. For example, RFC 4514 does not provide emailAddress. 19 | [ subject ] 20 | countryName = Country Name (2 letter code) 21 | countryName_default = US 22 | 23 | stateOrProvinceName = State or Province Name (full name) 24 | stateOrProvinceName_default = NY 25 | 26 | localityName = Locality Name (eg, city) 27 | localityName_default = New York 28 | 29 | organizationName = Organization Name (eg, company) 30 | organizationName_default = Example, LLC 31 | 32 | # Use a friendly name here because it's presented to the user. The server's DNS 33 | # names are placed in Subject Alternate Names. Plus, DNS names here is deprecated 34 | # by both IETF and CA/Browser Forums. If you place a DNS name here, then you 35 | # must include the DNS name in the SAN too (otherwise, Chrome and others that 36 | # strictly follow the CA/Browser Baseline Requirements will fail). 37 | commonName = Common Name (e.g. server FQDN or YOUR name) 38 | commonName_default = Example Company 39 | 40 | emailAddress = Email Address 41 | emailAddress_default = test@example.com 42 | 43 | # Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ... 44 | [ x509_ext ] 45 | 46 | subjectKeyIdentifier = hash 47 | authorityKeyIdentifier = keyid,issuer 48 | 49 | # If RSA Key Transport bothers you, then remove keyEncipherment. TLS 1.3 is removing RSA 50 | # Key Transport in favor of exchanges with Forward Secrecy, like DHE and ECDHE. 51 | basicConstraints = CA:FALSE 52 | keyUsage = digitalSignature, keyEncipherment 53 | subjectAltName = @alternate_names 54 | nsComment = "OpenSSL Generated Certificate" 55 | 56 | # RFC 5280, Section 4.2.1.12 makes EKU optional 57 | # CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused 58 | # extendedKeyUsage = serverAuth, clientAuth 59 | 60 | # Section req_ext is used when generating a certificate signing request. I.e., openssl req ... 61 | [ req_ext ] 62 | 63 | subjectKeyIdentifier = hash 64 | 65 | basicConstraints = CA:FALSE 66 | keyUsage = digitalSignature, keyEncipherment 67 | subjectAltName = @alternate_names 68 | nsComment = "OpenSSL Generated Certificate" 69 | 70 | # RFC 5280, Section 4.2.1.12 makes EKU optional 71 | # CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused 72 | # extendedKeyUsage = serverAuth, clientAuth 73 | 74 | [ alternate_names ] 75 | 76 | DNS.1 = example.com 77 | DNS.2 = www.example.com 78 | DNS.3 = mail.example.com 79 | DNS.4 = ftp.example.com 80 | 81 | # Add these if you need them. But usually you don't want them or 82 | # need them in production. You may need them for development. 83 | DNS.5 = localhost 84 | DNS.6 = localhost.localdomain 85 | DNS.7 = 127.0.0.1 86 | 87 | # IPv6 localhost 88 | DNS.8 = ::1 89 | DNS.9 = fe80::1 -------------------------------------------------------------------------------- /_example/https/go.mod: -------------------------------------------------------------------------------- 1 | module example/https 2 | 3 | go 1.19 4 | 5 | require github.com/arl/statsviz v0.6.0 6 | 7 | require github.com/gorilla/websocket v1.5.0 // indirect 8 | -------------------------------------------------------------------------------- /_example/https/go.sum: -------------------------------------------------------------------------------- 1 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 2 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 3 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 4 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 6 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 7 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 8 | -------------------------------------------------------------------------------- /_example/https/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC9/5VeKlZr5gYe 3 | NDaPG6Glt9PMQIcjiqRG51tNAs2mORNf9nG5nPrsMy51eDDaOGVwWiKaajimJG32 4 | F2oUQ1mGv0mveJRhUSGs8ykrG45MMixROc3n47k5uDSxal9Yy75R984Xzz/9qj5c 5 | BA98SyrTzPFqsmkt/U0DtbEKyUUDQlxvj0EcSi3TzcPc6J4K55GvVif1DfANpTaY 6 | sfny2UEiBjqao4W1PSCZkuTAI+JcIWycK06wlXGUGZ35iYPU8qqsQC7iLdr2wVFW 7 | IGCkd+Ec/oWyihbq0tUIPO6lb+JG5XBuiVio4V5UMF1U9DpEZ8ff4l2LUWumvwBb 8 | y2UU4k1LAgMBAAECggEACjpgCBmvXU+ctLOK3FlYcltbA3vVyowEG+0IdTTZbUFV 9 | Ua0qohtZRq3aqmg1+QlgLJQtmLJvKkJ7yOupwj197EsP5GpRUovTjEPqy29IUcHB 10 | sP2zn/AaYIkSDRr01wrgy/jTF2UIxNcNVFbDnqVNQOmfdWb1Uox4QOLfDP2YwXwR 11 | RV1aHnbIFOo9kGZrb1B+K1tW31ysQA0dIj2p+e8x4QFM28iywnNFHvBZHWQFmwxd 12 | OwVSFT9iRweZHolc/Z95RkIzcmJpDQ5zPFlwvsgjvoorKrq/MCVm9VpuO7HcHJE4 13 | l69iRAjVUt/q4MBwk7YXZTzaDmBtmIhSKB25fH1PAQKBgQDpXbLETjU2yWHPHtPy 14 | o78fzG6NboOHOh7GS8dPX9uQzDz8FEvdxNPPSod3xSA6qlD3mdZnCIB2yKnrvFVF 15 | uKBYFE7r8nj4FgLj5uH57AeZG8L7g06VO1gf6+gTsMWzYn7SXHVk8rC7TmQazUDw 16 | h1wqBIc8e0QzthYk308ct34TMQKBgQDQbRl6NMPM+f/NqFAuwpeAszG+5qVuSdkS 17 | 4xAXvLOwUhKKyB22OR3iRfC4B2ma/bd5sp5CBy1lJB3VyOyV1usS5zf1ZbQOdDoN 18 | cvfKjNZECYLROv5qQW6U+7kFd9nMUk8F6gZ1ftFWILN2JVr0rzXEgjtAwYEHsGAZ 19 | kWRdrr+xOwKBgByeTf7+7E20spb429sks5qNsn0LsXuS6BU/U8jrI30FXvE29/rR 20 | WXxRUn2gogwvuNA0gHOQOljW0OfE59wuARPsIaWTzbv2zxHtzYadqGzhV68Kckeg 21 | yHTZtFM92XDfQlg/2HGxxXSi72nOBAm7P9UzSvZi04FX8i1BaW1HH2HxAoGAPoy/ 22 | XKGBMX7Feh7NEXtspD8i6rulYhffgX+LUNRcyMmYrvWnlQxHH6UFvPTciJsj+O3V 23 | +NLWmMfh/Eq0VbOOELo7XPYMt1nCIk8idjMWCo0gKVKfD5xbZ3Kz8CQrxNnexVOa 24 | ZKCOil59/TLwvQXoCPVceu6XnoscDBBKIqNS/AkCgYBqvyPM4b5MksgaD2kEV201 25 | Pb+LOzb/9TAqxe0OZmYcBHeFBgv16/GBYH49yni0m0lOQEFm9v/YKKk/bx7XvG7p 26 | uFgpcr3UEKCp0cAhHACmDXwP6KOl+nPJr51gCePaBXm1fLRjIWdFJYKWiV2DwNwD 27 | YhlmOOyyF5o9+WnOnwX4Gg== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /_example/https/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/arl/statsviz" 9 | example "github.com/arl/statsviz/_example" 10 | ) 11 | 12 | func main() { 13 | // Force the GC to work to make the plots "move". 14 | go example.Work() 15 | 16 | // Use your own certificates and key files. 17 | const certFile = "./cert.pem" 18 | const keyFile = "./key.pem" 19 | 20 | mux := http.NewServeMux() 21 | 22 | // Register Statsviz handlers on the mux. 23 | _ = statsviz.Register(mux) 24 | 25 | fmt.Println("Point your browser to https://localhost:8087/debug/statsviz/") 26 | log.Fatal(http.ListenAndServeTLS(":8087", certFile, keyFile, mux)) 27 | } 28 | -------------------------------------------------------------------------------- /_example/iris/go.mod: -------------------------------------------------------------------------------- 1 | module example/iris 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/arl/statsviz v0.6.0 7 | github.com/kataras/iris/v12 v12.1.8 8 | ) 9 | 10 | require ( 11 | github.com/BurntSushi/toml v1.2.0 // indirect 12 | github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect 13 | github.com/CloudyKit/jet/v3 v3.0.0 // indirect 14 | github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect 15 | github.com/ajg/form v1.5.1 // indirect 16 | github.com/aymerick/douceur v0.2.0 // indirect 17 | github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible // indirect 18 | github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 // indirect 19 | github.com/fatih/structs v1.1.0 // indirect 20 | github.com/google/go-querystring v1.1.0 // indirect 21 | github.com/gorilla/css v1.0.0 // indirect 22 | github.com/gorilla/websocket v1.5.0 // indirect 23 | github.com/imkira/go-interpol v1.1.0 // indirect 24 | github.com/iris-contrib/blackfriday v2.0.0+incompatible // indirect 25 | github.com/iris-contrib/jade v1.1.4 // indirect 26 | github.com/iris-contrib/pongo2 v0.0.1 // indirect 27 | github.com/iris-contrib/schema v0.0.6 // indirect 28 | github.com/json-iterator/go v1.1.12 // indirect 29 | github.com/kataras/golog v0.1.7 // indirect 30 | github.com/kataras/pio v0.0.11 // indirect 31 | github.com/kataras/sitemap v0.0.6 // indirect 32 | github.com/klauspost/compress v1.15.11 // indirect 33 | github.com/microcosm-cc/bluemonday v1.0.20 // indirect 34 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 35 | github.com/modern-go/reflect2 v1.0.2 // indirect 36 | github.com/moul/http2curl v1.0.0 // indirect 37 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 38 | github.com/ryanuber/columnize v2.1.0+incompatible // indirect 39 | github.com/schollz/closestmatch v2.1.0+incompatible // indirect 40 | github.com/sergi/go-diff v1.3.1 // indirect 41 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 42 | github.com/stretchr/testify v1.7.0 // indirect 43 | github.com/valyala/fasthttp v1.44.0 // indirect 44 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 45 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect 46 | github.com/yudai/gojsondiff v1.0.0 // indirect 47 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect 48 | golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect 49 | golang.org/x/net v0.7.0 // indirect 50 | golang.org/x/sys v0.5.0 // indirect 51 | golang.org/x/text v0.7.0 // indirect 52 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect 53 | gopkg.in/ini.v1 v1.67.0 // indirect 54 | gopkg.in/yaml.v2 v2.4.0 // indirect 55 | gopkg.in/yaml.v3 v3.0.1 // indirect 56 | ) 57 | -------------------------------------------------------------------------------- /_example/iris/go.sum: -------------------------------------------------------------------------------- 1 | github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= 4 | github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 5 | github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= 6 | github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= 7 | github.com/CloudyKit/jet/v3 v3.0.0 h1:1PwO5w5VCtlUUl+KTOBsTGZlhjWkcybsGaAau52tOy8= 8 | github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= 9 | github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= 10 | github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= 11 | github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= 12 | github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= 13 | github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= 14 | github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= 15 | github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 16 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 17 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 18 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 19 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 20 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 21 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 22 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 23 | github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible h1:Ppm0npCCsmuR9oQaBtRuZcmILVE74aXE+AmrJj8L2ns= 24 | github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= 25 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 26 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 27 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 28 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 29 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 30 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 31 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= 33 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 34 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 35 | github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= 36 | github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= 37 | github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= 38 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 39 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 40 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 41 | github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8= 42 | github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= 43 | github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= 44 | github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= 45 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= 46 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 47 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 48 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 49 | github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 50 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 51 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 52 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 53 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 54 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 55 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 56 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 57 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 58 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 59 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 60 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 61 | github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= 62 | github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= 63 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 64 | github.com/iris-contrib/blackfriday v2.0.0+incompatible h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4= 65 | github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= 66 | github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= 67 | github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= 68 | github.com/iris-contrib/jade v1.1.4 h1:WoYdfyJFfZIUgqNAeOyRfTNQZOksSlZ6+FnXR3AEpX0= 69 | github.com/iris-contrib/jade v1.1.4/go.mod h1:EDqR+ur9piDl6DUgs6qRrlfzmlx/D5UybogqrXvJTBE= 70 | github.com/iris-contrib/pongo2 v0.0.1 h1:zGP7pW51oi5eQZMIlGA3I+FHY9/HOQWDB+572yin0to= 71 | github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= 72 | github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= 73 | github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= 74 | github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= 75 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 76 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 77 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 78 | github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= 79 | github.com/kataras/golog v0.1.7 h1:0TY5tHn5L5DlRIikepcaRR/6oInIr9AiWsxzt0vvlBE= 80 | github.com/kataras/golog v0.1.7/go.mod h1:jOSQ+C5fUqsNSwurB/oAHq1IFSb0KI3l6GMa7xB6dZA= 81 | github.com/kataras/iris/v12 v12.1.8 h1:O3gJasjm7ZxpxwTH8tApZsvf274scSGQAUpNe47c37U= 82 | github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= 83 | github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= 84 | github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= 85 | github.com/kataras/pio v0.0.10/go.mod h1:gS3ui9xSD+lAUpbYnjOGiQyY7sUMJO+EHpiRzhtZ5no= 86 | github.com/kataras/pio v0.0.11 h1:kqreJ5KOEXGMwHAWHDwIl+mjfNCPhAwZPa8gK7MKlyw= 87 | github.com/kataras/pio v0.0.11/go.mod h1:38hH6SWH6m4DKSYmRhlrCJ5WItwWgCVrTNU62XZyUvI= 88 | github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= 89 | github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= 90 | github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= 91 | github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 92 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 93 | github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= 94 | github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 95 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 96 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 97 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 98 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 99 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 100 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 101 | github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= 102 | github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= 103 | github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= 104 | github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y= 105 | github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= 106 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 107 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 108 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 109 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 110 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 111 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 112 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 113 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 114 | github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= 115 | github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= 116 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= 117 | github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= 118 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= 119 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 120 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 121 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 122 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 123 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 124 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 125 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 126 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 127 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 128 | github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= 129 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 130 | github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= 131 | github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= 132 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 133 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 134 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 135 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 136 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 137 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 138 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 139 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 140 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 141 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 142 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 143 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 144 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 145 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 146 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 147 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 148 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 149 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 150 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 151 | github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q= 152 | github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= 153 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 154 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= 155 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 156 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 157 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 158 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 159 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 160 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 161 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= 162 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= 163 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 164 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 165 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 166 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 167 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 168 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 169 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 170 | golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 171 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 172 | golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= 173 | golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 174 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 175 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 176 | golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 177 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 178 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 179 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 180 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 181 | golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 182 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 183 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 184 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 185 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 186 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 187 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 189 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 190 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 191 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 192 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 193 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 194 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 195 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 196 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 197 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 198 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 199 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 200 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 201 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 202 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 203 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 204 | golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 205 | golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 206 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 207 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 208 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 209 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 210 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 211 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= 212 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 213 | gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 214 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 215 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 216 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 217 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 218 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 219 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 220 | gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 221 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 222 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 223 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 224 | -------------------------------------------------------------------------------- /_example/iris/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/kataras/iris/v12" 8 | 9 | "github.com/arl/statsviz" 10 | example "github.com/arl/statsviz/_example" 11 | ) 12 | 13 | func main() { 14 | // Force the GC to work to make the plots "move". 15 | go example.Work() 16 | 17 | app := iris.New() 18 | 19 | // Need to run iris in a separate goroutine so we can start the dedicated 20 | // http server for Statsviz. 21 | go app.Listen(":8089") 22 | 23 | mux := http.NewServeMux() 24 | 25 | // Register Statsviz handlers on the mux. 26 | _ = statsviz.Register(mux) 27 | 28 | fmt.Println("Point your browser to http://localhost:8088/debug/statsviz") 29 | 30 | // NewHost puts the http server for statsviz under the control of iris but 31 | // iris won't touch its handlers. 32 | app.NewHost(&http.Server{Addr: ":8088", Handler: mux}).ListenAndServe() 33 | } 34 | -------------------------------------------------------------------------------- /_example/middleware/go.mod: -------------------------------------------------------------------------------- 1 | module example/middleware 2 | 3 | go 1.19 4 | 5 | require github.com/arl/statsviz v0.6.0 6 | 7 | require github.com/gorilla/websocket v1.5.0 // indirect 8 | -------------------------------------------------------------------------------- /_example/middleware/go.sum: -------------------------------------------------------------------------------- 1 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 2 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 3 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 4 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 6 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 7 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 8 | -------------------------------------------------------------------------------- /_example/middleware/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/arl/statsviz" 9 | example "github.com/arl/statsviz/_example" 10 | ) 11 | 12 | // basicAuth adds HTTP Basic Authentication to h. 13 | // 14 | // NOTE: This is just an example middleware to show how one can wrap statsviz 15 | // handler, it should absolutely not be used as is. 16 | func basicAuth(h http.HandlerFunc, user, pwd, realm string) http.HandlerFunc { 17 | return func(w http.ResponseWriter, r *http.Request) { 18 | if u, p, ok := r.BasicAuth(); !ok || user != u || pwd != p { 19 | w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) 20 | w.WriteHeader(401) 21 | w.Write([]byte("Unauthorised.\n")) 22 | return 23 | } 24 | 25 | h(w, r) 26 | } 27 | } 28 | 29 | func main() { 30 | // Force the GC to work to make the plots "move". 31 | go example.Work() 32 | 33 | // Create statsviz server. 34 | srv, _ := statsviz.NewServer() 35 | 36 | mux := http.NewServeMux() 37 | mux.Handle("/debug/statsviz/", basicAuth(srv.Index(), "statsviz", "rocks", "")) 38 | mux.HandleFunc("/debug/statsviz/ws", srv.Ws()) 39 | 40 | fmt.Println("Point your browser to http://localhost:8090/debug/statsviz/") 41 | fmt.Println("Basic auth user: statsviz") 42 | fmt.Println("Basic auth password: rocks") 43 | log.Fatal(http.ListenAndServe(":8090", mux)) 44 | } 45 | -------------------------------------------------------------------------------- /_example/mux/go.mod: -------------------------------------------------------------------------------- 1 | module example/mux 2 | 3 | go 1.19 4 | 5 | require github.com/arl/statsviz v0.6.0 6 | 7 | require github.com/gorilla/websocket v1.5.0 // indirect 8 | -------------------------------------------------------------------------------- /_example/mux/go.sum: -------------------------------------------------------------------------------- 1 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 2 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 3 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 4 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 6 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 7 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 8 | -------------------------------------------------------------------------------- /_example/mux/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/arl/statsviz" 9 | example "github.com/arl/statsviz/_example" 10 | ) 11 | 12 | func main() { 13 | // Force the GC to work to make the plots "move". 14 | go example.Work() 15 | 16 | mux := http.NewServeMux() 17 | 18 | // Register Statsviz handlers on the mux. 19 | _ = statsviz.Register(mux) 20 | 21 | fmt.Println("Point your browser to http://localhost:8091/debug/statsviz/") 22 | log.Fatal(http.ListenAndServe(":8091", mux)) 23 | } 24 | -------------------------------------------------------------------------------- /_example/options/go.mod: -------------------------------------------------------------------------------- 1 | module example/options 2 | 3 | go 1.19 4 | 5 | require github.com/arl/statsviz v0.6.0 6 | 7 | require github.com/gorilla/websocket v1.5.0 // indirect 8 | -------------------------------------------------------------------------------- /_example/options/go.sum: -------------------------------------------------------------------------------- 1 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 2 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 3 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 4 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 6 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 7 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 8 | -------------------------------------------------------------------------------- /_example/options/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/arl/statsviz" 9 | example "github.com/arl/statsviz/_example" 10 | ) 11 | 12 | func main() { 13 | // Force the GC to work to make the plots "move". 14 | go example.Work() 15 | 16 | mux := http.NewServeMux() 17 | 18 | // Register Statsviz server on the mux, serving the user interface from 19 | // /foo/bar instead of /debug/statsviz and send metrics every 250 20 | // milliseconds instead of the default of once per second. 21 | _ = statsviz.Register(mux, 22 | statsviz.Root("/foo/bar"), 23 | statsviz.SendFrequency(250*time.Millisecond), 24 | ) 25 | 26 | log.Println("Point your browser to http://localhost:8092/foo/bar") 27 | log.Fatal(http.ListenAndServe(":8092", mux)) 28 | } 29 | -------------------------------------------------------------------------------- /_example/userplots/go.mod: -------------------------------------------------------------------------------- 1 | module example/default 2 | 3 | go 1.19 4 | 5 | require github.com/arl/statsviz v0.6.0 6 | 7 | require github.com/gorilla/websocket v1.5.0 // indirect 8 | -------------------------------------------------------------------------------- /_example/userplots/go.sum: -------------------------------------------------------------------------------- 1 | github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= 2 | github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= 3 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 4 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 6 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 7 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 8 | -------------------------------------------------------------------------------- /_example/userplots/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math" 7 | "math/rand" 8 | "net/http" 9 | 10 | "github.com/arl/statsviz" 11 | example "github.com/arl/statsviz/_example" 12 | ) 13 | 14 | func main() { 15 | go example.Work() 16 | 17 | mux := http.NewServeMux() 18 | 19 | // Register statsviz handlers and 3 addition user plots. 20 | if err := statsviz.Register(mux, 21 | statsviz.TimeseriesPlot(scatterPlot()), 22 | statsviz.TimeseriesPlot(barPlot()), 23 | statsviz.TimeseriesPlot(stackedPlot()), 24 | ); err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | fmt.Println("Point your browser to http://localhost:8093/debug/statsviz/") 29 | log.Fatal(http.ListenAndServe(":8093", mux)) 30 | } 31 | 32 | func scatterPlot() statsviz.TimeSeriesPlot { 33 | // Describe the 'sine' time series. 34 | sine := statsviz.TimeSeries{ 35 | Name: "short sin", 36 | Unitfmt: "%{y:.4s}B", 37 | GetValue: updateSine, 38 | } 39 | 40 | // Build a new plot, showing our sine time series 41 | plot, err := statsviz.TimeSeriesPlotConfig{ 42 | Name: "sine", 43 | Title: "Sine", 44 | Type: statsviz.Scatter, 45 | InfoText: `This is an example of a 'scatter' type plot, showing a single time series.
46 | InfoText field (this) accepts any HTML tags like bold, italic, etc.`, 47 | YAxisTitle: "y unit", 48 | Series: []statsviz.TimeSeries{sine}, 49 | }.Build() 50 | if err != nil { 51 | log.Fatalf("failed to build timeseries plot: %v", err) 52 | } 53 | 54 | return plot 55 | } 56 | 57 | func barPlot() statsviz.TimeSeriesPlot { 58 | // Describe the 'user logins' time series. 59 | logins := statsviz.TimeSeries{ 60 | Name: "user logins", 61 | Unitfmt: "%{y:.4s}", 62 | GetValue: logins, 63 | } 64 | 65 | // Describe the 'user signins' time series. 66 | signins := statsviz.TimeSeries{ 67 | Name: "user signins", 68 | Unitfmt: "%{y:.4s}", 69 | GetValue: signins, 70 | } 71 | 72 | // Build a new plot, showing both time series at once. 73 | plot, err := statsviz.TimeSeriesPlotConfig{ 74 | Name: "users", 75 | Title: "Users", 76 | Type: statsviz.Bar, 77 | InfoText: `This is an example of a 'bar' type plot, showing 2 time series.
78 | InfoText field (this) accepts any HTML tags like bold, italic, etc.`, 79 | YAxisTitle: "users", 80 | Series: []statsviz.TimeSeries{logins, signins}, 81 | }.Build() 82 | if err != nil { 83 | log.Fatalf("failed to build timeseries plot: %v", err) 84 | } 85 | 86 | return plot 87 | } 88 | 89 | func stackedPlot() statsviz.TimeSeriesPlot { 90 | // Describe the 'user logins' time series. 91 | logins := statsviz.TimeSeries{ 92 | Name: "user logins", 93 | Unitfmt: "%{y:.4s}", 94 | Type: statsviz.Bar, 95 | GetValue: logins, 96 | } 97 | 98 | // Describe the 'user signins' time series. 99 | signins := statsviz.TimeSeries{ 100 | Name: "user signins", 101 | Unitfmt: "%{y:.4s}", 102 | Type: statsviz.Bar, 103 | GetValue: signins, 104 | } 105 | 106 | // Build a new plot, showing both time series at once. 107 | plot, err := statsviz.TimeSeriesPlotConfig{ 108 | Name: "users-stack", 109 | Title: "Stacked Users", 110 | Type: statsviz.Bar, 111 | BarMode: statsviz.Stack, 112 | InfoText: `This is an example of a 'bar' plot showing 2 time series stacked on top of each other with BarMode:Stack.
113 | InfoText field (this) accepts any HTML tags like bold, italic, etc.`, 114 | YAxisTitle: "users", 115 | Series: []statsviz.TimeSeries{logins, signins}, 116 | }.Build() 117 | if err != nil { 118 | log.Fatalf("failed to build timeseries plot: %v", err) 119 | } 120 | 121 | return plot 122 | } 123 | 124 | var val = 0. 125 | 126 | func updateSine() float64 { 127 | val += 0.5 128 | return math.Sin(val) 129 | } 130 | 131 | func logins() float64 { 132 | return (rand.Float64() + 2) * 1000 133 | } 134 | 135 | func signins() float64 { 136 | return (rand.Float64() + 1.5) * 100 137 | } 138 | -------------------------------------------------------------------------------- /_example/work.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | "runtime" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | // Wwork loops forever, generating allocations of various sizes, in order to 12 | // create artificial work for a nice 'demo effect'. 13 | func Work() { 14 | m := make(map[int64]any) 15 | tick := time.NewTicker(30 * time.Millisecond) 16 | clearTick := time.NewTicker(1 * time.Second) 17 | for { 18 | select { 19 | case <-clearTick.C: 20 | m = make(map[int64]any) 21 | if rand.Intn(100) < 5 { 22 | runtime.GC() 23 | } 24 | case ts := <-tick.C: 25 | m[ts.UnixNano()] = newStruct() 26 | } 27 | } 28 | } 29 | 30 | // create a randomly sized struct (to create 'motion' on size classes plot). 31 | func newStruct() any { 32 | nfields := rand.Intn(32) 33 | var fields []reflect.StructField 34 | for i := 0; i < nfields; i++ { 35 | fields = append(fields, reflect.StructField{ 36 | Name: "f" + strconv.Itoa(i), 37 | PkgPath: "main", 38 | Type: reflect.TypeOf(""), 39 | }) 40 | } 41 | return reflect.New(reflect.StructOf(fields)).Interface() 42 | } 43 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | round: down 6 | precision: 2 7 | 8 | status: 9 | project: 10 | default: 11 | informational: true 12 | patch: 13 | default: 14 | informational: true 15 | -------------------------------------------------------------------------------- /examples_test.go: -------------------------------------------------------------------------------- 1 | //go:build unix 2 | 3 | package statsviz_test 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "crypto/tls" 9 | "io" 10 | "net/http" 11 | "os" 12 | "testing" 13 | "time" 14 | 15 | "github.com/rogpeppe/go-internal/gotooltest" 16 | "github.com/rogpeppe/go-internal/testscript" 17 | ) 18 | 19 | func TestExamples(t *testing.T) { 20 | if testing.Short() { 21 | t.Skipf("TestExamples skipped in short mode") 22 | } 23 | 24 | p := testscript.Params{ 25 | Dir: "testdata", 26 | Setup: func(env *testscript.Env) error { 27 | // We want to run scripts with the local version of Statsviz. 28 | // Provide scripts with statsviz root dir so we can use a 29 | // 'go mod -edit replace' directive. 30 | wd, err := os.Getwd() 31 | if err != nil { 32 | return err 33 | } 34 | env.Setenv("STATSVIZ_ROOT", wd) 35 | return nil 36 | }, 37 | Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ 38 | "checkui": checkui, 39 | }, 40 | } 41 | 42 | if err := gotooltest.Setup(&p); err != nil { 43 | t.Fatal(err) 44 | } 45 | testscript.Run(t, p) 46 | } 47 | 48 | // checkui requests statsviz url from a script. 49 | // In a script, run it with: 50 | // 51 | // checkui url [basic_auth_user basic_auth_pwd] 52 | func checkui(ts *testscript.TestScript, neg bool, args []string) { 53 | if len(args) != 1 && len(args) != 3 { 54 | ts.Fatalf(`checkui: wrong number of arguments. Call with "checkui URL [BASIC_USER BASIC_PWD]`) 55 | } 56 | u := args[0] 57 | ts.Logf("checkui: loading web page %s", args[0]) 58 | 59 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 60 | defer cancel() 61 | 62 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) 63 | if err != nil { 64 | ts.Fatalf("checkui: bad request: %v", err) 65 | } 66 | if len(args) == 3 { 67 | ts.Logf("checkui: setting basic auth") 68 | req.SetBasicAuth(args[1], args[2]) 69 | } 70 | 71 | client := http.Client{ 72 | Transport: &http.Transport{ 73 | TLSClientConfig: &tls.Config{ 74 | InsecureSkipVerify: true, 75 | }, 76 | }, 77 | Timeout: 5 * time.Second, 78 | } 79 | 80 | // Let 1 second for the server to start and listen. 81 | time.Sleep(1 * time.Second) 82 | 83 | resp, err := client.Do(req) 84 | ts.Check(err) 85 | 86 | body, err := io.ReadAll(resp.Body) 87 | defer resp.Body.Close() 88 | ts.Check(err) 89 | 90 | want := []byte(`id="plots"`) 91 | if !bytes.Contains(body, want) { 92 | ts.Fatalf("checkui: response body doesn't contain %s\n\nbody;\n\n%s", want, body) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/arl/statsviz 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/gorilla/websocket v1.5.0 7 | github.com/rogpeppe/go-internal v1.11.0 8 | ) 9 | 10 | require ( 11 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 12 | golang.org/x/tools v0.1.12 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 2 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 3 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 4 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 5 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 6 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 7 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 8 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 9 | -------------------------------------------------------------------------------- /internal/plot/color.go: -------------------------------------------------------------------------------- 1 | package plot 2 | 3 | import ( 4 | "fmt" 5 | "image/color" 6 | ) 7 | 8 | func RGBString(r, g, b uint8) string { 9 | return fmt.Sprintf(`"rgb(%d,%d,%d,0)"`, r, g, b) 10 | } 11 | 12 | type WeightedColor struct { 13 | Value float64 14 | Color color.RGBA 15 | } 16 | 17 | func (c WeightedColor) MarshalJSON() ([]byte, error) { 18 | str := fmt.Sprintf(`[%f,"rgb(%d,%d,%d,%f)"]`, 19 | c.Value, c.Color.R, c.Color.G, c.Color.B, float64(c.Color.A)/255) 20 | return []byte(str), nil 21 | } 22 | 23 | // https://mdigi.tools/color-shades/ 24 | var BlueShades = []WeightedColor{ 25 | {Value: 0.0, Color: color.RGBA{0xea, 0xf8, 0xfd, 1}}, 26 | {Value: 0.1, Color: color.RGBA{0xbf, 0xeb, 0xfa, 1}}, 27 | {Value: 0.2, Color: color.RGBA{0x94, 0xdd, 0xf6, 1}}, 28 | {Value: 0.3, Color: color.RGBA{0x69, 0xd0, 0xf2, 1}}, 29 | {Value: 0.4, Color: color.RGBA{0x3f, 0xc2, 0xef, 1}}, 30 | {Value: 0.5, Color: color.RGBA{0x14, 0xb5, 0xeb, 1}}, 31 | {Value: 0.6, Color: color.RGBA{0x10, 0x94, 0xc0, 1}}, 32 | {Value: 0.7, Color: color.RGBA{0x0d, 0x73, 0x96, 1}}, 33 | {Value: 0.8, Color: color.RGBA{0x09, 0x52, 0x6b, 1}}, 34 | {Value: 0.9, Color: color.RGBA{0x05, 0x31, 0x40, 1}}, 35 | {Value: 1.0, Color: color.RGBA{0x02, 0x10, 0x15, 1}}, 36 | } 37 | 38 | var PinkShades = []WeightedColor{ 39 | {Value: 0.0, Color: color.RGBA{0xfe, 0xe7, 0xf3, 1}}, 40 | {Value: 0.1, Color: color.RGBA{0xfc, 0xb6, 0xdc, 1}}, 41 | {Value: 0.2, Color: color.RGBA{0xf9, 0x85, 0xc5, 1}}, 42 | {Value: 0.3, Color: color.RGBA{0xf7, 0x55, 0xae, 1}}, 43 | {Value: 0.4, Color: color.RGBA{0xf5, 0x24, 0x96, 1}}, 44 | {Value: 0.5, Color: color.RGBA{0xdb, 0x0a, 0x7d, 1}}, 45 | {Value: 0.6, Color: color.RGBA{0xaa, 0x08, 0x61, 1}}, 46 | {Value: 0.7, Color: color.RGBA{0x7a, 0x06, 0x45, 1}}, 47 | {Value: 0.8, Color: color.RGBA{0x49, 0x03, 0x2a, 1}}, 48 | {Value: 0.9, Color: color.RGBA{0x18, 0x01, 0x0e, 1}}, 49 | {Value: 1.0, Color: color.RGBA{0x00, 0x00, 0x00, 1}}, 50 | } 51 | 52 | var GreenShades = []WeightedColor{ 53 | {Value: 0.0, Color: color.RGBA{0xed, 0xf7, 0xf2, 0}}, 54 | {Value: 0.1, Color: color.RGBA{0xc9, 0xe8, 0xd7, 0}}, 55 | {Value: 0.2, Color: color.RGBA{0xa5, 0xd9, 0xbc, 0}}, 56 | {Value: 0.3, Color: color.RGBA{0x81, 0xca, 0xa2, 0}}, 57 | {Value: 0.4, Color: color.RGBA{0x5e, 0xbb, 0x87, 0}}, 58 | {Value: 0.5, Color: color.RGBA{0x44, 0xa1, 0x6e, 0}}, 59 | {Value: 0.6, Color: color.RGBA{0x35, 0x7e, 0x55, 0}}, 60 | {Value: 0.7, Color: color.RGBA{0x26, 0x5a, 0x3d, 0}}, 61 | {Value: 0.8, Color: color.RGBA{0x17, 0x36, 0x25, 0}}, 62 | {Value: 0.9, Color: color.RGBA{0x08, 0x12, 0x0c, 0}}, 63 | {Value: 1.0, Color: color.RGBA{0x00, 0x00, 0x00, 0}}, 64 | } 65 | -------------------------------------------------------------------------------- /internal/plot/hist.go: -------------------------------------------------------------------------------- 1 | package plot 2 | 3 | import ( 4 | "math" 5 | "runtime/metrics" 6 | ) 7 | 8 | // maxBuckets is the maximum number of buckets we'll plots in heatmaps. 9 | // Histograms with more buckets than that are going to be downsampled. 10 | const maxBuckets = 100 11 | 12 | // downsampleFactor computes the downsampling factor to use with 13 | // downsampleCounts and downsampleBuckets. nstart and nfinal are the number of 14 | // buckets in the original and final bucket. 15 | func downsampleFactor(norg, nfinal int) int { 16 | mod := norg % nfinal 17 | if mod == 0 { 18 | return norg / nfinal 19 | } 20 | return 1 + norg/nfinal 21 | } 22 | 23 | // downsampleBuckets downsamples the number of buckets in the provided 24 | // histogram, using the given dividing factor, and returns a slice of bucket 25 | // widths. 26 | // 27 | // Given that metrics.Float64Histogram contains the boundaries of histogram 28 | // buckets, the first bucket is not even considered since we're only interested 29 | // in upper bounds. Also, since we can't draw an infinitely large bucket, if h 30 | // last bucket holds +Inf, the width of the last returned bucket will 31 | // extrapolated from the previous 2 buckets. 32 | func downsampleBuckets(h *metrics.Float64Histogram, factor int) []float64 { 33 | var ret []float64 34 | vals := h.Buckets[1:] 35 | 36 | for i := 0; i < len(vals); i++ { 37 | if (i+1)%factor == 0 { 38 | ret = append(ret, vals[i]) 39 | } 40 | } 41 | if len(vals)%factor != 0 { 42 | // If the number of bucket is not divisible by the factor, let's make a 43 | // last downsampled bucket, even if it doesn't 'contain' the same number 44 | // of original buckets. 45 | ret = append(ret, vals[len(vals)-1]) 46 | } 47 | 48 | if len(ret) > 2 && math.IsInf(ret[len(ret)-1], 1) { 49 | // Plotly doesn't support a +Inf bound for the last bucket. So we make it 50 | // so that the last bucket has the same 'width' than the penultimate one. 51 | ret[len(ret)-1] = ret[len(ret)-2] - ret[len(ret)-3] + ret[len(ret)-2] 52 | } 53 | 54 | return ret 55 | } 56 | 57 | // downsampleCounts downsamples the counts in the provided histogram, using the 58 | // given factor. Every 'factor' buckets are merged into one, larger, bucket. If 59 | // the number of buckets is not divisible by 'factor', then an additional last 60 | // bucket will contain the sum of the counts in all relainbing buckets. 61 | // 62 | // Note: slice should be a slice of maxBuckets elements, so that it can be 63 | // reused across calls. 64 | func downsampleCounts(h *metrics.Float64Histogram, factor int, slice []uint64) []uint64 { 65 | vals := h.Counts 66 | 67 | if factor == 1 { 68 | copy(slice, vals) 69 | slice = slice[:len(vals)] 70 | return slice 71 | } 72 | 73 | slice = slice[:0] 74 | 75 | var sum uint64 76 | for i := 0; i < len(vals); i++ { 77 | if i%factor == 0 && i > 1 { 78 | slice = append(slice, sum) 79 | sum = vals[i] 80 | } else { 81 | sum += vals[i] 82 | } 83 | } 84 | 85 | // Whatever sum remains, it goes to the last bucket. 86 | return append(slice, sum) 87 | } 88 | -------------------------------------------------------------------------------- /internal/plot/hist_test.go: -------------------------------------------------------------------------------- 1 | package plot 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | "runtime/metrics" 8 | "testing" 9 | ) 10 | 11 | func Test_downsampleFactor(t *testing.T) { 12 | tests := []struct { 13 | nbuckets int 14 | maxbuckets int 15 | want int 16 | }{ 17 | {nbuckets: 99, maxbuckets: 100, want: 1}, 18 | {nbuckets: 100, maxbuckets: 100, want: 1}, 19 | {nbuckets: 101, maxbuckets: 100, want: 2}, 20 | {nbuckets: 10, maxbuckets: 5, want: 2}, 21 | {nbuckets: 11, maxbuckets: 5, want: 3}, 22 | } 23 | for _, tt := range tests { 24 | t.Run(fmt.Sprintf("n=%d,max=%d", tt.nbuckets, tt.maxbuckets), func(t *testing.T) { 25 | if got := downsampleFactor(tt.nbuckets, tt.maxbuckets); got != tt.want { 26 | t.Errorf("downsampleFactor() = %v, want %v", got, tt.want) 27 | } 28 | }) 29 | } 30 | } 31 | 32 | func Test_downsample(t *testing.T) { 33 | tests := []struct { 34 | name string 35 | hist metrics.Float64Histogram 36 | factor int 37 | wantBuckets []float64 38 | wantCounts []uint64 39 | }{ 40 | { 41 | name: "factor 1", 42 | hist: metrics.Float64Histogram{ 43 | Buckets: []float64{0, 1, 2, 3, 4, 5, 6}, 44 | Counts: []uint64{2, 2, 1, 4, 5, 6}, 45 | }, 46 | factor: 1, 47 | wantBuckets: []float64{1, 2, 3, 4, 5, 6}, 48 | wantCounts: []uint64{2, 2, 1, 4, 5, 6}, 49 | }, 50 | { 51 | name: "factor 1 with infinites", 52 | hist: metrics.Float64Histogram{ 53 | Buckets: []float64{math.Inf(-1), 1, 2, 3, 4, 5, math.Inf(1)}, 54 | Counts: []uint64{2, 2, 1, 4, 5, 6}, 55 | }, 56 | factor: 1, 57 | wantBuckets: []float64{1, 2, 3, 4, 5, 6}, 58 | wantCounts: []uint64{2, 2, 1, 4, 5, 6}, 59 | }, 60 | { 61 | name: "divisible by factor 3", 62 | hist: metrics.Float64Histogram{ 63 | Buckets: []float64{0, 1, 2, 3, 4, 5, 6}, 64 | Counts: []uint64{2, 2, 1, 4, 5, 6}, 65 | }, 66 | factor: 3, 67 | wantBuckets: []float64{3, 6}, 68 | wantCounts: []uint64{5, 15}, 69 | }, 70 | { 71 | name: "divisible by factor 2", 72 | hist: metrics.Float64Histogram{ 73 | Buckets: []float64{0, 1, 2, 3, 4, 5, 6}, 74 | Counts: []uint64{2, 2, 1, 4, 5, 6}, 75 | }, 76 | factor: 2, 77 | wantBuckets: []float64{2, 4, 6}, 78 | wantCounts: []uint64{4, 5, 11}, 79 | }, 80 | { 81 | name: "not divisible by factor 2", 82 | hist: metrics.Float64Histogram{ 83 | Buckets: []float64{0, 1, 2, 3, 4, 5}, 84 | Counts: []uint64{2, 2, 1, 4, 5}, 85 | }, 86 | factor: 2, 87 | wantBuckets: []float64{2, 4, 5}, 88 | wantCounts: []uint64{4, 5, 5}, 89 | }, 90 | { 91 | name: "not divisible by factor 3, end +Inf", 92 | hist: metrics.Float64Histogram{ 93 | Buckets: []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, math.Inf(1)}, 94 | Counts: []uint64{2, 2, 1, 4, 5, 3, 2, 1, 7, 13}, 95 | }, 96 | factor: 3, 97 | wantBuckets: []float64{3, 6, 9, 12}, 98 | wantCounts: []uint64{5, 12, 10, 13}, 99 | }, 100 | { 101 | name: "not divisible by factor 3, start/end +Inf", 102 | hist: metrics.Float64Histogram{ 103 | Buckets: []float64{math.Inf(-1), 1, 2, 3, 4, 5, 6, 7, 8, 9, math.Inf(1)}, 104 | Counts: []uint64{2, 2, 1, 4, 5, 3, 2, 1, 7, 13}, 105 | }, 106 | factor: 3, 107 | wantBuckets: []float64{3, 6, 9, 12}, 108 | wantCounts: []uint64{5, 12, 10, 13}, 109 | }, 110 | { 111 | name: "divisible by factor 3, end +Inf", 112 | hist: metrics.Float64Histogram{ 113 | Buckets: []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, math.Inf(1)}, 114 | Counts: []uint64{2, 2, 1, 4, 5, 3, 2, 1, 7}, 115 | }, 116 | factor: 3, 117 | wantBuckets: []float64{3, 6, 9}, 118 | wantCounts: []uint64{5, 12, 10}, 119 | }, 120 | } 121 | 122 | for _, tt := range tests { 123 | t.Run(tt.name, func(t *testing.T) { 124 | buckets := downsampleBuckets(&tt.hist, tt.factor) 125 | counts := make([]uint64, maxBuckets) 126 | counts = downsampleCounts(&tt.hist, tt.factor, counts) 127 | 128 | if !reflect.DeepEqual(buckets, tt.wantBuckets) { 129 | t.Errorf("downsampleBuckets() = %v, want %v", buckets, tt.wantBuckets) 130 | } 131 | if !reflect.DeepEqual(counts, tt.wantCounts) { 132 | t.Errorf("downsampleCounts() = %v, want %v", counts, tt.wantCounts) 133 | } 134 | }) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /internal/plot/layout.go: -------------------------------------------------------------------------------- 1 | package plot 2 | 3 | type ( 4 | Config struct { 5 | // Series contains the plots we want to show and how we want to show them. 6 | Series []any `json:"series"` 7 | // Events contains a list of 'events time series' names. Series with 8 | // these names must be sent alongside other series. An event time series 9 | // is just made of timestamps with no associated value, each of which 10 | // gets plotted as a vertical line over another plot. 11 | Events []string `json:"events"` 12 | } 13 | 14 | Scatter struct { 15 | Name string `json:"name"` 16 | Title string `json:"title"` 17 | Type string `json:"type"` 18 | UpdateFreq int `json:"updateFreq"` 19 | InfoText string `json:"infoText"` 20 | Events string `json:"events"` 21 | Layout ScatterLayout `json:"layout"` 22 | Subplots []Subplot `json:"subplots"` 23 | } 24 | ScatterLayout struct { 25 | Yaxis ScatterYAxis `json:"yaxis"` 26 | BarMode string `json:"barmode"` 27 | } 28 | ScatterYAxis struct { 29 | Title string `json:"title"` 30 | TickSuffix string `json:"ticksuffix"` 31 | } 32 | 33 | Subplot struct { 34 | Name string `json:"name"` 35 | Unitfmt string `json:"unitfmt"` 36 | StackGroup string `json:"stackgroup"` 37 | HoverOn string `json:"hoveron"` 38 | Color string `json:"color"` 39 | Type string `json:"type"` 40 | } 41 | 42 | Heatmap struct { 43 | Name string `json:"name"` 44 | Title string `json:"title"` 45 | Type string `json:"type"` 46 | UpdateFreq int `json:"updateFreq"` 47 | InfoText string `json:"infoText"` 48 | Events string `json:"events"` 49 | Layout HeatmapLayout `json:"layout"` 50 | Colorscale []WeightedColor `json:"colorscale"` 51 | Buckets []float64 `json:"buckets"` 52 | CustomData []float64 `json:"custom_data"` 53 | Hover HeapmapHover `json:"hover"` 54 | } 55 | HeatmapLayout struct { 56 | YAxis HeatmapYaxis `json:"yaxis"` 57 | } 58 | HeatmapYaxis struct { 59 | Title string `json:"title"` 60 | TickMode string `json:"tickmode"` 61 | TickVals []float64 `json:"tickvals"` 62 | TickText []float64 `json:"ticktext"` 63 | } 64 | HeapmapHover struct { 65 | YName string `json:"yname"` 66 | YUnit string `json:"yunit"` // 'duration', 'bytes' or custom 67 | ZName string `json:"zname"` 68 | } 69 | ) 70 | -------------------------------------------------------------------------------- /internal/plot/plots_list.go: -------------------------------------------------------------------------------- 1 | package plot 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "runtime/debug" 8 | "runtime/metrics" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var ( 14 | names map[string]bool 15 | usedMetrics map[string]struct{} 16 | ) 17 | 18 | type plotFunc func(idxs map[string]int) runtimeMetric 19 | 20 | var plotFuncs []plotFunc 21 | 22 | func registerPlotFunc(f plotFunc) { 23 | plotFuncs = append(plotFuncs, f) 24 | } 25 | 26 | func registerRuntimePlot(name string, metrics ...string) bool { 27 | if names == nil { 28 | names = make(map[string]bool) 29 | usedMetrics = make(map[string]struct{}) 30 | } 31 | if names[name] { 32 | panic(name + " is an already reserved plot name") 33 | } 34 | names[name] = true 35 | 36 | // Record the metrics we use. 37 | for _, m := range metrics { 38 | usedMetrics[m] = struct{}{} 39 | } 40 | 41 | return true 42 | } 43 | 44 | func IsReservedPlotName(name string) bool { 45 | return names[name] 46 | } 47 | 48 | type runtimeMetric interface { 49 | name() string 50 | isEnabled() bool 51 | layout([]metrics.Sample) any 52 | values([]metrics.Sample) any 53 | } 54 | 55 | // List holds all the plots that statsviz knows about. Some plots might be 56 | // disabled, if they rely on metrics that are unknown to the current Go version. 57 | type List struct { 58 | rtPlots []runtimeMetric 59 | userPlots []UserPlot 60 | 61 | once sync.Once // ensure Config is called once 62 | cfg *Config 63 | 64 | idxs map[string]int // map metrics name to idx in samples and descs 65 | descs []metrics.Description 66 | 67 | mu sync.Mutex // protects samples in case of concurrent calls to WriteValues 68 | samples []metrics.Sample 69 | } 70 | 71 | func NewList(userPlots []UserPlot) (*List, error) { 72 | if name := hasDuplicatePlotNames(userPlots); name != "" { 73 | return nil, fmt.Errorf("duplicate plot name %s", name) 74 | } 75 | 76 | descs := metrics.All() 77 | pl := &List{ 78 | idxs: make(map[string]int), 79 | descs: descs, 80 | samples: make([]metrics.Sample, len(descs)), 81 | userPlots: userPlots, 82 | } 83 | for i := range pl.samples { 84 | pl.samples[i].Name = pl.descs[i].Name 85 | pl.idxs[pl.samples[i].Name] = i 86 | } 87 | metrics.Read(pl.samples) 88 | 89 | return pl, nil 90 | } 91 | 92 | func (pl *List) Config() *Config { 93 | pl.once.Do(func() { 94 | pl.rtPlots = make([]runtimeMetric, 0, len(plotFuncs)) 95 | for _, f := range plotFuncs { 96 | pl.rtPlots = append(pl.rtPlots, f(pl.idxs)) 97 | } 98 | 99 | layouts := make([]any, 0, len(pl.rtPlots)) 100 | for i := range pl.rtPlots { 101 | if pl.rtPlots[i].isEnabled() { 102 | layouts = append(layouts, pl.rtPlots[i].layout(pl.samples)) 103 | } 104 | } 105 | 106 | pl.cfg = &Config{ 107 | Events: []string{"lastgc"}, 108 | Series: layouts, 109 | } 110 | 111 | // User plots go at the back of the list for now. 112 | for i := range pl.userPlots { 113 | pl.cfg.Series = append(pl.cfg.Series, pl.userPlots[i].Layout()) 114 | } 115 | }) 116 | return pl.cfg 117 | } 118 | 119 | // WriteValues writes into w a JSON object containing the data points for all 120 | // plots at the current instant. 121 | func (pl *List) WriteValues(w io.Writer) error { 122 | pl.mu.Lock() 123 | defer pl.mu.Unlock() 124 | 125 | metrics.Read(pl.samples) 126 | 127 | // lastgc time series is used as source to represent garbage collection 128 | // timestamps as vertical bars on certain plots. 129 | gcStats := debug.GCStats{} 130 | debug.ReadGCStats(&gcStats) 131 | 132 | m := map[string]any{ 133 | // javascript timestampts are in milliseconds 134 | "lastgc": []int64{gcStats.LastGC.UnixMilli()}, 135 | "timestamp": time.Now().UnixMilli(), 136 | } 137 | 138 | for _, p := range pl.rtPlots { 139 | if p.isEnabled() { 140 | m[p.name()] = p.values(pl.samples) 141 | } 142 | } 143 | 144 | for i := range pl.userPlots { 145 | up := &pl.userPlots[i] 146 | switch { 147 | case up.Scatter != nil: 148 | vals := make([]float64, len(up.Scatter.Funcs)) 149 | for i := range up.Scatter.Funcs { 150 | vals[i] = up.Scatter.Funcs[i]() 151 | } 152 | m[up.Scatter.Plot.Name] = vals 153 | case up.Heatmap != nil: 154 | panic("unimplemented") 155 | } 156 | } 157 | 158 | if err := json.NewEncoder(w).Encode(m); err != nil { 159 | return fmt.Errorf("failed to write/convert metrics values to json: %v", err) 160 | } 161 | return nil 162 | } 163 | -------------------------------------------------------------------------------- /internal/plot/plots_test.go: -------------------------------------------------------------------------------- 1 | package plot 2 | 3 | import ( 4 | "fmt" 5 | "runtime/metrics" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestUnusedRuntimeMetrics(t *testing.T) { 11 | // This "test" can't fail. It just prints which of the metrics exported by 12 | // /runtime/metrics are not used in any Statsviz plot. 13 | for _, m := range metrics.All() { 14 | if _, ok := usedMetrics[m.Name]; !ok && !strings.HasPrefix(m.Name, "/godebug/") { 15 | fmt.Printf("runtime/metric %q is not used\n", m.Name) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internal/plot/user.go: -------------------------------------------------------------------------------- 1 | package plot 2 | 3 | type ScatterUserPlot struct { 4 | Plot Scatter 5 | Funcs []func() float64 6 | } 7 | 8 | type HeatmapUserPlot struct { 9 | Plot Heatmap 10 | // TODO(arl): heatmap get value func 11 | } 12 | 13 | type UserPlot struct { 14 | Scatter *ScatterUserPlot 15 | Heatmap *HeatmapUserPlot 16 | } 17 | 18 | func (up UserPlot) Layout() any { 19 | switch { 20 | case (up.Scatter != nil) == (up.Heatmap != nil): 21 | panic("userplot must be a timeseries or a heatmap") 22 | case up.Scatter != nil: 23 | return up.Scatter.Plot 24 | case up.Heatmap != nil: 25 | return up.Heatmap.Plot 26 | } 27 | 28 | panic("unreachable") 29 | } 30 | 31 | func hasDuplicatePlotNames(userPlots []UserPlot) string { 32 | names := map[string]bool{} 33 | for _, p := range userPlots { 34 | name := "" 35 | if p.Scatter != nil { 36 | name = p.Scatter.Plot.Name 37 | } else if p.Heatmap != nil { 38 | name = p.Heatmap.Plot.Name 39 | } else { 40 | panic("both heapmap and scatter are nil") 41 | } 42 | if names[name] { 43 | return name 44 | } 45 | names[name] = true 46 | } 47 | return "" 48 | } 49 | -------------------------------------------------------------------------------- /internal/plot/user_test.go: -------------------------------------------------------------------------------- 1 | package plot 2 | 3 | import "testing" 4 | 5 | func Test_hasDuplicatePlotNames(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | plots []UserPlot 9 | want string 10 | }{ 11 | { 12 | "nil", 13 | nil, 14 | "", 15 | }, 16 | { 17 | "empty", 18 | []UserPlot{}, 19 | "", 20 | }, 21 | { 22 | "single scatter", 23 | []UserPlot{ 24 | {Scatter: &ScatterUserPlot{Plot: Scatter{Name: "a"}}}, 25 | }, 26 | "", 27 | }, 28 | { 29 | "single heatmap", 30 | []UserPlot{ 31 | {Heatmap: &HeatmapUserPlot{Plot: Heatmap{Name: "a"}}}, 32 | }, 33 | "", 34 | }, 35 | { 36 | "two scatter", 37 | []UserPlot{ 38 | {Scatter: &ScatterUserPlot{Plot: Scatter{Name: "a"}}}, 39 | {Heatmap: &HeatmapUserPlot{Plot: Heatmap{Name: "b"}}}, 40 | {Scatter: &ScatterUserPlot{Plot: Scatter{Name: "a"}}}, 41 | }, 42 | "a", 43 | }, 44 | { 45 | "two heatmap", 46 | []UserPlot{ 47 | {Heatmap: &HeatmapUserPlot{Plot: Heatmap{Name: "a"}}}, 48 | {Scatter: &ScatterUserPlot{Plot: Scatter{Name: "b"}}}, 49 | {Heatmap: &HeatmapUserPlot{Plot: Heatmap{Name: "a"}}}, 50 | }, 51 | "a", 52 | }, 53 | } 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | if got := hasDuplicatePlotNames(tt.plots); got != tt.want { 57 | t.Errorf("hasDuplicatePlotNames() = %q, want %q", got, tt.want) 58 | } 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /internal/static/css/statsviz.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | margin: 0; 5 | } 6 | 7 | nav.navbar { 8 | background-color: #f8f9fa; 9 | } 10 | 11 | body.dark-theme { 12 | color: white; 13 | background-color: #282a2c; 14 | } 15 | 16 | body.dark-theme nav.navbar span.action svg { 17 | color: lightgray; 18 | } 19 | 20 | #header { 21 | text-align: center; 22 | } 23 | 24 | .container-xxl { 25 | width: 2190px; 26 | } 27 | 28 | .tooltip-table { 29 | display: table; 30 | width: 160px; 31 | border-collapse: collapse; 32 | } 33 | 34 | .tooltip-style { 35 | font-family: "Open Sans", verdana, arial, sans-serif; 36 | font-size: 12px; 37 | fill: rgb(68, 68, 68); 38 | fill-opacity: 1; 39 | white-space: pre; 40 | } 41 | 42 | .tooltip-row { 43 | display: table-row; 44 | } 45 | 46 | .tooltip-value, 47 | .tooltip-label { 48 | display: table-cell; 49 | padding: 0.3em; 50 | /* border: #f0f0f0 1px solid; */ 51 | } 52 | 53 | .tooltip-label { 54 | font-weight: bold; 55 | } 56 | 57 | .plots-wrapper { 58 | display: grid; 59 | height: 100%; 60 | /* column must be same as plotWidth (plot.js) */ 61 | grid-template-columns: repeat(auto-fill, minmax(0px, 630px)); 62 | } -------------------------------------------------------------------------------- /internal/static/fs.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import "embed" 4 | 5 | //go:embed * 6 | var Assets embed.FS 7 | -------------------------------------------------------------------------------- /internal/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Statsviz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 56 | 57 |
58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /internal/static/js/app.js: -------------------------------------------------------------------------------- 1 | import * as stats from './stats.js'; 2 | import * as plot from "./plot.js"; 3 | import * as theme from "./theme.js"; 4 | import PlotsDef from './plotsdef.js'; 5 | 6 | const buildWebsocketURI = () => { 7 | var loc = window.location, 8 | ws_prot = "ws:"; 9 | if (loc.protocol === "https:") { 10 | ws_prot = "wss:"; 11 | } 12 | return ws_prot + "//" + loc.host + loc.pathname + "ws" 13 | } 14 | 15 | const dataRetentionSeconds = 600; 16 | var timeout = 250; 17 | 18 | const clamp = (val, min, max) => { 19 | if (val < min) return min; 20 | if (val > max) return max; 21 | return val; 22 | } 23 | 24 | /* nav bar ui management */ 25 | let paused = false; 26 | let show_gc = true; 27 | let timerange = 60; 28 | 29 | /* WebSocket connection handling */ 30 | const connect = () => { 31 | const uri = buildWebsocketURI(); 32 | let ws = new WebSocket(uri); 33 | console.info(`Attempting websocket connection to server at ${uri}`); 34 | 35 | ws.onopen = () => { 36 | console.info("Successfully connected"); 37 | timeout = 250; // reset connection timeout for next time 38 | }; 39 | 40 | ws.onclose = event => { 41 | console.error(`Closed websocket connection: code ${event.code}`); 42 | setTimeout(connect, clamp(timeout += timeout, 250, 5000)); 43 | }; 44 | 45 | ws.onerror = err => { 46 | console.error(`Websocket error, closing connection.`); 47 | ws.close(); 48 | }; 49 | 50 | let initDone = false; 51 | ws.onmessage = event => { 52 | let data = JSON.parse(event.data) 53 | 54 | if (!initDone) { 55 | configurePlots(PlotsDef); 56 | stats.init(PlotsDef, dataRetentionSeconds); 57 | 58 | attachPlots(); 59 | 60 | $('#play_pause').change(() => { paused = !paused; }); 61 | $('#show_gc').change(() => { 62 | show_gc = !show_gc; 63 | updatePlots(); 64 | }); 65 | $('#select_timerange').click(() => { 66 | const val = parseInt($("#select_timerange option:selected").val(), 10); 67 | timerange = val; 68 | updatePlots(); 69 | }); 70 | initDone = true; 71 | return; 72 | } 73 | 74 | stats.pushData(data); 75 | if (paused) { 76 | return 77 | } 78 | updatePlots(PlotsDef.events); 79 | } 80 | } 81 | 82 | connect(); 83 | 84 | let plots = []; 85 | 86 | const configurePlots = (plotdefs) => { 87 | plots = []; 88 | plotdefs.series.forEach(plotdef => { 89 | plots.push(new plot.Plot(plotdef)); 90 | }); 91 | } 92 | 93 | const attachPlots = () => { 94 | let plotsDiv = $('#plots'); 95 | plotsDiv.empty(); 96 | 97 | for (let i = 0; i < plots.length; i++) { 98 | const plot = plots[i]; 99 | let div = $(`
`); 100 | plot.createElement(div[0], i) 101 | plotsDiv.append(div); 102 | } 103 | } 104 | 105 | const updatePlots = () => { 106 | // Create shapes. 107 | let shapes = new Map(); 108 | 109 | let data = stats.slice(timerange); 110 | 111 | if (show_gc) { 112 | for (const [name, serie] of data.events) { 113 | shapes.set(name, plot.createVerticalLines(serie)); 114 | } 115 | } 116 | 117 | // Always show the full range on x axis. 118 | const now = data.times[data.times.length - 1]; 119 | let xrange = [now - timerange * 1000, now]; 120 | 121 | plots.forEach(plot => { 122 | if (!plot.hidden) { 123 | plot.update(xrange, data, shapes); 124 | } 125 | }); 126 | } 127 | 128 | const updatePlotsLayout = () => { 129 | plots.forEach(plot => { 130 | plot.updateTheme(); 131 | }); 132 | } 133 | 134 | theme.updateThemeMode(); 135 | 136 | /** 137 | * Change color theme when the user presses the theme switch button 138 | */ 139 | $('#color_theme_sw').change(() => { 140 | const themeMode = theme.getThemeMode(); 141 | const newTheme = themeMode === "dark" && "light" || "dark"; 142 | localStorage.setItem("theme-mode", newTheme); 143 | theme.updateThemeMode(); 144 | updatePlotsLayout(); 145 | }); 146 | -------------------------------------------------------------------------------- /internal/static/js/buffer.js: -------------------------------------------------------------------------------- 1 | // Buffer declares the Buffer class. 2 | export default class Buffer { 3 | constructor(len, cap) { 4 | if (cap - len < 0) { 5 | throw "cap - len must be positive"; 6 | } 7 | // TODO(arl): value using TypedArray rather than Array here 8 | this._buf = new Array(cap); 9 | this._pos = 0; 10 | this._len = len; 11 | this._cap = cap; 12 | } 13 | last() { 14 | if (this.length() == 0) { 15 | throw 'Cannot call last() on an empty Buffer'; 16 | } 17 | return this._buf[this._pos]; 18 | } 19 | push(pt) { 20 | if (this._pos >= this._cap) { 21 | // move data to the beginning of the buffer, effectively discarding 22 | // the cap-len oldest elements 23 | this._buf.copyWithin(0, this._cap - this._len); 24 | this._pos = this._len; 25 | } 26 | this._buf[this._pos] = pt; 27 | this._pos++; 28 | } 29 | length() { 30 | if (this._pos > this._len) { 31 | return this._len; 32 | } 33 | return this._pos; 34 | } 35 | 36 | // slice returns a slice of the len latest datapoints present in the buffer. 37 | slice(len) { 38 | // Cap the dimension of the returned slice to the data available 39 | if (len > this.length()) { 40 | len = this.length(); 41 | } 42 | 43 | let start = this._pos - len; 44 | return this._buf.slice(start, start + len); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /internal/static/js/plot.js: -------------------------------------------------------------------------------- 1 | import * as theme from "./theme.js"; 2 | 3 | var infoIcon = { 4 | 'width': 470, 5 | 'height': 530, 6 | 'path': 'M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z' 7 | } 8 | 9 | const newConfigObject = (cfg) => { 10 | return { 11 | // showEditInChartStudio: true, 12 | // plotlyServerURL: "https://chart-studio.plotly.com", 13 | displaylogo: false, 14 | modeBarButtonsToRemove: ['2D', 'zoom2d', 'pan2d', 'select2d', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d', 'toggleSpikelines'], 15 | modeBarButtonsToAdd: [{ 16 | name: 'info', 17 | title: "Plot info", 18 | icon: infoIcon, 19 | val: false, 20 | click: handleInfoButton, 21 | },], 22 | toImageButtonOptions: { 23 | format: 'png', 24 | filename: cfg.name, 25 | } 26 | } 27 | } 28 | 29 | const copyArrayOrNull = (o) => { 30 | return Array.isArray(o) && [...o] || null; 31 | } 32 | 33 | const newLayoutObject = (cfg) => { 34 | const layout = { 35 | title: { 36 | y: 0.88, 37 | font: { 38 | family: "Roboto", 39 | size: 18, 40 | }, 41 | text: cfg.title, 42 | }, 43 | margin: { 44 | t: 80, 45 | }, 46 | paper_bgcolor: cfg.layout.paper_bgcolor, 47 | plot_bgcolor: cfg.layout.plot_bgcolor, 48 | font: { 49 | color: cfg.layout.font_color 50 | }, 51 | width: 630, 52 | height: 450, 53 | hovermode: 'x', 54 | barmode: cfg.layout.barmode, 55 | xaxis: { 56 | tickformat: '%H:%M:%S', 57 | type: "date", 58 | fixedrange: true, 59 | autorange: false, 60 | }, 61 | yaxis: { 62 | exponentformat: 'SI', 63 | tickmode: cfg.layout.yaxis.tickmode, 64 | ticktext: copyArrayOrNull(cfg.layout.yaxis.ticktext), 65 | tickvals: copyArrayOrNull(cfg.layout.yaxis.tickvals), 66 | title: cfg.layout.yaxis.title, 67 | ticksuffix: cfg.layout.yaxis.ticksuffix, 68 | fixedrange: true, 69 | }, 70 | showlegend: true, 71 | legend: { 72 | "orientation": "h" 73 | } 74 | }; 75 | 76 | if (layout.yaxis.tickmode == "array") { 77 | // Format yaxis ticks 78 | const formatYUnit = formatFunction(cfg.hover.yunit); 79 | for (let i = 0; i < layout.yaxis.ticktext.length; i++) { 80 | layout.yaxis.ticktext[i] = formatYUnit(layout.yaxis.ticktext[i]); 81 | } 82 | } 83 | 84 | return layout; 85 | } 86 | 87 | const handleInfoButton = (gd, ev) => { 88 | let button = ev.currentTarget; 89 | let val = (button.getAttribute('data-val') === 'true'); 90 | 91 | const options = { 92 | allowHTML: true, 93 | trigger: 'click', 94 | }; 95 | 96 | const instance = tippy(ev.currentTarget, options); 97 | instance.setContent("
" + gd.infoText + "
"); 98 | if (val) { 99 | instance.hide(); 100 | } else { 101 | instance.show(); 102 | } 103 | button.setAttribute('data-val', !val); 104 | } 105 | 106 | const themeColors = { 107 | light: { 108 | paper_bgcolor: '#f8f8f8', 109 | plot_bgcolor: '#ffffdd', 110 | font_color: '#434343' 111 | }, 112 | dark: { 113 | paper_bgcolor: '#181a1c', 114 | plot_bgcolor: '#282a2c', 115 | font_color: '#fff' 116 | } 117 | }; 118 | 119 | /* 120 | Plot configuration object: 121 | { 122 | "name": string, // internal name 123 | "title": string, // plot title 124 | "type": 'scatter'|'bar'|'heatmap' 125 | "updateFreq": int, // datapoints to receive before redrawing the plot. (default: 1) 126 | "infoText": string, // text showed in the plot 'info' tooltip 127 | "events": "lastgc", // source of vertical lines (example: 'lastgc') 128 | "layout": object, // (depends on plot type) 129 | "subplots": array, // describe 'traces', only for 'scatter' or 'bar' plots 130 | "heatmap": object, // heatmap details 131 | } 132 | 133 | Layout for 'scatter' and 'bar' plots: 134 | { 135 | "yaxis": { 136 | "title": { 137 | "text": "bytes" // yaxis title 138 | }, 139 | "ticksuffix": "B", // base unit for ticks 140 | }, 141 | "barmode": "stack", // 'stack' or 'group' (only for bar plots) 142 | }, 143 | 144 | Layout" for heatmaps: 145 | { 146 | "yaxis": { 147 | tickmode: string (supports 'array' only) 148 | tickvals: []float64 149 | ticktext: []float64 150 | "title": { 151 | "text": "size class" 152 | } 153 | } 154 | 155 | Subplots show the potentially multiple trace objects for 'scatter' and 'bar' 156 | plots. Each trace is an object: 157 | { 158 | "name": string; // internal name 159 | "unitfmt": string, // d3 format string for tooltip 160 | "stackgroup": string, // stackgroup (if stacked line any) 161 | "hoveron": string // useful for stacked only (TODO(arl): remove from go) 162 | "color": colorstring // plot/trace color 163 | } 164 | 165 | Heatmap details object 166 | { 167 | "colorscale": array // array of weighted colors, 168 | "buckets": array 169 | "hover": { 170 | "yname": string, // y axis units 171 | "yunit": "bytes", // y axis name 172 | "zname": "objects" // z axis name 173 | } 174 | } 175 | */ 176 | 177 | 178 | class Plot { 179 | /** 180 | * Construct a new Plot object, wrapping a Plotly chart. See above 181 | * documentation for plot configuration. 182 | */ 183 | 184 | constructor(cfg) { 185 | cfg.layout.paper_bgcolor = themeColors[theme.getThemeMode()].paper_bgcolor; 186 | cfg.layout.plot_bgcolor = themeColors[theme.getThemeMode()].plot_bgcolor; 187 | cfg.layout.font_color = themeColors[theme.getThemeMode()].font_color; 188 | 189 | this._cfg = cfg; 190 | this._updateCount = 0; 191 | this._dataTemplate = []; 192 | this._lastData = [{ x: new Date() }]; 193 | 194 | if (this._cfg.type == 'heatmap') { 195 | this._dataTemplate.push({ 196 | type: 'heatmap', 197 | x: null, 198 | y: this._cfg.buckets, 199 | z: null, 200 | showlegend: false, 201 | colorscale: this._cfg.colorscale, 202 | custom_data: this._cfg.custom_data, 203 | }); 204 | } else { 205 | this._cfg.subplots.forEach(subplot => { 206 | this._dataTemplate.push({ 207 | type: this._cfg.type, 208 | x: null, 209 | y: null, 210 | name: subplot.name, 211 | hovertemplate: `${subplot.unitfmt}`, 212 | }) 213 | }); 214 | } 215 | 216 | this._plotlyLayout = newLayoutObject(cfg); 217 | this._plotlyConfig = newConfigObject(cfg); 218 | } 219 | 220 | name() { 221 | return this._cfg.name; 222 | } 223 | 224 | createElement(div, idx) { 225 | this._htmlElt = div; 226 | this._plotIdx = idx; 227 | // Pass a single data with no data to create an empty plot, this removes 228 | // the 'bad time formatting' warning at startup. 229 | Plotly.newPlot(this._htmlElt, this._lastData, this._plotlyLayout, this._plotlyConfig); 230 | if (this._cfg.type == 'heatmap') { 231 | this._installHeatmapTooltip(); 232 | } 233 | 234 | this._htmlElt.infoText = this._cfg.infoText.split('\n').map(line => `

${line}

`).join(''); 235 | } 236 | 237 | _installHeatmapTooltip() { 238 | const options = { 239 | followCursor: true, 240 | trigger: "manual", 241 | allowHTML: true 242 | }; 243 | const instance = tippy(document.body, options); 244 | const hover = this._cfg.hover; 245 | const formatYUnit = formatFunction(hover.yunit); 246 | 247 | const onHover = (data) => { 248 | const pt2txt = (d) => { 249 | let bucket; 250 | if (d.y == 0) { 251 | const yhigh = formatYUnit(d.data.custom_data[d.y]); 252 | bucket = `(-Inf, ${yhigh})`; 253 | } else if (d.y == d.data.custom_data.length - 1) { 254 | const ylow = formatYUnit(d.data.custom_data[d.y]); 255 | bucket = `[${ylow}, +Inf)`; 256 | } else { 257 | const ylow = formatYUnit(d.data.custom_data[d.y - 1]); 258 | const yhigh = formatYUnit(d.data.custom_data[d.y]); 259 | bucket = `[${ylow}, ${yhigh})`; 260 | } 261 | 262 | return ` 263 |
264 |
265 |
${hover.yname}
266 |
${bucket}
267 |
268 |
269 |
${hover.zname}
270 |
${d.z}
271 |
272 |
`; 273 | } 274 | instance.setContent(data.points.map(pt2txt)[0]); 275 | instance.show(); 276 | }; 277 | const onUnhover = (data) => { 278 | instance.hide(); 279 | }; 280 | 281 | this._htmlElt.on('plotly_hover', onHover) 282 | .on('plotly_unhover', onUnhover); 283 | } 284 | 285 | _extractData(data) { 286 | const serie = data.series.get(this._cfg.name); 287 | if (this._cfg.type == 'heatmap') { 288 | this._dataTemplate[0].x = data.times; 289 | this._dataTemplate[0].z = serie; 290 | this._dataTemplate[0].hoverinfo = 'none'; 291 | } else { 292 | for (let i = 0; i < this._dataTemplate.length; i++) { 293 | this._dataTemplate[i].x = data.times; 294 | this._dataTemplate[i].y = serie[i]; 295 | this._dataTemplate[i].stackgroup = this._cfg.subplots[i].stackgroup; 296 | this._dataTemplate[i].hoveron = this._cfg.subplots[i].hoveron; 297 | this._dataTemplate[i].type = this._cfg.subplots[i].type || this._cfg.type; 298 | this._dataTemplate[i].marker = { 299 | color: this._cfg.subplots[i].color, 300 | }; 301 | } 302 | } 303 | return this._dataTemplate; 304 | } 305 | 306 | update(xrange, data, shapes) { 307 | this._lastData = this._extractData(data); 308 | this._updateCount++; 309 | if (this._cfg.updateFreq == 0 || (this._updateCount % this._cfg.updateFreq == 0)) { 310 | // Update layout with vertical shapes if necessary. 311 | if (this._cfg.events != '') { 312 | this._plotlyLayout.shapes = shapes.get(this._cfg.events); 313 | } 314 | 315 | // Move the xaxis time range. 316 | this._plotlyLayout.xaxis.range = xrange; 317 | 318 | Plotly.react(this._htmlElt, this._lastData, this._plotlyLayout, this._plotlyConfig); 319 | } 320 | } 321 | 322 | /** 323 | * update theme color and immediately force plot redraw to apply the new theme 324 | */ 325 | updateTheme() { 326 | const themeMode = theme.getThemeMode(); 327 | this._cfg.layout.paper_bgcolor = themeColors[themeMode].paper_bgcolor; 328 | this._cfg.layout.plot_bgcolor = themeColors[themeMode].plot_bgcolor; 329 | this._cfg.layout.font_color = themeColors[themeMode].font_color; 330 | 331 | this._plotlyLayout = newLayoutObject(this._cfg); 332 | this._plotlyConfig = newConfigObject(this._cfg); 333 | 334 | Plotly.react(this._htmlElt, this._lastData, this._plotlyLayout); 335 | } 336 | }; 337 | 338 | // Create 'vertical lines' shapes for each of the given timestamps. 339 | const createVerticalLines = (tss) => { 340 | const shapes = []; 341 | for (let i = 0, n = tss.length; i < n; i++) { 342 | const d = tss[i]; 343 | shapes.push({ 344 | type: 'line', 345 | x0: d, 346 | x1: d, 347 | yref: 'paper', 348 | y0: 0, 349 | y1: 1, 350 | line: { 351 | color: 'rgb(55, 128, 191)', 352 | width: 1, 353 | dash: 'longdashdot', 354 | } 355 | }) 356 | } 357 | return shapes; 358 | } 359 | 360 | export { createVerticalLines, Plot }; 361 | 362 | const durUnits = ['w', 'd', 'h', 'm', 's', 'ms', 'µs', 'ns']; 363 | const durVals = [6048e11, 864e11, 36e11, 6e10, 1e9, 1e6, 1e3, 1]; 364 | 365 | // Formats a time duration provided in second. 366 | const formatDuration = sec => { 367 | let ns = sec * 1e9; 368 | for (let i = 0; i < durUnits.length; i++) { 369 | let inc = ns / durVals[i]; 370 | 371 | if (inc < 1) continue; 372 | return Math.round(inc) + durUnits[i]; 373 | } 374 | return res.trim(); 375 | }; 376 | 377 | const bytesUnits = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']; 378 | 379 | // Formats a size in bytes. 380 | const formatBytes = bytes => { 381 | let i = 0; 382 | while (bytes > 1000) { 383 | bytes /= 1000; 384 | i++; 385 | } 386 | const res = Math.trunc(bytes); 387 | return `${res}${bytesUnits[i]}`; 388 | }; 389 | 390 | // Returns a format function based on the provided unit. 391 | const formatFunction = unit => { 392 | switch (unit) { 393 | case 'duration': 394 | return formatDuration; 395 | case 'bytes': 396 | return formatBytes; 397 | } 398 | // Default formatting 399 | return (y) => { `${y} ${hover.yunit}` }; 400 | }; 401 | -------------------------------------------------------------------------------- /internal/static/js/stats.js: -------------------------------------------------------------------------------- 1 | // stats holds the data and function to modify it. 2 | import Buffer from "./buffer.js"; 3 | 4 | var series = { 5 | times: null, 6 | eventsData: new Map(), 7 | plotData: new Map(), 8 | }; 9 | 10 | // initialize time series storage. 11 | const init = (plotdefs, buflen) => { 12 | const extraBufferCapacity = 20; // 20% of extra (preallocated) buffer datapoints 13 | const bufcap = buflen + (buflen * extraBufferCapacity) / 100; // number of actual datapoints 14 | 15 | series.times = new Buffer(buflen, bufcap); 16 | series.plotData.clear(); 17 | plotdefs.series.forEach(plotdef => { 18 | let ndim; 19 | switch (plotdef.type) { 20 | case 'bar': 21 | case 'scatter': 22 | ndim = plotdef.subplots.length; 23 | break; 24 | case 'heatmap': 25 | ndim = plotdef.buckets.length; 26 | break; 27 | default: 28 | console.error(`[statsviz]: unknown plot type "${plotdef.type}"`); 29 | return; 30 | }; 31 | 32 | let data = new Array(ndim); 33 | for (let i = 0; i < ndim; i++) { 34 | data[i] = new Buffer(buflen, bufcap); 35 | } 36 | series.plotData.set(plotdef.name, data); 37 | }); 38 | 39 | plotdefs.events.forEach(event => { 40 | series.eventsData.set(event, new Array()); 41 | }); 42 | } 43 | 44 | // push a new datapoint to all time series. 45 | const pushData = (data) => { 46 | series.times.push(data.timestamp); 47 | 48 | // Update time series. 49 | for (const [name, plotData] of series.plotData) { 50 | const curdata = data[name]; 51 | for (let i = 0; i < curdata.length; i++) { 52 | plotData[i].push(curdata[i]); 53 | } 54 | } 55 | 56 | // Update events series, deduplicating event timestamps and trimming the ones 57 | // that are oldest with respect to the oldest timestamp we're keeping track of. 58 | for (const [name, event] of series.eventsData) { 59 | if (event.length == 0) { 60 | if (data[name].length != 0) { 61 | const eventTs = new Date(Math.floor(data[name][0])); 62 | event.push(eventTs); 63 | } 64 | return; 65 | } 66 | const eventTs = new Date(Math.floor(data[name][0])); 67 | if (eventTs.getTime() != event[event.length - 1].getTime()) { 68 | event.push(eventTs); 69 | let mints = series.times._buf[0]; 70 | if (event[0] < mints) { 71 | event.splice(0, 1); 72 | } 73 | } 74 | } 75 | } 76 | 77 | // slice returns the last n items from all time series. 78 | const slice = (n) => { 79 | let sliced = { 80 | times: series.times.slice(n), 81 | series: new Map(), 82 | events: series.eventsData, 83 | }; 84 | 85 | for (const [name, plotData] of series.plotData) { 86 | const arr = new Array(plotData.length); 87 | for (let i = 0; i < plotData.length; i++) { 88 | arr[i] = plotData[i].slice(n); 89 | } 90 | sliced.series.set(name, arr); 91 | } 92 | return sliced; 93 | } 94 | 95 | export { init, pushData, slice }; -------------------------------------------------------------------------------- /internal/static/js/theme.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get color theme based on previous user choice or browser theme 3 | */ 4 | export const getThemeMode = () => { 5 | let themeMode = localStorage.getItem("theme-mode"); 6 | 7 | if (themeMode === null) { 8 | const isDark = 9 | window.matchMedia && 10 | window.matchMedia("(prefers-color-scheme: dark)").matches; 11 | themeMode = (isDark && "dark") || "light"; 12 | 13 | localStorage.setItem("theme-mode", themeMode); 14 | } 15 | 16 | return themeMode; 17 | }; 18 | 19 | /** 20 | * Set light or dark theme 21 | */ 22 | export const updateThemeMode = () => { 23 | if (getThemeMode() === "dark") { 24 | document.body.classList.add("dark-theme"); 25 | document 26 | .getElementById("navbar") 27 | .classList.replace("navbar-light", "navbar-dark"); 28 | document.getElementById("navbar").classList.replace("bg-light", "bg-dark"); 29 | } else { 30 | document.body.classList.remove("dark-theme"); 31 | document 32 | .getElementById("navbar") 33 | .classList.replace("navbar-dark", "navbar-light"); 34 | document.getElementById("navbar").classList.replace("bg-dark", "bg-light"); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /internal/static/libs/css/bootstrap-toggle.min.css: -------------------------------------------------------------------------------- 1 | /*! ======================================================================== 2 | * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 3 | * http://www.bootstraptoggle.com 4 | * ======================================================================== 5 | * Copyright 2014 Min Hur, The New York Times Company 6 | * Licensed under MIT 7 | * ======================================================================== */ 8 | .checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px} 9 | .toggle{position:relative;overflow:hidden} 10 | .toggle input[type=checkbox]{display:none} 11 | .toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} 12 | .toggle.off .toggle-group{left:-100%} 13 | .toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} 14 | .toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} 15 | .toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} 16 | .toggle.btn{min-width:59px;min-height:34px} 17 | .toggle-on.btn{padding-right:24px} 18 | .toggle-off.btn{padding-left:24px} 19 | .toggle.btn-lg{min-width:79px;min-height:45px} 20 | .toggle-on.btn-lg{padding-right:31px} 21 | .toggle-off.btn-lg{padding-left:31px} 22 | .toggle-handle.btn-lg{width:40px} 23 | .toggle.btn-sm{min-width:50px;min-height:30px} 24 | .toggle-on.btn-sm{padding-right:20px} 25 | .toggle-off.btn-sm{padding-left:20px} 26 | .toggle.btn-xs{min-width:35px;min-height:22px} 27 | .toggle-on.btn-xs{padding-right:12px} 28 | .toggle-off.btn-xs{padding-left:12px} -------------------------------------------------------------------------------- /internal/static/libs/js/bootstrap-toggle.min.js: -------------------------------------------------------------------------------- 1 | /*! ======================================================================== 2 | * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 3 | * http://www.bootstraptoggle.com 4 | * ======================================================================== 5 | * Copyright 2014 Min Hur, The New York Times Company 6 | * Licensed under MIT 7 | * ======================================================================== */ 8 | +function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="2.2.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"default",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size?"btn-lg":"small"===this.options.size?"btn-sm":"mini"===this.options.size?"btn-xs":"",c=a('