pax_global_header00006660000000000000000000000064143365532460014525gustar00rootroot0000000000000052 comment=8eb3e3669ea76d3497f099b8c367abf461e53d6e golang-github-performancecopilot-speed-4.0.0/000077500000000000000000000000001433655324600212445ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/.github/000077500000000000000000000000001433655324600226045ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/.github/FUNDING.yml000066400000000000000000000000251433655324600244160ustar00rootroot00000000000000open_collective: pcp golang-github-performancecopilot-speed-4.0.0/.github/workflows/000077500000000000000000000000001433655324600246415ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/.github/workflows/ci.yaml000066400000000000000000000021221433655324600261150ustar00rootroot00000000000000name: CI on: push: branches: - master - github-actions pull_request: jobs: test: name: Test runs-on: ubuntu-latest strategy: matrix: go: ["1.15", "1.16"] env: GOFLAGS: -mod=readonly steps: - name: Set up Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - name: Checkout code uses: actions/checkout@v2 - name: Install PCP run: | echo "::group::Installation" sudo apt-get -y install pcp echo "::endgroup::" echo "::group::Config" cat /etc/pcp.conf echo "::endgroup::" - name: Run tests run: sudo make race lint: name: Lint runs-on: ubuntu-latest env: GOFLAGS: -mod=readonly steps: - name: Set up Go uses: actions/setup-go@v2 with: go-version: "1.16" - name: Checkout code uses: actions/checkout@v2 - name: Lint uses: golangci/golangci-lint-action@v2 with: skip-go-installation: true golang-github-performancecopilot-speed-4.0.0/.gitignore000066400000000000000000000000621433655324600232320ustar00rootroot00000000000000.DS_Store *.swp # coverage *.coverage .vscode/ golang-github-performancecopilot-speed-4.0.0/.travis.yml000066400000000000000000000035711433655324600233630ustar00rootroot00000000000000language: go go: - 1.10.x - 1.11.x - 1.12.x - tip env: global: - GO111MODULE=on # coveralls token - secure: Tt++Fdp5IqwJyYuK5EFA29BHGInHxVylFQMRtvpNt2qMZA89Z1N4BUDswqVu23TaMwoFM0a1/E3tAuC0Ffqm19oS8jnFyonuFlXduwG8iVb8znWzP67Jtj9l5xDzra/NhdhE25OxUD3b203rOMChpRx77i+eBITlaYsOMUTq/K77at+NW58qL3W7h6vu4WFBG0T0RbEVjp+IxSJiAUJI9jSYYldo7EpeDL/BqMJeMVe1D0UfVa/DU0GNdDKYibmF+Mag/v34ttlfdYarLEicwQpcQb52NfrRg06iPmiiqRsN/glU2clzkJPLZUmx3UgMPK8nKg4Iu8LXKMCm46UhnnsWcXjnquioLa8vPCv2hoModCcwjGLqC4BHnnh4QqNkMnL3UpLWQcT5zrqvsEIcy1TpwKwOEcIRo1ZqEmgl2kkz5hBN6hjFKR44Uti2ayw831X8FRxyGsq7O6b8R7M4w5gdt12Hj2V04Qnr718ZBhsrbNn+GPPv3p9Pspal9CXvuWTnIRbPCw0DOdajLIX9JMUB86ElPqLGxHBsrc6Ev2rsFPlJZqyur9KBU/KJfmlhrV8veksorugMk6nxshzS0P8eAdbmC+xTo999LQPNZqsNVfbKI97UKkfy7jZxHLDriTBD9qHtJSt+J2wkcS6iGrNe5bEpksZAmxJlSwSqIuY= before_install: - make deps - go get github.com/mattn/goveralls - go get golang.org/x/tools/cmd/cover install: true before_script: - make script: - make race - make lint - "$HOME/gopath/bin/goveralls -v -service=travis-ci -package=." notifications: email: on_success: change on_failure: change slack: on_success: change on_failure: change on_pull_requests: false secure: ro8jvUWa7x6eFvaWkVHpyC753If3lKVcNttAcQAZ+BtJAeWPGnNtwMbbRpuNfXNqt2ZZghICbK1AhX41F0SZ1Fs3/fRndPzBu1JjCVE3Rr+hiua3aZjF6ncTZjhVWg/IZQHVOIi7moveWd5LdJ6t0eS+Np4tRX1U25n4n3wbT0+tElA10cX7ESttbDs6eGiHF14IKMSHvJ6h8xW2ozi9F74Q0VDS0fyV0qCruGzRT15yOQyGI/iunJudQQvcrZmLGxuw1kwkYcWPUdniYBFbjmmCeuz0NhXE7m7p06cLSQsnrPQ/Ucy42QSxGgfT4+D1aoHQrfv4H5QFw9edTRINoX39qG4Ieuw7IbXbzjQFom0LvG65lZm/Y4MNGy4mJt5A+7uH5UPU5lBMUvAEMq3i92FCWGocJZTWuJi0Jpmlbp8JzdHZbBzAKIyHyxZB0fMVkrats0eH8nORRtBw0plKGp6c78rWlhw6RwXvFhlvDRpyEPhFugUbiN9bGNuVtHTcFqRgGc94Chmr1mKE9o5VFJiae82A0WX6OfGpffa+NOQ/lavcQvpfE9KWrVSq9I5mgVQCkQJ2LlDYuEtVLsRTwZjSBojgELBnEkttm77oeQNA51vYbuG+n7gyCTdckjs58OVmKGf9JGlJ37Xu4OST9gyFg875lCt3rIhLILmc3ng= golang-github-performancecopilot-speed-4.0.0/CHANGELOG.md000066400000000000000000000020471433655324600230600ustar00rootroot00000000000000 v4.0.0 / 2021-07-22 =================== * build: fix semantic import versioning (#63) v3.1.0 / 2021-07-16 =================== * build: update golang dependencies to modern versions (#60) * build: use github actions for CI (#61) * build: remove vendored code (#62) * tests: fixes for 32-bit platforms related to integers (#57) * Update README with modern PCP and grafana-pcp (over Vector) v3.0.1 / 2017-10-30 =================== * metrics: use a global InstanceDomain for PCPHistogram, fixes #54 (#55) * build: add golangci-lint and run lint on CI again (#56) v3.0.0 / 2017-10-30 =================== * metrics: add support for multidimensional composite metrics (#50) * speed: remove logging from library (#49) (**BREAKING**) * add mmvdump tests (#48) v2.0.0 / 2017-04-05 =================== * examples: use log.Fatal instead of panic in all examples (#45) * examples: add example which exposes Go runtime metrics (#44) * Replace logrus with zap (#43) * Port Speed to Windows (#41) v1.0.0 / 2016-10-28 =================== golang-github-performancecopilot-speed-4.0.0/LICENSE000066400000000000000000000020471433655324600222540ustar00rootroot00000000000000MIT License Copyright (c) 2016 Suyash Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-performancecopilot-speed-4.0.0/Makefile000066400000000000000000000013571433655324600227120ustar00rootroot00000000000000build: go build ./... go install ./... clean: git clean -Xf clean_string: rm *_string.go rm mmvdump/*_string.go gen: go generate deps: go get -u golang.org/x/tools/cmd/stringer curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.15.0 bin/golangci-lint --version lint: bin/golangci-lint run test: go test -v ./... race: go test -v -race ./... cover: coverage coverage: go test -v -coverprofile=speed.coverage go tool cover -html=speed.coverage go test -v -coverprofile=bytebuffer.coverage ./bytebuffer/ go tool cover -html=bytebuffer.coverage go test -v -coverprofile=mmvdump.coverage ./mmvdump/ go tool cover -html=mmvdump.coverage bench: benchmark benchmark: go test -v -bench ./... golang-github-performancecopilot-speed-4.0.0/README.md000066400000000000000000000257001433655324600225270ustar00rootroot00000000000000![Speed](images/speed.png) Golang implementation of the Performance Co-Pilot (PCP) instrumentation API [![Build Status](https://github.com/performancecopilot/speed/workflows/CI/badge.svg)](https://github.com/performancecopilot/speed/actions/workflows/ci.yaml) [![Coverage Status](https://coveralls.io/repos/github/performancecopilot/speed/badge.svg?branch=main)](https://coveralls.io/github/performancecopilot/speed?branch=main) [![GoDoc](https://godoc.org/github.com/performancecopilot/speed?status.svg)](https://godoc.org/github.com/performancecopilot/speed) [![Go Report Card](https://goreportcard.com/badge/github.com/performancecopilot/speed)](https://goreportcard.com/report/github.com/performancecopilot/speed) [![Mailing List](https://img.shields.io/badge/Mailing%20List-pcp-blue.svg)](https://groups.io/g/pcp) [![Slack Team](https://img.shields.io/badge/Slack-pcp-blue.svg)](https://h7zo83mvt1.execute-api.us-west-2.amazonaws.com/Express/) [![IRC #pcp](https://img.shields.io/badge/IRC-pcp-blue.svg)](https://web.libera.chat/#pcp) [![Github Release](https://img.shields.io/github/release/performancecopilot/speed.svg)](https://github.com/performancecopilot/speed/releases/latest) - [Install](#install) - [Prerequisites](#prerequisites) - [PCP](#pcp) - [Go](#go) - [Grafana](#grafana) - [Getting the library](#getting-the-library) - [Getting the examples](#getting-the-examples) - [Walkthrough](#walkthrough) - [SingletonMetric](#singletonmetric) - [InstanceMetric](#instancemetric) - [Counter](#counter) - [CounterVector](#countervector) - [Gauge](#gauge) - [GaugeVector](#gaugevector) - [Timer](#timer) - [Histogram](#histogram) - [Go Kit](#go-kit) ## Install ### Prerequisites #### [PCP](https://pcp.io) Install Performance Co-Pilot on your local machine, either using prebuilt archives or by getting and building the source code. For detailed instructions, read the [page from pcp.readthedocs.io](https://pcp.readthedocs.io/en/latest/HowTos/installation/index.html). #### [Go](https://golang.org) Set up a go environment on your computer. For more information about these steps, please read [how to write go code](https://golang.org/doc/code.html). - download and install go 1.6 or above from [https://golang.org/dl](https://golang.org/dl) - set up `$GOPATH` to the root folder where you want to keep your go code - add `$GOPATH/bin` to your `$PATH` by adding `export PATH=$GOPATH/bin:$PATH` to your shell configuration file, such as to your `.bashrc`, if using a Bourne shell variant. #### [Grafana](https://grafana-pcp.readthedocs.io/) The grafana-pcp plugin provides PCP metrics in the popular Grafana visualization tool. It includes [PCP Vector](https://grafana-pcp.readthedocs.io/en/latest/screenshots.html#pcp-vector), a live datasource for metrics exposed using Performance Co-Pilot. Metrics you create with Speed are immediately visible in Grafana using this datasource. ### Getting the library ```sh go get github.com/performancecopilot/speed ``` ### Getting the examples All examples are executable go programs. Simply doing ```sh go get github.com/performancecopilot/speed/examples/ ``` will get the example and add an executable to `$GOPATH/bin`. If it is on your path, simply doing ```sh ``` will run the binary, running the example ## Walkthrough There are 3 main components defined in the library, a [__Client__](https://godoc.org/github.com/performancecopilot/speed#Client), a [__Registry__](https://godoc.org/github.com/performancecopilot/speed#Registry) and a [__Metric__](https://godoc.org/github.com/performancecopilot/speed#Metric). A client is created using an application name, and the same name is used to create a memory mapped file in `PCP_TMP_DIR`. Each client contains a registry of metrics that it holds, and will publish on being activated. It also has a `SetFlag` method allowing you to set a mmv flag while a mapping is not active, to one of three values, [`NoPrefixFlag`, `ProcessFlag` and `SentinelFlag`](https://godoc.org/github.com/performancecopilot/speed#MMVFlag). The ProcessFlag is the default and reports metrics prefixed with the application name (i.e. like `mmv.app_name.metric.name`). Setting it to `NoPrefixFlag` will report metrics without being prefixed with the application name (i.e. like `mmv.metric.name`) which can lead to namespace collisions, so be sure of what you're doing. A client can register metrics to report through 2 interfaces, the first is the `Register` method, that takes a raw metric object. The other is using `RegisterString`, that can take a string with metrics and instances to register similar to the interface in parfait, along with type, semantics and unit, in that order. A client can be activated by calling the `Start` method, deactivated by the `Stop` method. While a client is active, no new metrics can be registered but it is possible to stop existing client for metric registration. Each client contains an instance of the `Registry` interface, which can give different information like the number of registered metrics and instance domains. It also exports methods to register metrics and instance domains. Finally, metrics are defined as implementations of different metric interfaces, but they all implement the `Metric` interface, the different metric types defined are ### [SingletonMetric](https://godoc.org/github.com/performancecopilot/speed#SingletonMetric) This type defines a metric with no instance domain and only one value. It __requires__ type, semantics and unit for construction, and optionally takes a couple of description strings. A simple construction ```go metric, err := speed.NewPCPSingletonMetric( 42, // initial value "simple.counter", // name speed.Int32Type, // type speed.CounterSemantics, // semantics speed.OneUnit, // unit "A Simple Metric", // short description "This is a simple counter metric to demonstrate the speed API", // long description ) ``` A SingletonMetric supports a `Val` method that returns the metric value and a `Set(interface{})` method that sets the metric value. ### [InstanceMetric](https://godoc.org/github.com/performancecopilot/speed#InstanceMetric) An `InstanceMetric` is a single metric object containing multiple values of the same type for multiple instances. It also __requires__ an instance domain along with type, semantics and unit for construction, and optionally takes a couple of description strings. A simple construction ```go indom, err := speed.NewPCPInstanceDomain( "Acme Products", // name []string{"Anvils", "Rockets", "Giant_Rubber_Bands"}, // instances "Acme products", // short description "Most popular products produced by the Acme Corporation", // long description ) ... countmetric, err := speed.NewPCPInstanceMetric( speed.Instances{ "Anvils": 0, "Rockets": 0, "Giant_Rubber_Bands": 0, }, "products.count", indom, speed.Uint64Type, speed.CounterSemantics, speed.OneUnit, "Acme factory product throughput", `Monotonic increasing counter of products produced in the Acme Corporation factory since starting the Acme production application. Quality guaranteed.`, ) ``` An instance metric supports a `ValInstance(string)` method that returns the value as well as a `SetInstance(interface{}, string)` that sets the value of a particular instance. ### [Counter](https://godoc.org/github.com/performancecopilot/speed#Counter) A counter is simply a PCPSingletonMetric with `Int64Type`, `CounterSemantics` and `OneUnit`. It can optionally take a short and a long description. A simple example ```go c, err := speed.NewPCPCounter(0, "a.simple.counter") ``` a counter supports `Set(int64)` to set a value, `Inc(int64)` to increment by a custom delta and `Up()` to increment by 1. ### [CounterVector](https://godoc.org/github.com/performancecopilot/speed#CounterVector) A CounterVector is a PCPInstanceMetric , with `Int64Type`, `CounterSemantics` and `OneUnit` and an instance domain created and registered on initialization, with the name `metric_name.indom`. A simple example ```go c, err := speed.NewPCPCounterVector( map[string]uint64{ "instance1": 0, "instance2": 1, }, "another.simple.counter" ) ``` It supports `Val(string)`, `Set(uint64, string)`, `Inc(uint64, string)` and `Up(string)` amongst other things. ### [Gauge](https://godoc.org/github.com/performancecopilot/speed#Gauge) A Gauge is a simple SingletonMetric storing float64 values, i.e. a PCP Singleton Metric with `DoubleType`, `InstantSemantics` and `OneUnit`. A simple example ```go g, err := speed.NewPCPGauge(0, "a.sample.gauge") ``` supports `Val()`, `Set(float64)`, `Inc(float64)` and `Dec(float64)` ### [GaugeVector](https://godoc.org/github.com/performancecopilot/speed#GaugeVector) A Gauge Vector is a PCP instance metric with `DoubleType`, `InstantSemantics` and `OneUnit` and an autogenerated instance domain. A simple example ```go g, err := NewPCPGaugeVector(map[string]float64{ "instance1": 1.2, "instance2": 2.4, }, "met") ``` supports `Val(string)`, `Set(float64, string)`, `Inc(float64, string)` and `Dec(float64, string)` ### [Timer](https://godoc.org/github.com/performancecopilot/speed#Timer) A timer stores the time elapsed for different operations. __It is not compatible with PCP's elapsed type metrics__. It takes a name and a `TimeUnit` for construction. ```go timer, err := speed.NewPCPTimer("test", speed.NanosecondUnit) ``` calling `timer.Start()` signals the start of an operation calling `timer.Stop()` signals end of an operation and will return the total elapsed time calculated by the metric so far. ### [Histogram](https://godoc.org/github.com/performancecopilot/speed#Histogram) A histogram implements a PCP Instance Metric that reports the `mean`, `variance` and `standard_deviation` while using a histogram backed by [codahale's hdrhistogram implementation in golang](https://github.com/HdrHistogram/hdrhistogram-go). Other than these, it also returns a custom percentile and buckets for plotting graphs. It requires a low and a high value and the number of significant figures used at the time of construction. ``` m, err := speed.NewPCPHistogram("hist", 0, 1000, 5) ``` ## [Go Kit](https://gokit.io) Go kit provides [a wrapper package](https://godoc.org/github.com/go-kit/kit/metrics/pcp) over speed that can be used for building microservices that expose metrics using PCP. For modified versions of the examples in go-kit that use pcp to report metrics, see [suyash/kit-pcp-examples](https://github.com/suyash/kit-pcp-examples) golang-github-performancecopilot-speed-4.0.0/appveyor.yml000066400000000000000000000010761433655324600236400ustar00rootroot00000000000000version: "{build}" clone_folder: c:\gopath\src\github.com\performancecopilot\speed environment: GOPATH: c:\gopath matrix: - environment: GOVERSION: 1.10.8 - environment: GOVERSION: 1.11.6 - environment: GOVERSION: 1.12.1 install: - rmdir c:\go /s /q - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi - msiexec /i go%GOVERSION%.windows-amd64.msi /q - echo %PATH% - echo %GOPATH% - set PATH=c:\go\bin;%GOPATH%\bin;%PATH% - go version - go env build_script: - go test -v ./... golang-github-performancecopilot-speed-4.0.0/bytewriter/000077500000000000000000000000001433655324600234445ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/bytewriter/README.md000066400000000000000000000021451433655324600247250ustar00rootroot00000000000000# bytewriter [![GoDoc](https://godoc.org/github.com/performancecopilot/speed/bytewriter?status.svg)](https://godoc.org/github.com/performancecopilot/speed/bytewriter) Package bytewriter implements writers that support concurrent writing within a fixed length block initially tried to use bytes.Buffer but the main restriction with that is that it does not allow the freedom to move around in the buffer. Further, it always writes at the end of the buffer another attempt was to maintain a position identifier that was always passed to any function that wrote anything and the function *had* to return the next writable location, which resulted in calls like ```go pos = writeString(buffer, pos, "mmv") ``` which became unmaintainable after a while, and along with all the side maintainance looked extremely ugly then implemented a minimal buffer wrapper that gives the freedom to move around and write anywhere you want, which worked if you only need one position identifier, i.e. only one write operation happens at a time this implements a writer that supports multiple concurrent writes within a fixed length block golang-github-performancecopilot-speed-4.0.0/bytewriter/bytewriter.go000066400000000000000000000100651433655324600261750ustar00rootroot00000000000000package bytewriter import ( "bytes" "encoding/binary" "github.com/pkg/errors" ) // assumes Little Endian, use _arch.go to set it to BigEndian for those archs var byteOrder = binary.LittleEndian // ByteWriter is a simple wrapper over a byte slice that supports writing anywhere type ByteWriter struct { buffer []byte } // NewByteWriter creates a new ByteWriter of the specified size func NewByteWriter(n int) *ByteWriter { return &ByteWriter{make([]byte, n)} } // NewByteWriterSlice creates a new ByteWriter using the passed slice func NewByteWriterSlice(buffer []byte) *ByteWriter { return &ByteWriter{buffer} } // Len returns the maximum size of the ByteWriter func (w *ByteWriter) Len() int { return len(w.buffer) } // Bytes returns the internal byte array of the ByteWriter func (w *ByteWriter) Bytes() []byte { return w.buffer } func (w *ByteWriter) Write(data []byte, offset int) (int, error) { l := len(data) if offset+l > w.Len() { return -1, errors.Errorf("cannot write %v bytes at offset %v", l, offset) } for i := 0; i < l; i++ { w.buffer[offset+i] = data[i] } return offset + l, nil } // MustWrite is a write that will panic if Write returns an error func (w *ByteWriter) MustWrite(data []byte, offset int) int { off, err := w.Write(data, offset) if err != nil { panic(err) } return off } // WriteVal writes an arbitrary value to the buffer func (w *ByteWriter) WriteVal(val interface{}, offset int) (int, error) { if s, isString := val.(string); isString { return w.WriteString(s, offset) } buf := bytes.NewBuffer(make([]byte, 0)) err := binary.Write(buf, byteOrder, val) if err != nil { return 0, err } return w.Write(buf.Bytes(), offset) } // MustWriteVal panics if WriteVal fails func (w *ByteWriter) MustWriteVal(val interface{}, offset int) int { if off, err := w.WriteVal(val, offset); err != nil { panic(err) } else { return off } } // WriteString writes a string to the buffer func (w *ByteWriter) WriteString(val string, offset int) (int, error) { _, err := w.Write([]byte(val), offset) return offset + len(val), err } // MustWriteString panics if WriteString fails func (w *ByteWriter) MustWriteString(val string, offset int) int { if off, err := w.WriteString(val, offset); err != nil { panic(err) } else { return off } } // WriteInt32 writes an int32 to the buffer func (w *ByteWriter) WriteInt32(val int32, offset int) (int, error) { return w.WriteVal(val, offset) } // MustWriteInt32 panics if WriteInt32 fails func (w *ByteWriter) MustWriteInt32(val int32, offset int) int { return w.MustWriteVal(val, offset) } // WriteInt64 writes an int64 to the buffer func (w *ByteWriter) WriteInt64(val int64, offset int) (int, error) { return w.WriteVal(val, offset) } // MustWriteInt64 panics if WriteInt64 fails func (w *ByteWriter) MustWriteInt64(val int64, offset int) int { return w.MustWriteVal(val, offset) } // WriteUint32 writes an uint32 to the buffer func (w *ByteWriter) WriteUint32(val uint32, offset int) (int, error) { return w.WriteVal(val, offset) } // MustWriteUint32 panics if WriteInt32 fails func (w *ByteWriter) MustWriteUint32(val uint32, offset int) int { return w.MustWriteVal(val, offset) } // WriteUint64 writes an uint64 to the buffer func (w *ByteWriter) WriteUint64(val uint64, offset int) (int, error) { return w.WriteVal(val, offset) } // MustWriteUint64 panics if WriteUint64 fails func (w *ByteWriter) MustWriteUint64(val uint64, offset int) int { return w.MustWriteVal(val, offset) } // WriteFloat32 writes an float32 to the buffer func (w *ByteWriter) WriteFloat32(val float32, offset int) (int, error) { return w.WriteVal(val, offset) } // MustWriteFloat32 panics if WriteFloat32 fails func (w *ByteWriter) MustWriteFloat32(val float32, offset int) int { return w.MustWriteVal(val, offset) } // WriteFloat64 writes an float64 to the buffer func (w *ByteWriter) WriteFloat64(val float64, offset int) (int, error) { return w.WriteVal(val, offset) } // MustWriteFloat64 panics if WriteFloat64 fails func (w *ByteWriter) MustWriteFloat64(val float64, offset int) int { return w.MustWriteVal(val, offset) } golang-github-performancecopilot-speed-4.0.0/bytewriter/bytewriter_test.go000066400000000000000000000046731433655324600272440ustar00rootroot00000000000000package bytewriter import "testing" func TestWriteInt32(t *testing.T) { cases := []int32{0, 10, 100, 200, 1000, 10000, 10000000, 1000000000, 2147483647} for _, val := range cases { b := NewByteWriter(4) off, err := b.WriteInt32(val, 0) if err != nil { t.Error(err) return } if off != 4 { t.Error("expected offset to be 4") } e := []byte{ byte(val & 0xFF), byte((val >> 8) & 0xFF), byte((val >> 16) & 0xFF), byte(val >> 24), } for i := 0; i < 4; i++ { if b.buffer[i] != e[i] { t.Errorf("pos: %v, expected: %v, got %v", i, e[i], b.buffer[i]) } } } } func TestWriteInt64(t *testing.T) { cases := []int64{0, 10, 100, 200, 1000, 10000, 10000000, 1000000000, 2147483647, 4294967295, 10000000000000, 100000000000000000, 9223372036854775807} for _, val := range cases { w := NewByteWriter(8) off, err := w.WriteInt64(val, 0) if err != nil { t.Error(err) return } if off != 8 { t.Error("Not Writing 8 bytes for int32") return } e := []byte{ byte(val & 0xFF), byte((val >> 8) & 0xFF), byte((val >> 16) & 0xFF), byte((val >> 24) & 0xFF), byte((val >> 32) & 0xFF), byte((val >> 40) & 0xFF), byte((val >> 48) & 0xFF), byte(val >> 56), } for i := 0; i < 8; i++ { if w.buffer[i] != e[i] { t.Errorf("pos: %v, expected: %v, got %v", i, e[i], w.buffer[i]) } } } } func TestWriteString(t *testing.T) { cases := []string{"MMV", "Suyash", "This is a little long string"} for _, val := range cases { w := NewByteWriter(len(val)) off, err := w.WriteString(val, 0) if err != nil { t.Error(err) return } if off != len(val) { t.Errorf("Expected to write %v bytes, writing %v bytes", len(val), off) return } e := []byte(val) for i := 0; i < len(val); i++ { if w.buffer[i] != e[i] { t.Errorf("pos: %v, expected: %v, got %v", i, e[i], w.buffer[i]) } } } } func TestOffset(t *testing.T) { w := NewByteWriter(4) off, err := w.WriteString("a", 2) if err != nil { t.Error("Did not Expect error in writing a value inside the buffer") return } if off != 3 { t.Error("Position not changing as expected") return } if w.Bytes()[2] != 'a' { t.Error("Value was not written at the expected position") return } off, err = w.WriteInt32(10, 2) if err == nil { t.Error("Expected error in writing a value guaranteed to overflow") return } if off != -1 { t.Error("expected write failure to return -1") return } } golang-github-performancecopilot-speed-4.0.0/bytewriter/memorymappedwriter.go000066400000000000000000000030261433655324600277300ustar00rootroot00000000000000package bytewriter import ( "os" "path/filepath" mmap "github.com/edsrzf/mmap-go" "github.com/pkg/errors" ) // MemoryMappedWriter is a ByteBuffer that is also mapped into memory type MemoryMappedWriter struct { *ByteWriter handle *os.File // file handle loc string // location of the memory mapped file size int // size in bytes } // NewMemoryMappedWriter will create and return a new instance of a MemoryMappedWriter func NewMemoryMappedWriter(loc string, size int) (*MemoryMappedWriter, error) { if _, err := os.Stat(loc); err == nil { err = os.Remove(loc) if err != nil { return nil, err } } // ensure destination directory exists dir := filepath.Dir(loc) err := os.MkdirAll(dir, 0700) if err != nil { return nil, err } f, err := os.OpenFile(loc, os.O_CREATE|os.O_RDWR|os.O_EXCL, 0644) if err != nil { return nil, err } l, err := f.Write(make([]byte, size)) if err != nil { return nil, err } if l < size { return nil, errors.Errorf("Could not initialize %d bytes", size) } b, err := mmap.Map(f, mmap.RDWR, 0) if err != nil { return nil, err } return &MemoryMappedWriter{ NewByteWriterSlice(b), f, loc, size, }, nil } // Unmap will manually delete the memory mapping of a mapped buffer func (b *MemoryMappedWriter) Unmap(removefile bool) error { m := mmap.MMap(b.buffer) if err := m.Unmap(); err != nil { return err } if err := b.handle.Close(); err != nil { return err } if removefile { if err := os.Remove(b.loc); err != nil { return err } } return nil } golang-github-performancecopilot-speed-4.0.0/bytewriter/memorymappedwriter_test.go000066400000000000000000000025341433655324600307720ustar00rootroot00000000000000package bytewriter import ( "os" "path/filepath" "testing" ) func TestMemoryMappedWriter(t *testing.T) { filename := "bytebuffer_memorymappedwriter_test.tmp" loc := filepath.Join(os.TempDir(), filename) if _, err := os.Stat(loc); err == nil { err = os.Remove(loc) if err != nil { t.Fatal("Cannot proceed with test as cannot remove spec file") } } w, err := NewMemoryMappedWriter(loc, 10) if err != nil { t.Fatal("Cannot proceed with test as create writer failed:", err) } if _, err = os.Stat(loc); err != nil { t.Fatalf("No File created at %v despite the Buffer being initialized", loc) } _, err = w.WriteString("x", 5) if err != nil { t.Fatal("Cannot Write to MemoryMappedWriter") } reader, err := os.Open(loc) if err != nil { t.Fatal("Cannot open memory mapped file") } data := make([]byte, 10) _, err = reader.Read(data) if err != nil { t.Fatal("Cannot read data from memory mapped file") } if data[5] != 'x' { t.Error("Data Written in buffer not getting reflected in file") } if err = reader.Close(); err != nil { t.Error("Cannot close file reader") } testUnmap(w, loc, t) } func testUnmap(w *MemoryMappedWriter, loc string, t *testing.T) { if err := w.Unmap(true); err != nil { t.Error(err) } if _, err := os.Stat(loc); err == nil { t.Error("Memory Mapped File not getting deleted on Unmap") } } golang-github-performancecopilot-speed-4.0.0/bytewriter/writer.go000066400000000000000000000035021433655324600253070ustar00rootroot00000000000000// Package bytewriter implements writers that support concurrent writing within a fixed length block // // initially tried to use bytes.Buffer but the main restriction with that is that // it does not allow the freedom to move around in the buffer. Further, it always // writes at the end of the buffer // // another attempt was to maintain a position identifier that was always passed // to any function that wrote anything and the function *had* to return the // next writable location, which resulted in calls like // // ```go // pos = writeString(buffer, pos, "mmv") // ``` // // which became unmaintainable after a while, and along with all the side // maintainance looked extremely ugly // // then implemented a minimal buffer wrapper that gives the freedom // to move around and write anywhere you want, which worked if you only need // one position identifier, i.e. only one write operation happens at a time // // this implements a writer that supports multiple concurrent writes within a fixed length block package bytewriter // Writer defines an abstraction for an object that allows writing of binary // values anywhere within a fixed range type Writer interface { Bytes() []byte Len() int Write([]byte, int) (int, error) WriteVal(interface{}, int) (int, error) WriteString(string, int) (int, error) WriteInt32(int32, int) (int, error) WriteInt64(int64, int) (int, error) WriteUint32(uint32, int) (int, error) WriteUint64(uint64, int) (int, error) WriteFloat32(float32, int) (int, error) WriteFloat64(float64, int) (int, error) MustWrite([]byte, int) int MustWriteVal(interface{}, int) int MustWriteString(string, int) int MustWriteInt32(int32, int) int MustWriteInt64(int64, int) int MustWriteUint32(uint32, int) int MustWriteUint64(uint64, int) int MustWriteFloat32(float32, int) int MustWriteFloat64(float64, int) int } golang-github-performancecopilot-speed-4.0.0/client.go000066400000000000000000000401201433655324600230460ustar00rootroot00000000000000package speed import ( "os" "path/filepath" "strings" "sync" "time" "github.com/pkg/errors" "github.com/performancecopilot/speed/v4/bytewriter" ) // byte lengths of different components in an mmv file const ( HeaderLength = 40 TocLength = 16 Metric1Length = 104 Metric2Length = 48 ValueLength = 32 Instance1Length = 80 Instance2Length = 24 InstanceDomainLength = 32 StringLength = 256 ) // MaxV1NameLength is the maximum length for a metric/instance name // under MMV format 1 const MaxV1NameLength = 63 // MaxDataValueSize is the maximum byte length for a stored metric value, unless it is a string const MaxDataValueSize = 16 // EraseFileOnStop if set to true, will also delete the memory mapped file var EraseFileOnStop = false // Client defines the interface for a type that can talk to an instrumentation agent type Client interface { // a client must contain a registry of metrics Registry() Registry // starts monitoring Start() error // Start that will panic on failure MustStart() // stop monitoring Stop() error // Stop that will panic on failure MustStop() // adds a metric to be monitored Register(Metric) error // tries to add a metric to be written and panics on error MustRegister(Metric) // adds metric from a string RegisterString(string, interface{}, MetricType, MetricSemantics, MetricUnit) (Metric, error) // tries to add a metric from a string and panics on an error MustRegisterString(string, interface{}, MetricType, MetricSemantics, MetricUnit) Metric } /////////////////////////////////////////////////////////////////////////////// func mmvFileLocation(name string) (string, error) { if strings.ContainsRune(name, os.PathSeparator) { return "", errors.New("name cannot have path separator") } tdir, present := config["PCP_TMP_DIR"] var loc string if present { loc = filepath.Join(rootPath, tdir) } else { loc = os.TempDir() } return filepath.Join(loc, "mmv", name), nil } // PCPClusterIDBitLength is the bit length of the cluster id // for a set of PCP metrics const PCPClusterIDBitLength = 12 // MMVFlag represents an enumerated type to represent mmv flag values type MMVFlag int // values for MMVFlag const ( NoPrefixFlag MMVFlag = 1 << iota ProcessFlag SentinelFlag ) //go:generate stringer -type=MMVFlag // PCPClient implements a client that can generate instrumentation for PCP type PCPClient struct { mutex sync.Mutex loc string // absolute location of the mmv file clusterID uint32 // cluster identifier for the writer flag MMVFlag // write flag r *PCPRegistry // current registry writer bytewriter.Writer instanceoffsetc chan int indomoffsetc chan int metricoffsetc chan int valueoffsetc chan int stringoffsetc chan int } // NewPCPClient initializes a new PCPClient object func NewPCPClient(name string) (*PCPClient, error) { return NewPCPClientWithRegistry(name, NewPCPRegistry()) } // NewPCPClientWithRegistry initializes a new PCPClient object with the given registry func NewPCPClientWithRegistry(name string, registry *PCPRegistry) (*PCPClient, error) { fileLocation, err := mmvFileLocation(name) if err != nil { return nil, errors.Wrap(err, "could not get a location for storing MMV file") } return &PCPClient{ loc: fileLocation, r: registry, clusterID: hash(name, PCPClusterIDBitLength), flag: ProcessFlag, }, nil } // Registry returns a writer's registry func (c *PCPClient) Registry() Registry { return c.r } // SetFlag sets the MMVflag for the client func (c *PCPClient) SetFlag(flag MMVFlag) error { c.mutex.Lock() defer c.mutex.Unlock() if c.r.mapped { return errors.New("cannot set mmv flag for an active client") } c.flag = flag return nil } func (c *PCPClient) tocCount() int { ans := 2 if c.r.InstanceCount() > 0 { ans += 2 } if c.r.StringCount() > 0 { ans++ } return ans } // Length returns the byte length of data in the mmv file written by the current writer func (c *PCPClient) Length() int { var ( InstanceLength = Instance1Length MetricLength = Metric1Length ) if c.r.version2 { InstanceLength = Instance2Length MetricLength = Metric2Length } return HeaderLength + (c.tocCount() * TocLength) + (c.r.InstanceCount() * InstanceLength) + (c.r.InstanceDomainCount() * InstanceDomainLength) + (c.r.MetricCount() * MetricLength) + (c.r.ValuesCount() * ValueLength) + (c.r.StringCount() * StringLength) } // Start dumps existing registry data func (c *PCPClient) Start() error { c.mutex.Lock() defer c.mutex.Unlock() l := c.Length() writer, err := bytewriter.NewMemoryMappedWriter(c.loc, l) if err != nil { return errors.Wrap(err, "cannot create MemoryMappedBuffer in client") } c.writer = writer c.start() c.r.mapped = true return nil } func (c *PCPClient) start() { var ( InstanceLength = Instance1Length MetricLength = Metric1Length ) if c.r.version2 { InstanceLength = Instance2Length MetricLength = Metric2Length } c.r.indomoffset = HeaderLength + TocLength*c.tocCount() c.r.instanceoffset = c.r.indomoffset + InstanceDomainLength*c.r.InstanceDomainCount() c.r.metricsoffset = c.r.instanceoffset + InstanceLength*c.r.InstanceCount() c.r.valuesoffset = c.r.metricsoffset + MetricLength*c.r.MetricCount() c.r.stringsoffset = c.r.valuesoffset + ValueLength*c.r.ValuesCount() if c.r.InstanceDomainCount() > 0 { c.instanceoffsetc, c.indomoffsetc = make(chan int, 1), make(chan int, 1) c.instanceoffsetc <- c.r.instanceoffset c.indomoffsetc <- c.r.indomoffset } if c.r.MetricCount() > 0 { c.metricoffsetc, c.valueoffsetc = make(chan int, 1), make(chan int, 1) c.metricoffsetc <- c.r.metricsoffset c.valueoffsetc <- c.r.valuesoffset } if c.r.StringCount() > 0 { c.stringoffsetc = make(chan int, 1) c.stringoffsetc <- c.r.stringsoffset } genc, g2offc := make(chan int64), make(chan int) go c.writeHeaderBlock(genc, g2offc) var wg sync.WaitGroup wg.Add(2) go func() { c.writeTocBlock() wg.Done() }() go func() { // instance domains **have** to be written before metrics // as metrics need instance offsets and multiple metrics // can have the same indom, so they need to be cached c.writeInstanceDomains() c.writeMetrics() wg.Done() }() gen, g2off := <-genc, <-g2offc wg.Wait() // must *always* be the last thing to happen _ = c.writer.MustWriteInt64(gen, g2off) } func (c *PCPClient) writeHeaderBlock(genc chan int64, g2offc chan int) { // tag c.writer.MustWriteString("MMV", 0) var pos int // version if c.r.version2 { pos = c.writer.MustWriteUint32(2, 4) } else { pos = c.writer.MustWriteUint32(1, 4) } // generation gen := time.Now().Unix() pos = c.writer.MustWriteInt64(gen, pos) g2off := pos pos = c.writer.MustWriteInt64(0, pos) // tocCount pos = c.writer.MustWriteInt32(int32(c.tocCount()), pos) // flag mask pos = c.writer.MustWriteInt32(int32(c.flag), pos) // process identifier pos = c.writer.MustWriteInt32(int32(os.Getpid()), pos) // cluster identifier _ = c.writer.MustWriteUint32(c.clusterID, pos) // NOTE: the order here is important, should be same as in start() // or deadlock genc <- gen g2offc <- g2off } func (c *PCPClient) writeTocBlock() { var wg sync.WaitGroup tocpos := HeaderLength wg.Add(c.tocCount()) // instance domains toc if c.r.InstanceDomainCount() > 0 { go func(pos int) { // 1 is the identifier for instance domains c.writeSingleToc(pos, 1, c.r.InstanceDomainCount(), c.r.indomoffset) wg.Done() }(tocpos) tocpos += TocLength } // instances toc if c.r.InstanceCount() > 0 { go func(pos int) { // 2 is the identifier for instances c.writeSingleToc(pos, 2, c.r.InstanceCount(), c.r.instanceoffset) wg.Done() }(tocpos) tocpos += TocLength } // metrics and values toc metricsoffset, valuesoffset := c.r.metricsoffset, c.r.valuesoffset if c.r.MetricCount() == 0 { metricsoffset, valuesoffset = 0, 0 } go func(pos int) { // 3 is the identifier for metrics c.writeSingleToc(pos, 3, c.r.MetricCount(), metricsoffset) wg.Done() }(tocpos) tocpos += TocLength go func(pos int) { // 4 is the identifier for values c.writeSingleToc(pos, 4, c.r.ValuesCount(), valuesoffset) wg.Done() }(tocpos) tocpos += TocLength // strings toc if c.r.StringCount() > 0 { go func(pos int) { // 5 is the identifier for strings c.writeSingleToc(pos, 5, c.r.StringCount(), c.r.stringsoffset) wg.Done() }(tocpos) } wg.Wait() } func (c *PCPClient) writeSingleToc(pos, identifier, count, offset int) { pos = c.writer.MustWriteInt32(int32(identifier), pos) pos = c.writer.MustWriteInt32(int32(count), pos) _ = c.writer.MustWriteUint64(uint64(offset), pos) } func (c *PCPClient) writeInstanceDomains() { var wg sync.WaitGroup wg.Add(c.r.InstanceDomainCount()) for _, indom := range c.r.instanceDomains { go func(indom *PCPInstanceDomain) { c.writeInstanceDomain(indom) wg.Done() }(indom) } wg.Wait() } func (c *PCPClient) writeInstanceDomain(indom *PCPInstanceDomain) { off := <-c.indomoffsetc c.indomoffsetc <- off + InstanceDomainLength InstanceLength := Instance1Length if c.r.version2 { InstanceLength = Instance2Length } inoff := off ioff := <-c.instanceoffsetc c.instanceoffsetc <- ioff + InstanceLength*indom.InstanceCount() var wg sync.WaitGroup wg.Add(indom.InstanceCount()) off = c.writer.MustWriteUint32(indom.id, off) off = c.writer.MustWriteInt32(int32(indom.InstanceCount()), off) off = c.writer.MustWriteInt64(int64(ioff), off) for _, i := range indom.instances { go func(i *pcpInstance, offset int) { c.writeInstance(i, inoff, offset) wg.Done() }(i, ioff) ioff += InstanceLength } so, lo := 0, 0 if indom.shortDescription != "" { so = <-c.stringoffsetc c.stringoffsetc <- so + StringLength c.writer.MustWriteString(indom.shortDescription, so) } if indom.longDescription != "" { lo = <-c.stringoffsetc c.stringoffsetc <- lo + StringLength c.writer.MustWriteString(indom.longDescription, lo) } off = c.writer.MustWriteUint64(uint64(so), off) _ = c.writer.MustWriteUint64(uint64(lo), off) wg.Wait() } func (c *PCPClient) writeInstance(i *pcpInstance, indomoff int, off int) { i.offset = off off = c.writer.MustWriteInt64(int64(indomoff), off) off = c.writer.MustWriteInt32(0, off) off = c.writer.MustWriteUint32(i.id, off) if c.r.version2 { soff := <-c.stringoffsetc c.stringoffsetc <- soff + StringLength c.writer.MustWriteUint64(uint64(soff), off) c.writer.MustWriteString(i.name, soff) } else { c.writer.MustWriteString(i.name, off) } } func (c *PCPClient) writeMetrics() { var wg sync.WaitGroup launchSingletonMetric := func(metric *pcpSingletonMetric) { go func() { c.writeSingletonMetric(metric) wg.Done() }() } launchInstanceMetric := func(metric *pcpInstanceMetric) { go func() { c.writeInstanceMetric(metric) wg.Done() }() } wg.Add(c.r.MetricCount()) for _, m := range c.r.metrics { switch metric := m.(type) { case *PCPSingletonMetric: launchSingletonMetric(metric.pcpSingletonMetric) case *PCPCounter: launchSingletonMetric(metric.pcpSingletonMetric) case *PCPGauge: launchSingletonMetric(metric.pcpSingletonMetric) case *PCPTimer: launchSingletonMetric(metric.pcpSingletonMetric) case *PCPInstanceMetric: launchInstanceMetric(metric.pcpInstanceMetric) case *PCPCounterVector: launchInstanceMetric(metric.pcpInstanceMetric) case *PCPGaugeVector: launchInstanceMetric(metric.pcpInstanceMetric) case *PCPHistogram: launchInstanceMetric(metric.pcpInstanceMetric) } } wg.Wait() } func (c *PCPClient) writeSingletonMetric(m *pcpSingletonMetric) { var wg sync.WaitGroup wg.Add(2) doff := <-c.metricoffsetc go func() { c.writeMetricDesc(m.pcpMetricDesc, m.Indom(), doff) wg.Done() }() off := <-c.valueoffsetc c.valueoffsetc <- off + ValueLength go func(offset int) { m.update = c.writeValue(m.t, m.val, offset) wg.Done() }(off) off = c.writer.MustWriteInt64(int64(doff), off+MaxDataValueSize) _ = c.writer.MustWriteInt64(0, off) wg.Wait() } func (c *PCPClient) writeInstanceMetric(m *pcpInstanceMetric) { var wg sync.WaitGroup wg.Add(1 + m.Indom().InstanceCount()) doff := <-c.metricoffsetc go func() { c.writeMetricDesc(m.pcpMetricDesc, m.Indom(), doff) wg.Done() }() for name, i := range m.indom.instances { off := <-c.valueoffsetc c.valueoffsetc <- off + ValueLength go func(i *instanceValue, offset int) { i.update = c.writeValue(m.t, i.val, offset) wg.Done() }(m.vals[name], off) off = c.writer.MustWriteInt64(int64(doff), off+MaxDataValueSize) _ = c.writer.MustWriteInt64(int64(i.offset), off) } wg.Wait() } func (c *PCPClient) writeMetricDesc(desc *pcpMetricDesc, indom *PCPInstanceDomain, off int) { if c.r.version2 { c.metricoffsetc <- off + Metric2Length noff := <-c.stringoffsetc c.stringoffsetc <- noff + StringLength off = c.writer.MustWriteUint64(uint64(noff), off) c.writer.MustWriteString(desc.name, noff) } else { c.metricoffsetc <- off + Metric1Length c.writer.MustWriteString(desc.name, off) off += MaxV1NameLength + 1 } off = c.writer.MustWriteUint32(desc.id, off) off = c.writer.MustWriteInt32(int32(desc.t), off) off = c.writer.MustWriteInt32(int32(desc.sem), off) off = c.writer.MustWriteUint32(desc.u.PMAPI(), off) if indom != nil { off = c.writer.MustWriteUint32(indom.ID(), off) } else { off = c.writer.MustWriteInt32(-1, off) } off = c.writer.MustWriteInt32(0, off) so, lo := 0, 0 if desc.shortDescription != "" { so = <-c.stringoffsetc c.stringoffsetc <- so + StringLength c.writer.MustWriteString(desc.shortDescription, so) } if desc.longDescription != "" { lo = <-c.stringoffsetc c.stringoffsetc <- lo + StringLength c.writer.MustWriteString(desc.longDescription, lo) } off = c.writer.MustWriteUint64(uint64(so), off) _ = c.writer.MustWriteUint64(uint64(lo), off) } func (c *PCPClient) writeValue(t MetricType, val interface{}, offset int) updateClosure { if t == StringType { pos := c.writer.MustWriteUint64(StringLength-1, offset) offset = <-c.stringoffsetc c.stringoffsetc <- offset + StringLength c.writer.MustWriteUint64(uint64(offset), pos) } update := newupdateClosure(offset, c.writer) _ = update(val) return update } // MustStart is a start that panics func (c *PCPClient) MustStart() { if err := c.Start(); err != nil { panic(err) } } // Stop removes existing mapping and cleans up func (c *PCPClient) Stop() error { c.mutex.Lock() defer c.mutex.Unlock() if !c.r.mapped { return errors.New("trying to stop an already stopped mapping") } c.stop() c.r.mapped = false err := c.writer.(*bytewriter.MemoryMappedWriter).Unmap(EraseFileOnStop) c.writer = nil if err != nil { return errors.Wrap(err, "client: error unmapping MemoryMappedBuffer") } return nil } func (c *PCPClient) stop() { c.instanceoffsetc, c.indomoffsetc = nil, nil c.metricoffsetc, c.valueoffsetc = nil, nil c.stringoffsetc = nil } // MustStop is a stop that panics func (c *PCPClient) MustStop() { if err := c.Stop(); err != nil { panic(err) } } // Register is simply a shorthand for Registry().AddMetric func (c *PCPClient) Register(m Metric) error { return c.r.AddMetric(m) } // MustRegister is simply a Register that can panic func (c *PCPClient) MustRegister(m Metric) { if err := c.Register(m); err != nil { panic(err) } } // RegisterIndom is simply a shorthand for Registry().AddInstanceDomain func (c *PCPClient) RegisterIndom(indom InstanceDomain) error { return c.r.AddInstanceDomain(indom) } // MustRegisterIndom is simply a RegisterIndom that can panic func (c *PCPClient) MustRegisterIndom(indom InstanceDomain) { if err := c.RegisterIndom(indom); err != nil { panic(err) } } // RegisterString is simply a shorthand for Registry().AddMetricByString func (c *PCPClient) RegisterString(str string, val interface{}, t MetricType, s MetricSemantics, u MetricUnit) (Metric, error) { return c.r.AddMetricByString(str, val, t, s, u) } // MustRegisterString is simply a RegisterString that panics func (c *PCPClient) MustRegisterString(str string, val interface{}, t MetricType, s MetricSemantics, u MetricUnit) Metric { if m, err := c.RegisterString(str, val, t, s, u); err != nil { panic(err) } else { return m } } golang-github-performancecopilot-speed-4.0.0/client_test.go000066400000000000000000001007311433655324600241120ustar00rootroot00000000000000package speed import ( "fmt" "math" "os" "testing" "time" "github.com/HdrHistogram/hdrhistogram-go" "github.com/performancecopilot/speed/v4/mmvdump" ) func TestMmvFileLocation(t *testing.T) { l, present := config["PCP_TMP_DIR"] if present { loc, _ := mmvFileLocation("test") expected := fmt.Sprintf("%v%cmmv%c%v", l, os.PathSeparator, os.PathSeparator, "test") if loc != expected { t.Errorf("location not expected value, expected %v, got %v", expected, loc) } } delete(config, "PCP_TMP_DIR") loc, _ := mmvFileLocation("test") expected := fmt.Sprintf("%v%cmmv%c%v", os.TempDir(), os.PathSeparator, os.PathSeparator, "test") if loc != expected { t.Errorf("location not expected value, expected %v, got %v", expected, loc) } if present { config["PCP_TMP_DIR"] = l } loc, err := mmvFileLocation(fmt.Sprintf("%v%c", "test", os.PathSeparator)) if err == nil { t.Errorf("expected error, instead got path %v", loc) } } func TestTocCountAndLength(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Errorf("cannot create writer, error: %v", err) } if c.tocCount() != 2 { t.Errorf("expected tocCount to be 2, got %v", c.tocCount()) } expectedLength := HeaderLength + 2*TocLength if c.Length() != expectedLength { t.Errorf("expected Length to be %v, got %v", expectedLength, c.Length()) } m, err := NewPCPSingletonMetric(10, "test", Int32Type, CounterSemantics, OneUnit, "test") if err != nil { t.Error("Cannot create a metric") } c.MustRegister(m) if c.tocCount() != 3 { t.Errorf("expected tocCount to be 3, got %v", c.tocCount()) } var MetricLength = Metric1Length if c.r.version2 { MetricLength = Metric2Length } expectedLength = HeaderLength + 3*TocLength + 1*MetricLength + 1*ValueLength + 1*StringLength if c.Length() != expectedLength { t.Errorf("expected Length to be %v, got %v", expectedLength, c.Length()) } indom, _ := NewPCPInstanceDomain("testindom", []string{"test"}) c.MustRegisterIndom(indom) m2, err := NewPCPInstanceMetric( Instances{ "test": 1, }, "test2", indom, Int32Type, CounterSemantics, OneUnit, ) if err != nil { t.Error("Cannot create a metric") } c.MustRegister(m2) if c.tocCount() != 5 { t.Errorf("expected tocCount to be 5, got %v", c.tocCount()) } } func TestMapping(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Fatal("Cannot create client") } _, err = c.RegisterString("test.1", 2, Int32Type, CounterSemantics, OneUnit) if err != nil { t.Error("Cannot Register") } c.MustStart() loc, _ := mmvFileLocation("test") if _, err = os.Stat(loc); err != nil { t.Error("expected a MMV file to be created on startup") } _, err = c.RegisterString("test.2", 2, Int32Type, CounterSemantics, OneUnit) if err == nil { t.Error("expected registration to fail when a mapping is active") } EraseFileOnStop = true err = c.Stop() if err != nil { t.Errorf("Cannot stop a mapping, error: %v", err) } if _, err = os.Stat(loc); err == nil { t.Error("expected the MMV file be deleted after stopping") } EraseFileOnStop = false } func findMetric(metric Metric, metrics map[uint64]mmvdump.Metric) (uint64, mmvdump.Metric) { for off, m := range metrics { if uint32(m.Item()) == metric.ID() { return off, m } } return 0, nil } func findSingletonValue(off uint64, values map[uint64]*mmvdump.Value) (uint64, *mmvdump.Value) { for voff, v := range values { if uint64(v.Metric) == off { return voff, v } } return 0, nil } func findInstanceValue(off, ioff uint64, values map[uint64]*mmvdump.Value) (uint64, *mmvdump.Value) { for voff, v := range values { if uint64(v.Metric) == off && uint64(v.Instance) == ioff { return voff, v } } return 0, nil } func findInstanceDomain(indom *PCPInstanceDomain, indoms map[uint64]*mmvdump.InstanceDomain) (uint64, *mmvdump.InstanceDomain) { for off, in := range indoms { if in.Serial == indom.id { return off, in } } return 0, nil } func matchString(s string, str *mmvdump.String, t *testing.T) { sv := string(str.Payload[:len(s)]) if sv != s { t.Errorf("expected %v, got %v", s, sv) } } func matchName(n string, name [64]byte, t *testing.T) { if s := name[:len(n)]; n != string(s) { t.Errorf("expected name to be %v, got %v", n, s) } } func matchMetricDesc(desc *pcpMetricDesc, metric mmvdump.Metric, strings map[uint64]*mmvdump.String, t *testing.T) { switch m := metric.(type) { case *mmvdump.Metric1: matchName(desc.name, m.Name, t) case *mmvdump.Metric2: matchString(desc.name, strings[m.Name], t) } if int32(metric.Sem()) != int32(desc.sem) { t.Errorf("expected semantics to be %v, got %v", desc.sem, MetricSemantics(metric.Sem())) } if int32(metric.Typ()) != int32(desc.t) { t.Errorf("expected type to be %v, got %v", desc.t, MetricType(metric.Typ())) } if int32(metric.Unit()) != int32(desc.u.PMAPI()) { t.Errorf("expected unit to be %v, got %v", desc.u, metric.Unit()) } if metric.ShortText() != 0 { matchString(desc.shortDescription, strings[metric.ShortText()], t) } else if desc.shortDescription != "" { t.Error("expected short description to be \"\"") } if metric.LongText() != 0 { matchString(desc.longDescription, strings[metric.LongText()], t) } else if desc.longDescription != "" { t.Error("expected long description to be \"\"") } } func matchSingletonMetric(m *pcpSingletonMetric, metric mmvdump.Metric, strings map[uint64]*mmvdump.String, t *testing.T) { if metric.Indom() != mmvdump.NoIndom { t.Error("expected indom to be null") } matchMetricDesc(m.pcpMetricDesc, metric, strings, t) } func matchSingletonValue(m *pcpSingletonMetric, value *mmvdump.Value, metrics map[uint64]mmvdump.Metric, strings map[uint64]*mmvdump.String, t *testing.T) { off, met := findMetric(m, metrics) if met == nil { t.Errorf("expected to find metric with name %v", m.Name()) return } if value.Metric != off { t.Errorf("expected value's metric to be at %v", off) } if m.t == StringType { matchString(m.val.(string), strings[uint64(value.Extra)], t) } else { if av, err := mmvdump.FixedVal(value.Val, mmvdump.Type(m.t)); err != nil || av != m.val { t.Errorf("expected the value to be %v, got %v", m.val, av) } } if value.Instance != 0 { t.Errorf("expected value instance to be 0") } } func matchInstanceMetric(m *pcpInstanceMetric, met mmvdump.Metric, strings map[uint64]*mmvdump.String, t *testing.T) { if uint32(met.Indom()) != m.indom.id { t.Errorf("expected indom id to be %d, got %d", m.indom.id, met.Indom()) } matchMetricDesc(m.pcpMetricDesc, met, strings, t) } func matchInstanceValue(v *mmvdump.Value, i *instanceValue, ins string, met *pcpInstanceMetric, metrics map[uint64]mmvdump.Metric, strings map[uint64]*mmvdump.String, t *testing.T) { if v.Instance == 0 { t.Errorf("expected instance offset to not be 0") } if met.indom == nil { t.Errorf("expected indom to be non nil") } else if in := met.indom.instances[ins]; in == nil { t.Errorf("expected the instance domain to have an instance %v", ins) } else if in.offset != int(v.Instance) { t.Errorf("expected the value's instance to be at offset %v, found at %v", in.offset, v.Instance) } if met.t == StringType { matchString(i.val.(string), strings[uint64(v.Extra)], t) } else { if av, err := mmvdump.FixedVal(v.Val, mmvdump.Type(met.t)); err != nil || av != i.val { t.Errorf("expected the value to be %v, got %v", i.val, av) } } } func matchSingletonMetricAndValue(met *pcpSingletonMetric, metrics map[uint64]mmvdump.Metric, values map[uint64]*mmvdump.Value, strings map[uint64]*mmvdump.String, t *testing.T) { off, metric := findMetric(met, metrics) if metric == nil { t.Errorf("expected a metric of name %v", met.name) return } matchSingletonMetric(met, metric, strings, t) _, mv := findSingletonValue(off, values) if mv == nil { t.Errorf("expected a value for metric %v", met.name) } else { matchSingletonValue(met, mv, metrics, strings, t) } } func matchInstanceMetricAndValues(met *pcpInstanceMetric, metrics map[uint64]mmvdump.Metric, values map[uint64]*mmvdump.Value, instances map[uint64]mmvdump.Instance, strings map[uint64]*mmvdump.String, t *testing.T) { _, metric := findMetric(met, metrics) if metric == nil { t.Errorf("expected a metric of name %v", met.name) return } matchInstanceMetric(met, metric, strings, t) for n, i := range met.indom.instances { off, _ := findMetric(met, metrics) _, mv := findInstanceValue(off, uint64(i.offset), values) if mv == nil { t.Errorf("expected a value at offset %v", i.offset) } else { matchInstanceValue(mv, met.vals[n], n, met, metrics, strings, t) } } } func matchMetricAndValue(m Metric, metrics map[uint64]mmvdump.Metric, values map[uint64]*mmvdump.Value, instances map[uint64]mmvdump.Instance, strings map[uint64]*mmvdump.String, c *PCPClient, t *testing.T) { switch met := m.(type) { case *PCPSingletonMetric: matchSingletonMetricAndValue(met.pcpSingletonMetric, metrics, values, strings, t) case *PCPInstanceMetric: matchInstanceMetricAndValues(met.pcpInstanceMetric, metrics, values, instances, strings, t) case *PCPCounter: matchSingletonMetricAndValue(met.pcpSingletonMetric, metrics, values, strings, t) case *PCPGauge: matchSingletonMetricAndValue(met.pcpSingletonMetric, metrics, values, strings, t) case *PCPTimer: matchSingletonMetricAndValue(met.pcpSingletonMetric, metrics, values, strings, t) case *PCPCounterVector: matchInstanceMetricAndValues(met.pcpInstanceMetric, metrics, values, instances, strings, t) case *PCPGaugeVector: matchInstanceMetricAndValues(met.pcpInstanceMetric, metrics, values, instances, strings, t) case *PCPHistogram: matchInstanceMetricAndValues(met.pcpInstanceMetric, metrics, values, instances, strings, t) } } func matchMetricsAndValues(metrics map[uint64]mmvdump.Metric, values map[uint64]*mmvdump.Value, instances map[uint64]mmvdump.Instance, strings map[uint64]*mmvdump.String, c *PCPClient, t *testing.T) { if c.Registry().MetricCount() != len(metrics) { t.Errorf("expected %v metrics, got %v", c.Registry().MetricCount(), len(metrics)) } if c.Registry().ValuesCount() != len(values) { t.Errorf("expected %v values, got %v", c.Registry().ValuesCount(), len(values)) } for _, m := range c.r.metrics { matchMetricAndValue(m, metrics, values, instances, strings, c, t) } } func TestWritingSingletonMetric(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Error(err) return } met, err := NewPCPSingletonMetric(10, "test.1", Int32Type, CounterSemantics, OneUnit, "test") if err != nil { t.Error(err) return } c.MustRegister(met) c.MustStart() defer c.MustStop() h, toc, m, v, i, ind, s, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Error(err) return } if int(h.Toc) != len(toc) { t.Errorf("expected the number of tocs specified in the header and the number of tocs in the toc array to be the same, h.Toc = %d, len(tocs) = %d", h.Toc, len(toc)) } if h.Toc != 3 { t.Errorf("expected client to write %d tocs, written %d", 3, h.Toc) } if h.Flag != int32(ProcessFlag) { t.Errorf("expected client to write a ProcessFlag, writing %v", MMVFlag(h.Flag)) } matchMetricsAndValues(m, v, i, s, c, t) // strings if len(s) != 1 { t.Error("expected one string") } _, me := findMetric(met, m) matchString(met.shortDescription, s[me.ShortText()], t) // instances if len(i) != 0 { t.Error("expected no instances when writing a singleton metric") } // indoms if len(ind) != 0 { t.Error("expected no indoms when writing a singleton metric") } } func TestUpdatingSingletonMetric(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Error(err) return } m := c.MustRegisterString("met.1", 10, Int32Type, CounterSemantics, OneUnit) c.MustStart() defer c.MustStop() _, _, metrics, values, instances, _, strings, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Fatal("Cannot extract dump from the writer buffer") } matchMetricsAndValues(metrics, values, instances, strings, c, t) if m.(SingletonMetric).Val().(int32) != 10 { t.Errorf("expected metric value to be 10") } m.(SingletonMetric).MustSet(42) _, _, metrics, values, instances, _, strings, err = mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Errorf("cannot get dump, error: %v", err) } matchMetricsAndValues(metrics, values, instances, strings, c, t) if m.(SingletonMetric).Val().(int32) != 42 { t.Errorf("expected metric value to be 42") } } func matchInstance(i mmvdump.Instance, pi *pcpInstance, id *PCPInstanceDomain, indoms map[uint64]*mmvdump.InstanceDomain, strings map[uint64]*mmvdump.String, t *testing.T) { off, _ := findInstanceDomain(id, indoms) if i.Indom() != off { t.Errorf("expected indom offset to be %d, got %d", i.Indom(), off) } switch ins := i.(type) { case *mmvdump.Instance1: matchName(pi.name, ins.External, t) case *mmvdump.Instance2: matchString(pi.name, strings[ins.External], t) } } func matchInstanceDomain(id *mmvdump.InstanceDomain, pid *PCPInstanceDomain, strings map[uint64]*mmvdump.String, t *testing.T) { if pid.InstanceCount() != int(id.Count) { t.Errorf("expected %d instances in instance domain %v, got %d", pid.InstanceCount(), pid.Name(), id.Count) } if id.Shorttext != 0 { matchString(pid.shortDescription, strings[id.Shorttext], t) } if id.Longtext != 0 { matchString(pid.longDescription, strings[id.Longtext], t) } } func matchInstancesAndInstanceDomains( ins map[uint64]mmvdump.Instance, ids map[uint64]*mmvdump.InstanceDomain, ss map[uint64]*mmvdump.String, c *PCPClient, t *testing.T, ) { if len(ins) != c.r.InstanceCount() { t.Errorf("expected %d instances, got %d", c.r.InstanceCount(), len(ins)) } for _, id := range c.r.instanceDomains { _, ind := findInstanceDomain(id, ids) if ind == nil { t.Errorf("expected an instance domain of name %v", id.name) } else { matchInstanceDomain(ind, id, ss, t) } for _, i := range id.instances { if ioff := uint64(i.offset); ins[ioff] == nil { t.Errorf("expected an instance domain at %d", ioff) } else { matchInstance(ins[ioff], i, id, ids, ss, t) } } } } func TestWritingInstanceMetric(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Error(err) return } id, err := NewPCPInstanceDomain("testid", []string{"a", "b", "c"}, "testid") if err != nil { t.Error(err) return } c.MustRegisterIndom(id) m, err := NewPCPInstanceMetric(Instances{ "a": 1, "b": 2, "c": 3, }, "test.1", id, Uint32Type, CounterSemantics, OneUnit, "", "test long description") if err != nil { t.Error(err) return } c.MustRegister(m) c.MustStart() defer c.MustStop() h, tocs, mets, vals, ins, ids, ss, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Error(err) return } if int(h.Toc) != len(tocs) { t.Errorf("expected the number of tocs specified in the header and the number of tocs in the toc array to be the same, h.Toc = %d, len(tocs) = %d", h.Toc, len(tocs)) } if h.Toc != 5 { t.Errorf("expected client to write %d tocs, written %d", 5, h.Toc) } if h.Flag != int32(ProcessFlag) { t.Errorf("expected client to write a ProcessFlag, writing %v", MMVFlag(h.Flag)) } matchMetricsAndValues(mets, vals, ins, ss, c, t) matchInstancesAndInstanceDomains(ins, ids, ss, c, t) // strings _, ind := findInstanceDomain(id, ids) matchString(id.shortDescription, ss[ind.Shorttext], t) _, me := findMetric(m, mets) matchString(m.longDescription, ss[me.LongText()], t) } func TestUpdatingInstanceMetric(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Error(err) return } m := c.MustRegisterString("met[a, b].1", Instances{"a": 21, "b": 42}, Int32Type, CounterSemantics, OneUnit) c.MustStart() defer c.MustStop() _, _, metrics, values, instances, indoms, strings, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Errorf("cannot get dump, error: %v", err) } matchMetricsAndValues(metrics, values, instances, strings, c, t) matchInstancesAndInstanceDomains(instances, indoms, strings, c, t) im := m.(InstanceMetric) valmatcher := func(v interface{}, val int32, err error, ins string) { if err != nil { t.Errorf("cannot retrieve instance a value, error: %v", err) return } if v.(int32) != val { t.Errorf("expected instance %v's value to be %v", ins, val) } } v, err := im.ValInstance("a") valmatcher(v, 21, err, "a") v, err = im.ValInstance("b") valmatcher(v, 42, err, "b") im.MustSetInstance(63, "a") im.MustSetInstance(84, "b") _, _, metrics, values, instances, indoms, strings, err = mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Errorf("cannot get dump, error: %v", err) } matchMetricsAndValues(metrics, values, instances, strings, c, t) matchInstancesAndInstanceDomains(instances, indoms, strings, c, t) v, err = im.ValInstance("a") valmatcher(v, 63, err, "a") v, err = im.ValInstance("b") valmatcher(v, 84, err, "b") } func TestStringValueWriting(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Error(err) return } metric := c.MustRegisterString("test.str", "kirk", StringType, CounterSemantics, OneUnit) c.MustStart() defer c.MustStop() h, _, m, v, _, _, s, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Error(err) return } if h.Toc != 3 { t.Errorf("expected toc to be 3, not %v", h.Toc) } sm := metric.(*PCPSingletonMetric) off, _ := findMetric(sm, m) voff, val := findSingletonValue(off, v) if val == nil { t.Errorf("expected value at %v", voff) } else { add := uint64(val.Extra) if str, ok := s[add]; !ok { t.Errorf("expected a string at address %v", add) } else { if v := string(str.Payload[:4]); v != "kirk" { t.Errorf("expected metric value to be kirk, not %v", v) } } } sm.MustSet("spock") _, _, _, v, _, _, s, err = mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Error(err) return } _, val = findSingletonValue(off, v) add := uint64(val.Extra) if str, ok := s[add]; !ok { t.Errorf("expected a string at address %v", add) } else { if v := string(str.Payload[:5]); v != "spock" { t.Errorf("expected metric value to be spock, not %v", v) } } } func TestWritingDifferentSemantics(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Errorf("cannot create client: %v", err) return } c.MustRegisterString("m.1", 10, Int32Type, NoSemantics, OneUnit) c.MustRegisterString("m.2", 10, Int32Type, CounterSemantics, OneUnit) c.MustRegisterString("m.3", 10, Int32Type, InstantSemantics, OneUnit) c.MustRegisterString("m.4", 10, Int32Type, DiscreteSemantics, OneUnit) c.MustRegisterString("m[a, b].5", Instances{"a": 1, "b": 2}, Int32Type, NoSemantics, OneUnit) c.MustRegisterString("m[a, b].6", Instances{"a": 3, "b": 4}, Int32Type, CounterSemantics, OneUnit) c.MustRegisterString("m[a, b].7", Instances{"a": 5, "b": 6}, Int32Type, InstantSemantics, OneUnit) c.MustRegisterString("m[a, b].8", Instances{"a": 7, "b": 8}, Int32Type, DiscreteSemantics, OneUnit) c.MustStart() defer c.MustStop() _, _, metrics, values, instances, indoms, strings, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Errorf("cannot create dump: %v", err) } matchMetricsAndValues(metrics, values, instances, strings, c, t) matchInstancesAndInstanceDomains(instances, indoms, strings, c, t) } func TestWritingDifferentUnits(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Errorf("cannot create client: %v", err) return } c.MustRegisterString("m.0", 10, Uint64Type, CounterSemantics, NanosecondUnit) c.MustRegisterString("m.1", 10, Uint64Type, CounterSemantics, MicrosecondUnit) c.MustRegisterString("m.2", 10, Uint64Type, CounterSemantics, MillisecondUnit) c.MustRegisterString("m.3", 10, Uint64Type, CounterSemantics, SecondUnit) c.MustRegisterString("m.4", 10, Uint64Type, CounterSemantics, MinuteUnit) c.MustRegisterString("m.5", 10, Uint64Type, CounterSemantics, HourUnit) c.MustRegisterString("m.6", 10, Uint64Type, CounterSemantics, ByteUnit) c.MustRegisterString("m.7", 10, Uint64Type, CounterSemantics, KilobyteUnit) c.MustRegisterString("m.8", 10, Uint64Type, CounterSemantics, MegabyteUnit) c.MustRegisterString("m.9", 10, Uint64Type, CounterSemantics, GigabyteUnit) c.MustRegisterString("m.10", 10, Uint64Type, CounterSemantics, TerabyteUnit) c.MustRegisterString("m.11", 10, Uint64Type, CounterSemantics, PetabyteUnit) c.MustRegisterString("m.12", 10, Uint64Type, CounterSemantics, ExabyteUnit) c.MustRegisterString("m.13", 10, Uint64Type, CounterSemantics, OneUnit) c.MustRegisterString("m[a, b].14", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, NanosecondUnit) c.MustRegisterString("m[a, b].15", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, MicrosecondUnit) c.MustRegisterString("m[a, b].16", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, MillisecondUnit) c.MustRegisterString("m[a, b].17", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, SecondUnit) c.MustRegisterString("m[a, b].18", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, MinuteUnit) c.MustRegisterString("m[a, b].19", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, HourUnit) c.MustRegisterString("m[a, b].20", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, ByteUnit) c.MustRegisterString("m[a, b].21", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, KilobyteUnit) c.MustRegisterString("m[a, b].22", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, MegabyteUnit) c.MustRegisterString("m[a, b].23", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, GigabyteUnit) c.MustRegisterString("m[a, b].24", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, TerabyteUnit) c.MustRegisterString("m[a, b].25", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, PetabyteUnit) c.MustRegisterString("m[a, b].26", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, ExabyteUnit) c.MustRegisterString("m[a, b].27", Instances{"a": 1, "b": 2}, Uint64Type, CounterSemantics, OneUnit) c.MustStart() defer c.MustStop() _, _, metrics, values, instances, indoms, strings, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Errorf("cannot get dump: %v", err) return } matchMetricsAndValues(metrics, values, instances, strings, c, t) matchInstancesAndInstanceDomains(instances, indoms, strings, c, t) } func TestWritingDifferentTypes(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Errorf("cannot create client: %v", err) return } c.MustRegisterString("m.1", 2147483647, Int32Type, CounterSemantics, OneUnit) c.MustRegisterString("m.2", 2147483647, Int64Type, CounterSemantics, OneUnit) c.MustRegisterString("m.3", uint32(4294967295), Uint32Type, CounterSemantics, OneUnit) c.MustRegisterString("m.4", uint64(4294967295), Uint64Type, CounterSemantics, OneUnit) c.MustRegisterString("m.5", 3.14, FloatType, CounterSemantics, OneUnit) c.MustRegisterString("m.6", 6.28, DoubleType, CounterSemantics, OneUnit) c.MustRegisterString("m.7", "luke", StringType, CounterSemantics, OneUnit) c.MustRegisterString("m[a, b].8", Instances{"a": 2147483647, "b": -2147483648}, Int32Type, CounterSemantics, OneUnit) c.MustRegisterString("m[a, b].9", Instances{"a": 2147483647, "b": -2147483648}, Int64Type, CounterSemantics, OneUnit) c.MustRegisterString("m[a, b].10", Instances{"a": uint32(4294967295), "b": 0}, Uint32Type, CounterSemantics, OneUnit) c.MustRegisterString("m[a, b].11", Instances{"a": uint64(4294967295), "b": 0}, Uint64Type, CounterSemantics, OneUnit) c.MustRegisterString("m[a, b].12", Instances{"a": 3.14, "b": -3.14}, FloatType, CounterSemantics, OneUnit) c.MustRegisterString("m[a, b].13", Instances{"a": 6.28, "b": -6.28}, DoubleType, CounterSemantics, OneUnit) c.MustRegisterString("m[a, b].14", Instances{"a": "luke", "b": "skywalker"}, StringType, CounterSemantics, OneUnit) c.MustStart() defer c.MustStop() _, _, metrics, values, instances, indoms, strings, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Errorf("cannot get dump: %v", err) return } matchMetricsAndValues(metrics, values, instances, strings, c, t) matchInstancesAndInstanceDomains(instances, indoms, strings, c, t) } func TestMMV2MetricWriting(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Errorf("cannot create client, error: %v", err) return } m := c.MustRegisterString("it_takes_a_big_man_to_cry_but_it_takes_a_bigger_man_to_laugh_at_that_man", 21, Int32Type, CounterSemantics, OneUnit) c.MustStart() defer c.MustStop() h, _, metrics, values, instances, indoms, strings, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Errorf("cannot create dump, error: %v", err) } if h.Version != 2 { t.Error("expected mmv version to be 2") } if h.Toc != 3 { t.Error("expected tocs to be 3") } matchMetricsAndValues(metrics, values, instances, strings, c, t) matchInstancesAndInstanceDomains(instances, indoms, strings, c, t) if len(strings) != 1 { t.Error("expected one string in the dump") } _, met := findMetric(m, metrics) off := met.(*mmvdump.Metric2).Name if str, ok := strings[uint64(off)]; !ok { t.Errorf("expected a string at offset %v", off) } else if (string(str.Payload[:len(m.Name())])) != m.Name() { t.Error("the metric name in strings section doesn't match the registered metric name") } } func TestMMV2InstanceWriting(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Errorf("cannot create client, error: %v", err) return } c.MustRegisterString( "a[it_takes_a_big_man_to_cry_but_it_takes_a_bigger_man_to_laugh_at_that_man].b", Instances{ "it_takes_a_big_man_to_cry_but_it_takes_a_bigger_man_to_laugh_at_that_man": 32, }, Int32Type, CounterSemantics, OneUnit, ) c.MustStart() defer c.MustStop() h, _, metrics, values, instances, indoms, strings, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Errorf("cannot create dump, error: %v", err) } if h.Version != 2 { t.Error("expected mmv version to be 2") } if h.Toc != 5 { t.Error("expected tocs to be 3") } matchMetricsAndValues(metrics, values, instances, strings, c, t) matchInstancesAndInstanceDomains(instances, indoms, strings, c, t) if len(strings) != 2 { t.Errorf("expected two strings in the dump") } } func toFixed(v float64, p int) float64 { return float64(uint64(v*math.Pow(10, float64(p)))) / math.Pow(10, float64(p)) } func matchSingleDump(expected interface{}, m PCPMetric, c *PCPClient, t *testing.T) { _, _, metrics, values, instances, _, strings, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Errorf("cannot get dump: %v", err) return } matchMetricsAndValues(metrics, values, instances, strings, c, t) off, _ := findMetric(m, metrics) _, v := findSingletonValue(off, values) if val, err := mmvdump.FixedVal(v.Val, mmvdump.Type(m.Type())); val != expected { t.Errorf("expected metric to be %v, got %v", expected, val) } else if err != nil { t.Errorf("cannot convert stored metric val to float64") } } func matchSingle(expected, val interface{}, m PCPMetric, c *PCPClient, t *testing.T) { if val != expected { t.Errorf("expected Val() to return %v", expected) } matchSingleDump(expected, m, c, t) } func TestCounter(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Errorf("cannot create client, error: %v", err) return } m, err := NewPCPCounter(0, "c.1") if err != nil { t.Errorf("cannot create counter, error: %v", err) return } c.MustRegister(m) c.MustStart() defer c.MustStop() // Up m.Up() matchSingle(int64(1), m.Val(), m, c, t) // Inc m.MustInc(9) matchSingle(int64(10), m.Val(), m, c, t) // Inc decrement err = m.Inc(-9) if err == nil { t.Errorf("expected decrementing a counter to generate an error") } matchSingle(int64(10), m.Val(), m, c, t) // Set less err = m.Set(9) if err == nil { t.Errorf("expected setting a counter to a lesser value to generate an error") } matchSingle(int64(10), m.Val(), m, c, t) // Set more err = m.Set(99) if err != nil { t.Errorf("expected setting a counter to a larger value to not generate an error") } matchSingle(int64(99), m.Val(), m, c, t) } func TestGauge(t *testing.T) { c, err := NewPCPClient("test") if err != nil { t.Errorf("cannot create client, error: %v", err) return } m, err := NewPCPGauge(0, "g.1") if err != nil { t.Errorf("cannot create gauge, error: %v", err) return } c.MustRegister(m) c.MustStart() defer c.MustStop() // Inc m.MustInc(10) matchSingle(float64(10), m.Val(), m, c, t) // Dec err = m.Dec(9) if err != nil { t.Errorf("cannot decrement the gauge") } matchSingle(float64(1), m.Val(), m, c, t) // Set err = m.Set(9) if err != nil { t.Errorf("cannot set the gauge's value") } matchSingle(float64(9), m.Val(), m, c, t) } func TestTimer(t *testing.T) { timer, err := NewPCPTimer("t.1", NanosecondUnit) if err != nil { t.Errorf("cannot create timer, error: %v", err) return } c, err := NewPCPClient("test") if err != nil { t.Errorf("cannot create client, error: %v", err) return } c.MustRegister(timer) c.MustStart() defer c.MustStop() err = timer.Start() if err != nil { t.Errorf("cannot start timer, error: %v", err) } time.Sleep(time.Second) v, err := timer.Stop() if err != nil { t.Errorf("cannot stop timer, error: %v", err) } matchSingleDump(v, timer, c, t) } func TestCounterVector(t *testing.T) { cv, err := NewPCPCounterVector(map[string]int64{ "m1": 1, "m2": 2, }, "m.1") if err != nil { t.Errorf("cannot create CounterVector, error: %v", err) return } c, err := NewPCPClient("c") if err != nil { t.Errorf("cannot create client, error: %v", err) } c.MustRegister(cv) c.MustStart() defer c.MustStop() var val int64 // Set cv.MustSet(10, "m1") if val, err = cv.Val("m1"); val != 10 { t.Errorf("expected m.1[m1] to be 10, got %v", val) } else if err != nil { t.Errorf("cannot retrieve m.1[m1] value, error: %v", err) } // Inc cv.MustInc(10, "m2") if val, err = cv.Val("m2"); val != 12 { t.Errorf("expected m.1[m2] to be 12, got %v", val) } else if err != nil { t.Errorf("cannot retrieve m.1[m2] value, error: %v", err) } // Up cv.Up("m1") if val, err = cv.Val("m1"); val != 11 { t.Errorf("expected m.1[m1] to be 11, got %v", val) } else if err != nil { t.Errorf("cannot retrieve m.1[m1] value, error: %v", err) } } func TestGaugeVector(t *testing.T) { g, err := NewPCPGaugeVector(map[string]float64{ "m1": 1.2, "m2": 2.4, }, "m.1") if err != nil { t.Errorf("cannot create GaugeVector, error: %v", err) return } c, err := NewPCPClient("c") if err != nil { t.Errorf("cannot create client, error: %v", err) } c.MustRegister(g) c.MustStart() defer c.MustStop() var val float64 // Set g.MustSet(10, "m1") if val, err = g.Val("m1"); val != 10 { t.Errorf("expected m.1[m1] to be 10, got %v", val) } else if err != nil { t.Errorf("cannot retrieve m.1[m1] value, error: %v", err) } // Inc g.MustInc(10, "m2") if val, err = g.Val("m2"); val != 12.4 { t.Errorf("expected m.1[m2] to be 12.4, got %v", val) } else if err != nil { t.Errorf("cannot retrieve m.1[m2] value, error: %v", err) } // Dec g.MustDec(10, "m2") if val, err = g.Val("m2"); toFixed(val, 5) != 2.4 { t.Errorf("expected m.1[m2] to be 2.4, got %v", val) } else if err != nil { t.Errorf("cannot retrieve m.1[m2] value, error: %v", err) } } func TestHistogram(t *testing.T) { hist := hdrhistogram.New(0, 100, 5) h, err := NewPCPHistogram("test.hist", 0, 100, 5, OneUnit) if err != nil { t.Fatalf("cannot create metric, error: %v", err) } c, err := NewPCPClient("test") if err != nil { t.Fatalf("cannot create client, error: %v", err) } c.MustRegister(h) c.MustStart() defer c.MustStop() _, _, m, v, i, id, s, err := mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Fatalf("cannot create dump, error: %v", err) } matchMetricsAndValues(m, v, i, s, c, t) matchInstancesAndInstanceDomains(i, id, s, c, t) for i := int64(1); i <= 100; i++ { err = hist.RecordValues(i, i) if err != nil { t.Errorf("hdrhistogram couldn't record, error: %v", err) } h.MustRecordN(i, i) } _, _, m, v, _, _, _, err = mmvdump.Dump(c.writer.Bytes()) if err != nil { t.Fatalf("cannot create dump, error: %v", err) } cases := [...]struct { ins string val float64 }{ {"mean", hist.Mean()}, {"mean", h.Mean()}, {"variance", math.Pow(hist.StdDev(), 2)}, {"variance", h.Variance()}, {"standard_deviation", hist.StdDev()}, {"standard_deviation", h.StandardDeviation()}, {"max", float64(hist.Max())}, {"max", float64(h.Max())}, {"min", float64(hist.Min())}, {"min", float64(h.Min())}, } for _, c := range cases { off := h.indom.instances[c.ins].offset moff, _ := findMetric(h, m) _, dv := findInstanceValue(moff, uint64(off), v) val, _ := mmvdump.FixedVal(uint64(dv.Val), mmvdump.DoubleType) if c.val != val { t.Errorf("expected %v to be %v, got %v", c.ins, c.val, val) } } } golang-github-performancecopilot-speed-4.0.0/config.go000066400000000000000000000020651433655324600230430ustar00rootroot00000000000000package speed import ( "bufio" "os" "path/filepath" "regexp" ) // rootPath stores path to the pcp root installation var rootPath string // confPath stores path to pcp.conf var confPath string // config stores the configuration as defined in current PCP environment var config map[string]string // pat stores a valid key-value pattern line var pat = "([A-Z0-9_]+)=(.*)" // initConfig initializes the config constants func initConfig() error { re, _ := regexp.Compile(pat) r, ok := os.LookupEnv("PCP_DIR") if !ok { r = "/" } rootPath = r c, ok := os.LookupEnv("PCP_CONF") if !ok { c = filepath.Join(rootPath, "etc", "pcp.conf") } confPath = c f, err := os.Open(confPath) if err != nil { return err } // if we reach at this point, it means we have a valid config // that can be read, so we can make the map non-nil config = make(map[string]string) scanner := bufio.NewScanner(f) for scanner.Scan() { t := scanner.Text() if re.MatchString(t) { matches := re.FindStringSubmatch(t) config[matches[1]] = matches[2] } } return nil } golang-github-performancecopilot-speed-4.0.0/config_test.go000066400000000000000000000030041433655324600240740ustar00rootroot00000000000000package speed import ( "os" "testing" ) func TestRootPath(t *testing.T) { if rootPath == "" { t.Errorf("RootPath is invalid") return } _, err := os.Stat(rootPath) if err != nil { t.Errorf("RootPath err: %s", err) } } func TestConfPath(t *testing.T) { if confPath == "" { t.Errorf("ConfPath is invalid") return } fi, err := os.Stat(confPath) if err != nil { return } if !fi.Mode().IsRegular() { t.Errorf("%s should be a regular file", confPath) return } } var keysToTest = []string{ "PCP_VERSION", "PCP_USER", "PCP_GROUP", "PCP_PLATFORM", "PCP_PLATFORM_PATHS", "PCP_ETC_DIR", "PCP_SYSCONF_DIR", "PCP_SYSCONFIG_DIR", "PCP_RC_DIR", "PCP_BIN_DIR", "PCP_BINADM_DIR", "PCP_LIB_DIR", "PCP_LIB32_DIR", "PCP_SHARE_DIR", "PCP_INC_DIR", "PCP_MAN_DIR", "PCP_PMCDCONF_PATH", "PCP_PMCDOPTIONS_PATH", "PCP_PMCDRCLOCAL_PATH", "PCP_PMPROXYOPTIONS_PATH", "PCP_PMIECONTROL_PATH", "PCP_PMSNAPCONTROL_PATH", "PCP_PMLOGGERCONTROL_PATH", "PCP_PMDAS_DIR", "PCP_RUN_DIR", "PCP_PMDAS_DIR", "PCP_LOG_DIR", "PCP_TMP_DIR", "PCP_TMPFILE_DIR", "PCP_DOC_DIR", "PCP_DEMOS_DIR", } var optionalKeysToTest = []string{ "PCP_PMWEBDOPTIONS_PATH", "PCP_PMMGROPTIONS_PATH", } func TestConfig(t *testing.T) { if config == nil { return } for _, key := range keysToTest { _, ok := config[key] if !ok { t.Errorf("key %s not present in Config", key) } } for _, key := range optionalKeysToTest { _, ok := config[key] if !ok { t.Logf("key %s not present in Config (optional)", key) } } } golang-github-performancecopilot-speed-4.0.0/countunit_string.go000066400000000000000000000006011433655324600252060ustar00rootroot00000000000000// Code generated by "stringer -type=CountUnit"; DO NOT EDIT package speed import "fmt" const _CountUnit_name = "OneUnit" var _CountUnit_index = [...]uint8{0, 7} func (i CountUnit) String() string { i -= 1048576 if i >= CountUnit(len(_CountUnit_index)-1) { return fmt.Sprintf("CountUnit(%d)", i+1048576) } return _CountUnit_name[_CountUnit_index[i]:_CountUnit_index[i+1]] } golang-github-performancecopilot-speed-4.0.0/examples/000077500000000000000000000000001433655324600230625ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/acme/000077500000000000000000000000001433655324600237675ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/acme/main.go000066400000000000000000000111311433655324600252370ustar00rootroot00000000000000// A golang implementation of the acme factory examples from python and Java APIs // // The python implementation is in mmv.py in PCP core // (https://github.com/performancecopilot/pcp/blob/master/src/python/pcp/mmv.py#L21-L70) // // The Java implementation is in examples in parfait core // (https://github.com/performancecopilot/parfait/tree/master/examples/acme) // // To run the python version of the example that exits do // go run examples/acme/main.go // // To run the java version of the example that runs forever, simply add a --forever // flag // go run examples/acme/main.go --forever package main import ( "flag" "fmt" "log" "math/rand" "time" "github.com/performancecopilot/speed/v4" ) var runforever bool func init() { flag.BoolVar(&runforever, "forever", false, "if enabled, runs the forever running version of this example") } func main() { flag.Parse() if runforever { forever() } else { serial() } } func serial() { instances := []string{"Anvils", "Rockets", "Giant_Rubber_Bands"} indom, err := speed.NewPCPInstanceDomain( "Acme Products", instances, "Acme products", "Most popular products produced by the Acme Corporation", ) if err != nil { log.Fatal("Could not create indom, error: ", err) } countmetric, err := speed.NewPCPInstanceMetric( speed.Instances{ "Anvils": 0, "Rockets": 0, "Giant_Rubber_Bands": 0, }, "products.count", indom, speed.Uint64Type, speed.CounterSemantics, speed.OneUnit, "Acme factory product throughput", `Monotonic increasing counter of products produced in the Acme Corporation factory since starting the Acme production application. Quality guaranteed.`, ) if err != nil { log.Fatal("Could not create countmetric, error: ", err) } timemetric, err := speed.NewPCPInstanceMetric( speed.Instances{ "Anvils": 0, "Rockets": 0, "Giant_Rubber_Bands": 0, }, "products.time", indom, speed.Uint64Type, speed.CounterSemantics, speed.MicrosecondUnit, "Machine time spent producing Acme products", ) if err != nil { log.Fatal("Could not create timemetric, error: ", err) } client, err := speed.NewPCPClient("acme") if err != nil { log.Fatal("Could not create client, error: ", err) } client.MustRegisterIndom(indom) client.MustRegister(countmetric) client.MustRegister(timemetric) client.MustStart() defer client.MustStop() time.Sleep(time.Second * 5) err = countmetric.SetInstance(42, "Anvils") if err != nil { log.Fatal("Could not set countmetric[\"Anvils\"], error: ", err) } time.Sleep(time.Second * 5) } // ProductBuilder is based on ProductBuilder in the parfait example // https://github.com/performancecopilot/parfait/blob/master/examples/acme/src/main/java/ProductBuilder.java type ProductBuilder struct { completed speed.Counter totalTime speed.Gauge bound int name string } // NewProductBuilder creates a new instance of ProductBuilder func NewProductBuilder(name string, client speed.Client) *ProductBuilder { completed, err := speed.NewPCPCounter(0, "products."+name+".count") if err != nil { log.Fatal("Could not create completed, error: ", err) } totalTime, err := speed.NewPCPGauge(0, "products."+name+".time") if err != nil { log.Fatal("Could not create totalTime, error: ", err) } client.MustRegister(completed) client.MustRegister(totalTime) return &ProductBuilder{ name: name, bound: 500, completed: completed, totalTime: totalTime, } } // Difficulty sets the upper bound on the sleep time func (p *ProductBuilder) Difficulty(bound int) { p.bound = bound } // Build sleeps for a random time, then adds that value to totalTime func (p *ProductBuilder) Build() { elapsed := rand.Intn(p.bound) time.Sleep(time.Duration(elapsed) * time.Millisecond) p.totalTime.MustInc(float64(elapsed)) p.completed.Up() } // Start starts an infinite loop calling Build and logging the value of completed func (p *ProductBuilder) Start() { for { p.Build() fmt.Printf("Built %d %s\n", p.completed.Val(), p.name) } } func forever() { client, err := speed.NewPCPClient("acme") if err != nil { log.Fatal("Could not create client, error: ", err) } rockets := NewProductBuilder("Rockets", client) anvils := NewProductBuilder("Anvils", client) gbrs := NewProductBuilder("Giant_Rubber_Bands", client) rockets.Difficulty(4500) anvils.Difficulty(1500) gbrs.Difficulty(2500) go func() { rockets.Start() }() go func() { anvils.Start() }() go func() { gbrs.Start() }() client.MustStart() defer client.MustStop() // block forever // TODO: maybe use signal.Notify and shut down gracefully select {} } golang-github-performancecopilot-speed-4.0.0/examples/basic_histogram/000077500000000000000000000000001433655324600262205ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/basic_histogram/main.go000066400000000000000000000011461433655324600274750ustar00rootroot00000000000000package main import ( "fmt" "log" "math/rand" "time" "github.com/performancecopilot/speed/v4" ) func main() { max := int64(100) c, err := speed.NewPCPClient("histogram_test") if err != nil { log.Fatal("Could not create client, error: ", err) } m, err := speed.NewPCPHistogram("hist", 0, max, 5, speed.OneUnit, "a sample histogram") if err != nil { log.Fatal("Could not create histogram, error: ", err) } c.MustRegister(m) c.MustStart() defer c.MustStop() for i := 0; i < 60; i++ { v := rand.Int63n(max) fmt.Println("recording", v) m.MustRecord(v) time.Sleep(time.Second) } } golang-github-performancecopilot-speed-4.0.0/examples/download/000077500000000000000000000000001433655324600246715ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/download/main.go000066400000000000000000000027551433655324600261550ustar00rootroot00000000000000// This is an example of using composite units // It makes successive downloads of the linux source code // and reports the download speed as well as the download time package main import ( "io" "log" "net/http" "github.com/performancecopilot/speed/v4" ) type sink int func (s sink) Write(data []byte) (int, error) { return len(data), nil } const url = "https://codeload.github.com/suyash/ulid/zip/master" func main() { client, err := speed.NewPCPClient("download") if err != nil { log.Fatal(err) } downloadSpeed, err := speed.NewPCPSingletonMetric( float64(0), "download_speed", speed.DoubleType, speed.InstantSemantics, speed.MegabyteUnit.Time(speed.SecondUnit, -1), ) if err != nil { log.Fatal(err) } timer, err := speed.NewPCPTimer("download_time", speed.SecondUnit) if err != nil { log.Fatal(err) } client.MustRegister(timer) client.MustRegister(downloadSpeed) client.MustStart() defer client.MustStop() for { timer.Reset() run(timer, downloadSpeed) } } func run(timer *speed.PCPTimer, downloadSpeed *speed.PCPSingletonMetric) { res, err := http.Get(url) if err != nil { log.Fatal(err) } defer res.Body.Close() var s sink err = timer.Start() if err != nil { log.Fatal(err) } n, err := io.Copy(s, res.Body) if err != nil { log.Fatal(err) } elapsed, err := timer.Stop() if err != nil { log.Fatal(err) } downloadSpeed.Set(float64(n) / (1024 * 1024 * float64(elapsed))) log.Println("downloaded", n, "bytes in", elapsed, "seconds") } golang-github-performancecopilot-speed-4.0.0/examples/http_counter/000077500000000000000000000000001433655324600256005ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/http_counter/server.go000066400000000000000000000016201433655324600274340ustar00rootroot00000000000000package main import ( "fmt" "log" "net/http" "os" "github.com/performancecopilot/speed/v4" ) var metric speed.Counter func main() { var err error metric, err = speed.NewPCPCounter( 0, "http.requests", "Number of Requests", ) if err != nil { log.Fatal("Could not create counter, error: ", err) } client, err := speed.NewPCPClient("example") if err != nil { log.Fatal("Could not create client, error: ", err) } client.MustRegister(metric) client.MustStart() defer client.MustStop() http.HandleFunc("/increment", handleIncrement) go func() { if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal("Could not listen on port, error: ", err) } }() fmt.Println("To stop the server press enter") _, _ = os.Stdin.Read(make([]byte, 1)) os.Exit(0) } func handleIncrement(w http.ResponseWriter, r *http.Request) { metric.Up() fmt.Fprintf(w, "incremented\n") } golang-github-performancecopilot-speed-4.0.0/examples/instance_string/000077500000000000000000000000001433655324600262545ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/instance_string/main.go000066400000000000000000000017311433655324600275310ustar00rootroot00000000000000package main import ( "flag" "log" "time" "github.com/performancecopilot/speed/v4" ) var timelimit = flag.Int("time", 60, "number of seconds to run for") func main() { flag.Parse() c, err := speed.NewPCPClient("strings") if err != nil { log.Fatal("Could not create client, error: ", err) } m, err := c.RegisterString("language[go, javascript, php].users", speed.Instances{ "go": 1, "javascript": 100, "php": 10, }, speed.Uint64Type, speed.CounterSemantics, speed.OneUnit) if err != nil { log.Fatal("Could not register string, error: ", err) } c.MustStart() defer c.MustStop() metric := m.(speed.InstanceMetric) for i := 0; i < *timelimit; i++ { v, _ := metric.ValInstance("go") metric.MustSetInstance(v.(uint64)*2, "go") v, _ = metric.ValInstance("javascript") metric.MustSetInstance(v.(uint64)+10, "javascript") v, _ = metric.ValInstance("php") metric.MustSetInstance(v.(uint64)+1, "php") time.Sleep(time.Second) } } golang-github-performancecopilot-speed-4.0.0/examples/runtime/000077500000000000000000000000001433655324600245455ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/runtime/main.go000066400000000000000000000071441433655324600260260ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "runtime" "time" "github.com/performancecopilot/speed/v4" ) // refresh interval const interval = time.Millisecond // list of memory-related metric domains var memMetricInstances = []string{ "Alloc", "TotalAlloc", "Sys", "Lookups", "Mallocs", "Frees", "HeapAlloc", "HeapSys", "HeapIdle", "HeapInuse", "HeapReleased", "HeapObjects", "StackInuse", "StackSys", "MSpanInuse", "MSpanSys", "MCacheInuse", "MCacheSys", "BuckHashSys", "GCSys", "OtherSys", "NextGC", "LastGC", "PauseTotalNs", "PauseNs", "PauseEnd", "NumGC", } func main() { cpuIndom, err := speed.NewPCPInstanceDomain( "CPU Metrics", []string{"CGoCalls", "Goroutines"}, ) if err != nil { log.Fatal("Could not create cpuIndom, error: ", err) } cpuMetric, err := speed.NewPCPInstanceMetric( speed.Instances{ "CGoCalls": 0, "Goroutines": 0, }, "cpu", cpuIndom, speed.Int64Type, speed.CounterSemantics, speed.OneUnit, ) if err != nil { log.Fatal("Could not create cpuMetric, error: ", err) } memIndom, err := speed.NewPCPInstanceDomain("Memory Metrics", memMetricInstances) if err != nil { log.Fatal("Could not create memIndom, error: ", err) } memInsts := speed.Instances{} for _, v := range memMetricInstances { memInsts[v] = 0 } memMetric, err := speed.NewPCPInstanceMetric( memInsts, "mem", memIndom, speed.Uint64Type, speed.CounterSemantics, speed.OneUnit, ) if err != nil { log.Fatal("Could not create memMetric, error: ", err) } client, err := speed.NewPCPClient("runtime") if err != nil { log.Fatal("Could not create client, error: ", err) } client.MustRegister(cpuMetric) client.MustRegister(memMetric) client.MustStart() defer client.MustStop() mStats := runtime.MemStats{} c := time.Tick(interval) go func() { for range c { cpuMetric.MustSetInstance(runtime.NumCgoCall(), "CGoCalls") cpuMetric.MustSetInstance(runtime.NumGoroutine(), "Goroutines") runtime.ReadMemStats(&mStats) memMetric.MustSetInstance(mStats.Alloc, "Alloc") memMetric.MustSetInstance(mStats.TotalAlloc, "TotalAlloc") memMetric.MustSetInstance(mStats.Sys, "Sys") memMetric.MustSetInstance(mStats.Mallocs, "Mallocs") memMetric.MustSetInstance(mStats.Frees, "Frees") memMetric.MustSetInstance(mStats.HeapAlloc, "HeapAlloc") memMetric.MustSetInstance(mStats.HeapSys, "HeapSys") memMetric.MustSetInstance(mStats.HeapIdle, "HeapIdle") memMetric.MustSetInstance(mStats.HeapInuse, "HeapInuse") memMetric.MustSetInstance(mStats.HeapReleased, "HeapReleased") memMetric.MustSetInstance(mStats.HeapObjects, "HeapObjects") memMetric.MustSetInstance(mStats.StackInuse, "StackInuse") memMetric.MustSetInstance(mStats.StackSys, "StackSys") memMetric.MustSetInstance(mStats.MSpanInuse, "MSpanInuse") memMetric.MustSetInstance(mStats.MSpanSys, "MSpanSys") memMetric.MustSetInstance(mStats.MCacheInuse, "MCacheInuse") memMetric.MustSetInstance(mStats.MCacheSys, "MCacheSys") memMetric.MustSetInstance(mStats.BuckHashSys, "BuckHashSys") memMetric.MustSetInstance(mStats.GCSys, "GCSys") memMetric.MustSetInstance(mStats.OtherSys, "OtherSys") memMetric.MustSetInstance(mStats.NextGC, "NextGC") memMetric.MustSetInstance(mStats.LastGC, "LastGC") memMetric.MustSetInstance(mStats.PauseTotalNs, "PauseTotalNs") memMetric.MustSetInstance(mStats.PauseNs[(mStats.NumGC+255)%256], "PauseNs") memMetric.MustSetInstance(mStats.PauseEnd[(mStats.NumGC+255)%256], "PauseEnd") memMetric.MustSetInstance(uint64(mStats.NumGC), "NumGC") } }() fmt.Println("To stop the mapping, press enter") _, _ = os.Stdin.Read(make([]byte, 1)) } golang-github-performancecopilot-speed-4.0.0/examples/simple/000077500000000000000000000000001433655324600243535ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/simple/main.go000066400000000000000000000013641433655324600256320ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "github.com/performancecopilot/speed/v4" ) func main() { metric, err := speed.NewPCPSingletonMetric( 42, "simple.counter", speed.Int32Type, speed.CounterSemantics, speed.OneUnit, "A Simple Metric", "This is a simple counter metric to demonstrate the speed API", ) if err != nil { log.Fatal("Could not create singelton metric, error: ", err) } client, err := speed.NewPCPClient("simple") if err != nil { log.Fatal("Could not create client, error: ", err) } client.MustRegister(metric) client.MustStart() defer client.MustStop() fmt.Println("The metric is currently mapped as mmv.simple.simple.counter, to stop the mapping, press enter") _, _ = os.Stdin.Read(make([]byte, 1)) } golang-github-performancecopilot-speed-4.0.0/examples/simple_string_metric/000077500000000000000000000000001433655324600273045ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/simple_string_metric/main.go000066400000000000000000000014061433655324600305600ustar00rootroot00000000000000package main import ( "flag" "log" "time" "github.com/performancecopilot/speed/v4" ) var timelimit = flag.Int("time", 60, "number of seconds to run for") func main() { flag.Parse() c, err := speed.NewPCPClient("stringtest") if err != nil { log.Fatal("Could not create client, error: ", err) } names := []string{ "Batman", "Robin", "Nightwing", "Batgirl", "Red Robin", "Red Hood", } m, err := c.RegisterString("bat.names", names[0], speed.StringType, speed.InstantSemantics, speed.OneUnit) if err != nil { log.Fatal("Could not register string, error: ", err) } c.MustStart() defer c.MustStop() metric := m.(speed.SingletonMetric) for i := 0; i < *timelimit; i++ { metric.MustSet(names[i%len(names)]) time.Sleep(time.Second) } } golang-github-performancecopilot-speed-4.0.0/examples/singleton_counter/000077500000000000000000000000001433655324600266235ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/singleton_counter/main.go000066400000000000000000000014271433655324600301020ustar00rootroot00000000000000package main import ( "flag" "fmt" "log" "time" "github.com/performancecopilot/speed/v4" ) var timelimit = flag.Int("time", 60, "number of seconds to run for") func main() { flag.Parse() metric, err := speed.NewPCPCounter( 0, "counter", "A Simple Metric", ) if err != nil { log.Fatal("Could not create counter, error: ", err) } client, err := speed.NewPCPClient("singletoncounter") if err != nil { log.Fatal("Could not create client, error: ", err) } err = client.Register(metric) if err != nil { log.Fatal("Could not register metric, error: ", err) } client.MustStart() defer client.MustStop() fmt.Println("The metric should be visible as mmv.singletoncounter.counter") for i := 0; i < *timelimit; i++ { metric.Up() time.Sleep(time.Second) } } golang-github-performancecopilot-speed-4.0.0/examples/singleton_string/000077500000000000000000000000001433655324600264525ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/examples/singleton_string/main.go000066400000000000000000000014701433655324600277270ustar00rootroot00000000000000// this example showcases speeds metric inference from strings property package main import ( "flag" "log" "time" "github.com/performancecopilot/speed/v4" ) var timelimit = flag.Int("time", 60, "number of seconds to run for") func main() { flag.Parse() c, err := speed.NewPCPClient("strings") if err != nil { log.Fatal("Could not create client, error: ", err) } m, err := c.RegisterString( "this.is.a.simple.counter.metric.to.demonstrate.the.RegisterString.function", 10, speed.Int32Type, speed.CounterSemantics, speed.OneUnit) if err != nil { log.Fatal("Could not register string, error: ", err) } c.MustStart() defer c.MustStop() metric := m.(speed.SingletonMetric) for i := 0; i < *timelimit; i++ { val := metric.Val().(int32) val++ metric.MustSet(val) time.Sleep(time.Second) } } golang-github-performancecopilot-speed-4.0.0/go.mod000066400000000000000000000002641433655324600223540ustar00rootroot00000000000000module github.com/performancecopilot/speed/v4 go 1.12 require ( github.com/HdrHistogram/hdrhistogram-go v1.1.0 github.com/edsrzf/mmap-go v1.0.0 github.com/pkg/errors v0.9.1 ) golang-github-performancecopilot-speed-4.0.0/go.sum000066400000000000000000000154741433655324600224120ustar00rootroot00000000000000dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= golang-github-performancecopilot-speed-4.0.0/images/000077500000000000000000000000001433655324600225115ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/images/speed.png000066400000000000000000000577721433655324600243410ustar00rootroot00000000000000PNG  IHDR9sBIT|d pHYsmhtEXtSoftwarewww.inkscape.org<RtEXtCopyrightCC Attribution-ShareAlike http://creativecommons.org/licenses/by-sa/3.0/^Z IDATxYu&sɭ6 DD%j9ܲ';c"&84A}q31C#aYԲYDH$P{r=gV%6K`EV%u{wvlvlvlvlvlvlvlvlvlvlvlvlvlvg{& ɓ< ! (|Wq|ƉWb8VN1sg>T8XX9q8sHvϲ|MXSq~e嬮 RV0v^v:BPpB}wpngCivm f;qB?g-дf/-0H; qf0u\K'YgZ+ G ѳ'C{@>01nX?vҎ}6s҇:O$"xSy(sdr4ꉄ#tI#YZ3Fb;cn'>VyƳG;K@7{СWyލ 6#(^@@?Iz1/Y~x_sj  DT3F}~澇am2ugx=xΆ6k^?ԿFWϡ8~ 艓sMAM<dz`{Ǜej6B-L P !Q:E f.z.)rۜkq He "/2w[k={AUAWhE]NTă^6J,Cv'(;&8}(el{2тC1BEgėLȽAZM\4 awZҜGhJ ,|eC$~I'UC|=(uIl`V$/7 Q{z~=>!@FL×tZ]*tQ iGjճ2D`ON PhC{ĹT宕 ,]o}WҘCRoBL )  P{pЧ ϟ&%KU)q$Z1e tY@qp'1Ȼ $@GN.4@K (B]sIm8n[ٽqգoDÍ6(m朮8]o\(޵B{rį/nOz I$NV C + n}~־o,y(}E\Ѭ jY}sfRr8I' 8y^9r.,ОFJhni=ф~ҔU87Q.fexBi0*?3zM3g__7fAD1 JHQ@EQ.YWxM9yaE 8JHQBBg(!$EB/<"\DI+"< ݗ>q DNӌEnvk)Tl6LxV#bā&$ Q!%Q'CPU}#&Qxv79 k cf L" Rde(mm҆0 R  t (@ @FCeEoy#4>/D!wqzAa#TO@/l@YQc5qNkAB|( @J $DDBK ,@:Re&#elmltʅfAwmp\$ HY@(rH>ė0$"cXhP.@{w _•9B1B9tPv7uVa|D|}Bkx݇2|@3K.tSZ0ԶJMR&L5 T   IE$!DQRB(RAEDj$lZ~}]ja?ǹ @@$%ĕXRNe|Z@>4>L0!@#{(%T q%r2φ(] 9~D zKz'@qz'PHu2[ dTȲ5)/Ai("XD ADYL&R (.G1с'^_# {@zu}JW av RW)o !BV1ToWt~/fxIkZ<X8( 0⠈R b 6rՃYA$=.\8w3 _?pS6!&h B )%Ib0GĈ("ֆY&2 ҊY1 a"O`4 !5>Aq D D UUT5r@4{8.slɇa٠?6lO>DGx (@ H3|s5~Eڻ!V *K| @޿ztÛR'܁ N&AYXD؃( 5 2RXMD44wal\1Fm*@ f+K@\9P\%T+ EV^xԇwzg8w`pJ < 3b"(U5^9c?o]Tk(!|~|Du~:zVmԏ|_ s.X$AFk*kNjNTbGe$"F$"(mbeE[C+Jj<{S4XXҀcTR4h1XB~{PBH|@Aoߠh"I8f`5Uo@.;?֗?'%ÍE$n~1Yl8#|<[Oؿ__?+Gh+_S]9k%cu,(JmFb# >@e9P$HT*"5TSZQJ4JՊZR%iHKzIMj~/xf\k6h@eIsIijl%eQƉ| M:6h?4" ,9T#d}^*Ρ[dNN~w-dV3['NW^9B+G= 2Kh }[p츧ĆPD&X .&T ͍<544U{83txv7pk\k0HqB-.V2N'hPeEm t`k-? X;< $Mdd%>(sɛʖW΅c;qD(ĆXw񪣭N_w{ 9B6հN7b۹f}>w8>ltH뇣x<; 2? RI-1?D;e\T9q JjNpu,o<(ڏ<  &J@oÿH4LM.~T,YtȘv;y.-sn`tYzxW_uzmxk<Ѧ}0@@[A (JW@1dcP DXBZGwm 'zkMN@na.{U,[f; Wx AyolXqEAύWe 6U2Jkoc#}i ~,_Qag/)=C9E{;Nt+>.@SU,<5pv @~ƞPkg4V z[?/_Bj:LG׋F!Y|O8Sז& ou[P!ch( E Ko#[[S_A0 ((e嵥~G>ef>lNj}r 8; ,@\/͂|=3{f 2 !l: GDVG _ؐ]nMjH.9d0q'xk? 3(ڈoy4[]`rd3ܔn"6 X[rrcW7glF L=tczõ=] ZϏY> t7Hccju՟ۥ˰~j;XF)RjZXy2 p!FrD|R:IRyPReWa)#GH hɳ|oxqI>QG$;X98}gS7G2-kѮă5]+j! NMA4D5%ta|֩pnG~O~""WghDvuq͈$yj\N3TVAhiINHj8p䥌Wڵr0P>)%(YQQ-y_[F%$1!H,x)d^tFpa kfmX??獋c"{W(3%O?ÛlS]՟|?UP܀jzA\{WϬbЯfɓĎpIЛSvDE[Z#vm*iIsdHHKBoFkKq/!I@Y:`U˿3KlKs0y1`H dD!@1Q@@e (#O#j˿$q%ė PB*nݩiśʛ0k1#ׯagqڀѽ~+jkWx#\m"+<_+=>P \fR%qn:;s^֗R)ZQ\I=brvԭVtJQd#Z~UvN-4#<@#R6(D9!t"})5q#( UT*V[ my [er*P7(am K?v}+j-r鵣2j"lt>|){gQ iZq}|y´dbo6r}nǼ0ھ;͕a(mT=q ghMnSz(JFȰ҅ݘ;Z{8xD @T FC1`n^?}hXu$6窮Ĥu&bD%a d%Ȱ@+K/=iKHި5ÕM`wn8~8Q>G,с]S7\r]sAve$s!ЁLAӮ+?>]s]gFSj?cA_2Qӆ)E7n >d؃ bØ;bqp0! v&Hӌ4FJc,Zڹ_c9pkjAfWO1\yE 7-wX/Ce]%&ȏoWcYV{v޸Z HA]q4_=נ;H:/rAg27 U9*qȈUh$e^-^Z(\1+W.rMܜ[{p]fRNn| @wɰ\A>" iRwUQ_4NdPf,: \"n̓mp5U/بm2 k%6HVB3c/6@+O(z):KAqt(;z+, Z3Ex` 7'AGܸZ/^j#l#v6((Xpd)Db HD1ABP,BM y{+3* d}fUs}uyfguc' jMpZqm`,/vs&d*L(UtlQȰtnwhwDR* e!9Qe N0H7ThK#mۿ[č*䚄Q\ ]A 'y)5] oeԬ}=)zGk&KphmIma`,-͍w}3jP4IQ[j#>i9U( b H1D,@F@D@ ,D,{HAfHG=t|oMP0㎍K{nҟ![[L$A :0v Th:71gi}nOW֫d9'ɄSz{x7ds~u >B٨@rɓZʉֆt! u9e8|ɻDD O'n|[qΊ V,y+J[] XHL!@ !T@2BbbH%&!$(H""Uu9@¥@a.U&di{w.j5<3GNf'-w[P-)Ȱ0@zP6E|ro Gp|yh JQT)$?;ZC% H57innΚN&Bg asG&ݗ'RB=u&t:]%u+@a&w^"ϜKZBn@I"hqH(!H$@%"+@+"V@*}0   EIu$, =0 J"!(ʨ#/Axv68Ʊy5v<ݦoojbnJCÏ]~ Md3T`V7G~A5g.%[lVhUCMB'lyMAXQ߳c3ظ\ハ]Pi}@X*4V4.B~aW =6eX7$4EI iR^R8b-X,VADLA (XDLB,E ,!(T !s܇k5Ԇ6ʒ.;@A*݊߇"n"E6L +'I`Kީ]>t y!B':wϜ7L=fV;rQ\惦w]e>/L,)=%Qk̜QČD 1"VĤ"W!TDB h#R4r0d>z~zGsL1]nnSƋ9dB F}Gy l !(@0!ͻ/<.q ՚U*׃(1DrRdC#l@^p9\֚ @ 9+dWg^EM0 mBEH PM1'HH#flfEu9+nb+ G}g7[lݤ`&9\YZEV}dB/ҿ)Ep-DX$?`Z%tի5!F*pbASD@RRPczꤸL5"ԅ(ekA8(9ŹUl_8/yZgc=h\$.Bg ~:d4Ȓ?MxWL}d8noqsF*m* " *đZl;̘s#͖6jhj6fŽb;4bm|SR1Jdy7IMRؤ~` ppHB+8`O`BY.os+m`]<ʿ6j7 kXW#|PA$x-خKQλ4h(U_DQ-M:LLkY~?\YJ-ǝب+zn_;oAS2rL1Fwl|zFT;DHz=׉::*>1 q0ф.d/~gI,lp:ɿ>N߷ite ߉3.ѹP1[Um0D u i- 7d?fՖԻf^&{Tp={&D[qNX 6y"}¿a p>@YRk3Aw޸O|9yP )ofK[ryuCF1k|O9[\|y1>=3)1( C-ȇr Al_Ax澾@J4CBa ū/WL'W mAAI%${5 ]7Dl"[_B(%cHMOO (-z3EUҐlP_RdRAe4Hk-9JweֽzB /Ca?ט:{\)Q/~0ֽXE4]$_9r?'R4{q[N\䁡臆o3$/SIGj3U9dLઃ6Q[G9JM3ڢ *@ߡb2իҕ$&opuSzGs4cO'hDi{=~:%轇_O| X[)T7|,jqGq憳;+ZujmWCԮ 4S0e;*Q{$k]0^$wZn/&AT'$ߘ»H0N%mĴ悝?൉ L!]+oP|(NąXC*P -d4gAKlO!]h*NW8 _~{5s5|h-^9}?֯G}EG"L}ݦߕ¹zӊi\(~K=x+^1|pwd҉xwMz=PT#jJB 11A,V!Xb"` %A؋0Y !&! $S^bq֮ʼ@Wn%2iy? wO/Ǔ]zr:-1`cP[gc>~˭g,> /1WsƬך ?6ʳ/|0c0\DKp8p/.55(wxXfo\[/5_ 8E IE(&HX#`XXMBF-D,c@PdZyh[UfƔ[fy^ ѵ8߆^Drztta!h[ 5RQ5C5EUk˱Ψ1Pz&02Hf( L,#0Ŋ $=hD4XLT*E \ 2BM{M=AR%jFewZh7S9 UUe\Q6m:kQoO5w9V{ 1tlEOc>w?=ėVX="׿OtӴ{m&dxJ=f6TZ"҂B3Hh0!H]) $eV ["Xb ذ&4+a\Z^ e励 B>B`WEUeϖ]Svs[PPM_~Qup)_~/|16WgHX6EXU8Z-jvuh)F-od|swoݺsE>6V]s 3 1Y<&Ez=m 4B@S2Yd޽yUs{X*/%\7Wb㮻 "U e{yJ&jǧ{pMߓ2"kKKol|9x<1p6.`x75ZwK>É7~Bl=8yN92lRnj[rK%qVkJma'VR1dkH>'j6X[;q>7/K#=?'Cg@[6:}lrO*/tIL 3=QP0dO&[oNT0WCqzk4gkzjkFZ GMN_b;&C֮/{$n`d\kO+8S[|>_g'~=UI50wm?|[ QW p8| > C70z౷dߒp+>j=m{6a;&*Zޚlg~yaaג}3Cg ȫYrkZ:)7S{]1J!xa(P } [gܯv&?QB*dXpf9 hvJPA"f0ELxl:Ajqѝ吮.a(:kwR.hC@G'R$ 70F _iɻ?c3~%ZH}K'޻6y]gbZ{v.}C@\^Dl hyqyf_'*DL2 K665/"s(x!p{ސ֙"ɮ󷟸XjY[}+@79Qғfpj^>;n5Q&vL=Q[zHg8`~305g6(~bQUzŋ0/HB3ZY(ף>PKq؀JTe9D5UAAGPuБoԂ XEX,L%[xVp$?9{1fq:{"{gl([ՋGy5?r,]Cuit D=~/&1{N/B$Y&IƉ-^ͷ*7`%Rl),^Êߩy{1J`&shq0-5fѶXMH&* EAP;rP50U&mԠ`GH~J$FY+lNvuDѦݛ/yRxOu9P{$8o僝-h1ӯ>2[UEA|4s3Huxݻ&fdbwrx]9,r\1j+5Y&â'l>\w#Or <>gP^ZkKշ\H[{#D&L"QAL,NGFAT|0"&UV9 eR-k q1$3x=u{>,On܍,Gau!)^;w2⯼j>( eI~%\TO z/2=jwݷwƥmzgR\oD]Uܞ[?K}5n`A7Xrt[O}4k? ;oǎ?tQJp4"(bd[k3|,6+ZA)fDRe Ĥ(!&%*fXE QC %%]D RaMDE $dtsf@gֶs-*9;IrOjk k|.sϼBg_Ti{2~/v^kc?a{=a3>Ts)NpYm ÞE%$kMml`,aicx4JD?!E՟νQ~^_ĵq5<\(݈ܰu,WsJ2Ί,3,"0%dLD@L,03ބDD (@-VX% $8/!llTMwD"|zu^!]W7^y^G]0y>O.މ"ǿW=cIwv* _f?dIQ#@  \DJUJ@'QtrB(q&CG= h߾M_iUNfr +)ˬ Ș%DltBD1EdcÆ ff"b&"Sj@Db%[Ρobt}[}1U;)i~!fp'o~UQA؟ _FHxm"7^:b[u9 #ܓ_kWil!'^&"lPUaR9*F?ÀAbQXGߛB 85Õا6؄MDUS(L=ZƢ8SYjiS&(!l"Z% f5DShO3Z]A?O0sO"ؚv7֗ G{7zK@y5ڷ}In'4n7lOUnO_U+V`*c`ZTᩄC(a ܄7T=6PmedwIDAT.t҉ T Ʃ׻"MTJ iKɧMElQ"~l#H"!V؈M22HM#@,Ԅ&MCAB*lb5& H5 !%5sG{Quf.R+RG/ᶣ'(I%UWW.GxPE]%́7ܱ'fKF~.|$+k`<|=D7 blQ#>|C!,ԁ@C CHqr4J1Q1Qv߾#r`pԟYd!OVLH#D*y,6T(FGRap3mB4̙*1U#O; q7ۄӷp|$ꄽ*~kjKb }0-i|d(2 Ǯ_ h+j 4@eJ) ApHN@"CpS/1rI >qhzMj;r#4[JBI/T7g9_26>8vW}3ud|܊mhq~$Uz\^- Huه~^ Rx44Y4k'z|d lc}&#W⧾x 7\Ţoe/u >?1Txvw; ~ G' p vq۟dp#mOi3'֕'a]V_;:c*1"$GLvđYS_}%(w-W|~nGv}v5nN-\5Xވc2~vY`^? A~KӍؾLǾ!8*//kLs5=0M>#qqccA%I|1 ]Ƞ[_[,h-7WݷCPkfۄhWo,XmMF}B[oo%\ E_bc n8{O3+k2Gh#PXz$ 6;-jX֒--#N>uu F'0d×@5iPn/@dQg/IWSOGǤt#ЉUr% 9xW]~"QԤqUo5F##(#3mS$_shdoǻB[c}xv\hn> <@3x0Οrb[E|(pngO!ș38yČM6ܹtWsYqYFF4IY"Qcb.ZA ZvjDBldNK'V2\YL+q߲W9KUHZ)sQ&km,5j3F{  =^F uG>}ט932/>.?bڶӦ(s(_qX]=p ۦ69ON@xinBdrIaj"#L ;p LBB. K+%%SRq;o+lGF~*GOZD0&!TF e!*Qu1YzV[3/ƒB>9bJg΀7C jTMYޙ*8W^㜪ffFEfDu mĔ 0% <ȩe#B(Ȍ0JDM))D1 0>7 n]WB {&77߂l".@V$y??GBs<:jFq ADԏ܉?,`+U} .:q>!Jq$f^"V,]–!ǖ~+ja8O𔈟1NA%dBdc j*#SpB##r rd(` "()Q}fF5T8PVq!.B۳u'{܇7+&7c^7uWg?* WXU@JYtShBQRDybrD Y(jCw79AamE=Not̲eW4# 62VMbv֚&6 I 4nf܀mi,'"QF1رpj6.cKk8ۘqn]> ֗Bs'f>dWKk[~@pk T;byax 耰.**lV tg.#<y?(9Sy`b-}=>ùJәϹ:|bhW>a9# GUf.3Vd1Zp$5Jvn]tq$wQYljIF?U:ׁ"$xX9 p`ja`y-I)A>4P9s~) .ҰRjNDtaua+f\_Ea!zݐMmkjU_q|\ {Zk&ztdr!t~*O=s'mX\`C! Hp$ư_J'GLbG{Qn4jd\tvhVmX sц7a"wP)GNo| ږMhA$}q4*6!7 0l鍠ĊFe1(rxJ6/ǴIA>R(9+q/I͢9)3b V {NJיGi|Qڨ㑐a[T#:"5cNKdTx'X ec<r5sR`B́($ʣV5Q 2yi&XL{KWK Ь 42 jBg 0xŪU՛>63? Ə]e,zF{M1}<^@Fuh1^DVx*=NhZ6zWؙFvzk)'̹fRÆfD܀" )QBQySCQ4 '\dm $[ S[W~|X$!kB-h҆ w&7|`o1!X-.Fa*I[@2xS`,} E*a 8Fb@M -m-!d4∠HmU2UA "VX9&?JN|/h nC$gF QIoZ-ӾOCaRb83urALG(5F1# RHRz^4)SE&HP hD "D HTjUapA`K VaXT Q*@PaíC K.mǢ4xں_2Iڛ+3>mB-hc5ww*_uawex Xu!P 0}UM kfDw… Uƹ+7Q ҌMrKTʔA1S& "PjJKE"hHTCH  ѹEFrP"LA@%DwsG@Kx_AC,(E` RU*zQʍ[8*o@mt[h#!z\>$䤹)qg ^E*87T29xh7`DqV}Ir胜)IuX2HT YAB0^BDL! )o H!,TI !E|G6\ 4`jHke؀mf՞cUHb'HH[oɾ{:w`y/F!]8#1d?Rft#w' ؈t=Ű!0cxȎ>*E45pjݘg!# $A@<+;ϸq:l2DFNJ )Q%ՠhØjI f&TDPJ bbDR≭e4k5al]<"! vF!HbhTmPi_ L\?\%X_~bZ7*@Q ﵫe1A. h93RuL/fuYl}W6GOtr4u V8C*$)+!M}(*œ S[+q1L& شQ "0%q}_0uw[wm%|aj*#Wf_Mv%c-gRWibX$ ӏpҩ*B౉fZz9>~{h"zM&jF"Ƣwt_zEKhx}=|hH_ﷆ.:!$_7AwXZ%@Uуɡ2 <9B9D`2]V&Ud+lEe0k5Q9h __ V+QGQbwm{f #Z kb- y)sӽAȰ 9+MRΌэ7RIV!2[!#rHY TM[i+'tS-5#蘢eͩrQv??UhhtNw5%ץv飌{u蠋| Pq 6#r <*_P}B{]:8O=[~':o\Y9@q۝(zHGVC݈g(A,x;Dv#⍢ȷ2( ZHP9*cQ"kUop>Þ4(kI)I1lU+%Pv%gLzdkee?bmvrX/>OW>Ȩ:ԧ;3GK$ Όa*\hfLC"4[nЪd rmjְ۩|謕t$fxQYB'x3;u}D&8IsMnMs-f@I@( G 6XUW<3gԞ{6^+n͘-gTirZ*ڄ +Ǫd#r%j,I^}uБc}QVV2ϯ /S-HbFW0  a^w^םeF v;Ԁ35&dB1(B@Ʉ2J<+JPq<Xl Bm G*9 > lKQ$ipmTq dVC""Ȅ cC&B}dxXx[ 0pC qaY^nj*쁩1 .BΌ'{I ,H0YfL AC(!@J%+J(HPzbBAA"#E(]@NՃ"Gň$NQc>Ŗ mj<JYbW4Vۼ JQᰊ0U_iRy_?'++r;Ęl2΂ CD]$G:,pFqX#hJJmLry==d+rAEmqG{Ê{DaL`=E4,\BbXal7BX  c~C Sj \BlC6$CyB7'1AXL(PT ."pRGF .u1U۳2TgB4j5yC5:." +I؋{=a{=a{=a{=a{{:sIENDB`golang-github-performancecopilot-speed-4.0.0/images/speed.svg000066400000000000000000000411641433655324600243400ustar00rootroot00000000000000 image/svg+xml golang-github-performancecopilot-speed-4.0.0/instance.go000066400000000000000000000015611433655324600234020ustar00rootroot00000000000000package speed // Instances defines a valid collection of instance name and values type Instances map[string]interface{} // Keys collects and returns all the keys in all instance values func (i Instances) Keys() []string { s := make([]string, 0, len(i)) for k := range i { s = append(s, k) } return s } // pcpInstance wraps a PCP compatible Instance type pcpInstance struct { name string id uint32 offset int } // newpcpInstance generates a new Instance type based on the passed parameters // the id is passed explicitly as it is assumed that this will be constructed // after initializing the InstanceDomain // this is not a part of the public API as this is not supposed to be used directly, // but instead added using the AddInstance method of InstanceDomain func newpcpInstance(name string) *pcpInstance { return &pcpInstance{ name, hash(name, 0), 0, } } golang-github-performancecopilot-speed-4.0.0/instance_domain.go000066400000000000000000000074361433655324600247400ustar00rootroot00000000000000package speed import ( "fmt" "github.com/pkg/errors" ) // InstanceDomain defines the interface for an instance domain type InstanceDomain interface { ID() uint32 // unique identifier for the instance domain Name() string // name of the instance domain Description() string // description for the instance domain HasInstance(name string) bool // checks if an instance is in the indom InstanceCount() int // returns the number of instances in the indom Instances() []string // returns a slice of instances in the instance domain } // PCPInstanceDomainBitLength is the maximum bit length of a PCP Instance Domain // // see: https://github.com/performancecopilot/pcp/blob/main/src/include/pcp/impl.h#L102-L121 const PCPInstanceDomainBitLength = 22 // PCPInstanceDomain wraps a PCP compatible instance domain type PCPInstanceDomain struct { id uint32 name string instances map[string]*pcpInstance shortDescription, longDescription string } // NewPCPInstanceDomain creates a new instance domain or returns an already created one for the passed name // NOTE: this is different from parfait's idea of generating ids for InstanceDomains // We simply generate a unique 32 bit hash for an instance domain name, and if it has not // already been created, we create it, otherwise we return the already created version func NewPCPInstanceDomain(name string, instances []string, desc ...string) (*PCPInstanceDomain, error) { if name == "" { return nil, errors.New("Instance Domain name cannot be empty") } if len(desc) > 2 { return nil, errors.New("Only 2 description strings allowed to define an instance domain") } shortDescription, longDescription := "", "" if len(desc) > 0 { shortDescription = desc[0] } if len(desc) > 1 { longDescription = desc[1] } imap := make(map[string]*pcpInstance) for _, instance := range instances { if len(instance) > StringLength { return nil, errors.Errorf("instance name %v is too long", instance) } imap[instance] = newpcpInstance(instance) } return &PCPInstanceDomain{ id: hash(name, PCPInstanceDomainBitLength), name: name, instances: imap, shortDescription: shortDescription, longDescription: longDescription, }, nil } // HasInstance returns true if an instance of the specified name is in the Indom func (indom *PCPInstanceDomain) HasInstance(name string) bool { _, present := indom.instances[name] return present } // ID returns the id for PCPInstanceDomain func (indom *PCPInstanceDomain) ID() uint32 { return indom.id } // Name returns the name for PCPInstanceDomain func (indom *PCPInstanceDomain) Name() string { return indom.name } // InstanceCount returns the number of instances in the current instance domain func (indom *PCPInstanceDomain) InstanceCount() int { return len(indom.instances) } // Instances returns a slice of defined instances for the instance domain func (indom *PCPInstanceDomain) Instances() []string { ans, i := make([]string, len(indom.instances)), 0 for k := range indom.instances { ans[i] = k i++ } return ans } // MatchInstances returns true if the passed InstanceDomain // has exactly the same instances as the passed array func (indom *PCPInstanceDomain) MatchInstances(ins []string) bool { if len(ins) != len(indom.instances) { return false } for _, i := range ins { if _, ok := indom.instances[i]; !ok { return false } } return true } // Description returns the description for PCPInstanceDomain func (indom *PCPInstanceDomain) Description() string { return indom.shortDescription + "\n" + indom.longDescription } func (indom *PCPInstanceDomain) String() string { return fmt.Sprintf("%s%v", indom.name, indom.Instances()) } golang-github-performancecopilot-speed-4.0.0/metrics.go000066400000000000000000001200171433655324600232420ustar00rootroot00000000000000package speed import ( "fmt" "math" "strconv" "sync" "time" histogram "github.com/HdrHistogram/hdrhistogram-go" "github.com/pkg/errors" "github.com/performancecopilot/speed/v4/bytewriter" ) // MetricType is an enumerated type representing all valid types for a metric. type MetricType int32 // Possible values for a MetricType. const ( Int32Type MetricType = iota Uint32Type Int64Type Uint64Type FloatType DoubleType StringType ) //go:generate stringer -type=MetricType func (m MetricType) isCompatibleInt(val int) bool { v := int64(val) switch m { case Int32Type: return v >= math.MinInt32 && v <= math.MaxInt32 case Int64Type: return true case Uint32Type: return v >= 0 && v <= math.MaxUint32 case Uint64Type: return v >= 0 } return false } func (m MetricType) isCompatibleUint(val uint) bool { switch { case val <= math.MaxUint32: return m == Uint32Type || m == Uint64Type default: return m == Uint64Type } } func (m MetricType) isCompatibleFloat(val float64) bool { switch { case val >= -math.MaxFloat32 && val <= math.MaxFloat32: return m == FloatType || m == DoubleType default: return m == DoubleType } } // IsCompatible checks if the passed value is compatible with the current MetricType. func (m MetricType) IsCompatible(val interface{}) bool { switch v := val.(type) { case int: return m.isCompatibleInt(v) case int32: return m == Int32Type case int64: return m == Int64Type case uint: return m.isCompatibleUint(v) case uint32: return m == Uint32Type case uint64: return m == Uint64Type case float32: return m == FloatType case float64: return m.isCompatibleFloat(v) case string: return m == StringType } return false } // resolveInt will resolve an int to one of the 4 compatible types. func (m MetricType) resolveInt(val interface{}) interface{} { if vi, isInt := val.(int); isInt { switch m { case Int64Type: return int64(vi) case Uint32Type: return uint32(vi) case Uint64Type: return uint64(vi) } return int32(val.(int)) } if vui, isUint := val.(uint); isUint { if m == Uint64Type { return uint64(vui) } return uint32(vui) } return val } // resolveFloat will resolve a float64 to one of the 2 compatible types. func (m MetricType) resolveFloat(val interface{}) interface{} { _, isFloat64 := val.(float64) if isFloat64 && m == FloatType { return float32(val.(float64)) } return val } func (m MetricType) resolve(val interface{}) interface{} { val = m.resolveInt(val) val = m.resolveFloat(val) return val } /////////////////////////////////////////////////////////////////////////////// // MetricUnit defines the interface for a unit type for speed. type MetricUnit interface { fmt.Stringer // return 32 bit PMAPI representation for the unit // see: https://github.com/performancecopilot/pcp/blob/main/src/include/pcp/pmapi.h#L61-L101 PMAPI() uint32 // add a space unit to the current unit at a specific dimension Space(SpaceUnit, int8) MetricUnit // add a time unit to the current unit at a specific dimension Time(TimeUnit, int8) MetricUnit // add a count unit to the current unit at a specific dimension Count(CountUnit, int8) MetricUnit } // internal struct for supporting composite units, // based on the implementation inside hornet // // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#327 type metricUnit struct { repr uint32 } // NewMetricUnit returns a new object for initialization func NewMetricUnit() MetricUnit { return &metricUnit{} } func (m *metricUnit) PMAPI() uint32 { return m.repr } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#375 func (m *metricUnit) Space(s SpaceUnit, dimension int8) MetricUnit { if dimension < -8 || dimension > 7 { panic("dimension has to be between -8 and 7 inclusive") } m.repr |= uint32(s) m.repr |= (uint32(dimension) & 0xF) << 28 return m } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#383 func (m *metricUnit) Time(t TimeUnit, dimension int8) MetricUnit { if dimension < -8 || dimension > 7 { panic("dimension has to be between -8 and 7 inclusive") } m.repr |= uint32(t) m.repr |= (uint32(dimension) & 0xF) << 24 return m } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#391 func (m *metricUnit) Count(c CountUnit, dimension int8) MetricUnit { if dimension < -8 || dimension > 7 { panic("dimension has to be between -8 and 7 inclusive") } m.repr |= uint32(c) m.repr |= (uint32(dimension) & 0xF) << 20 return m } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#410 func (m *metricUnit) SpaceDim() int8 { return int8(int32(m.repr) >> 28) } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#398 func (m *metricUnit) SpaceScale() SpaceUnit { d := m.SpaceDim() if d == 0 { panic("no space scale on unit") } return SpaceUnit(1<<28 | (uint32((m.repr>>16)&0xF))<<16) } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#410 func (m *metricUnit) TimeDim() int8 { return int8(int32(m.repr<<4) >> 28) } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#398 func (m *metricUnit) TimeScale() TimeUnit { d := m.TimeDim() if d == 0 { panic("no time scale on unit") } return TimeUnit(1<<24 | (uint32((m.repr>>12)&0xF))<<12) } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#410 func (m *metricUnit) CountDim() int8 { return int8(int32(m.repr<<8) >> 28) } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#398 func (m *metricUnit) CountScale() CountUnit { d := m.CountDim() if d == 0 { panic("no count scale on unit") } return CountUnit(1<<20 | (uint32((m.repr>>8)&0xF))>>8) } func (m *metricUnit) String() string { sd, td, cd := m.SpaceDim(), m.TimeDim(), m.CountDim() ans := "" if sd != 0 { ans = ans + m.SpaceScale().String() + "^" + strconv.Itoa(int(m.SpaceDim())) } if td != 0 { ans = ans + m.TimeScale().String() + "^" + strconv.Itoa(int(m.TimeDim())) } if cd != 0 { ans = ans + m.CountScale().String() + "^" + strconv.Itoa(int(m.CountDim())) } return ans } // SpaceUnit is an enumerated type representing all units for space. type SpaceUnit uint32 // Possible values for SpaceUnit. const ( ByteUnit SpaceUnit = 1<<28 | iota<<16 KilobyteUnit MegabyteUnit GigabyteUnit TerabyteUnit PetabyteUnit ExabyteUnit ) //go:generate stringer -type=SpaceUnit // PMAPI returns the PMAPI representation for a SpaceUnit // for space units bits 0-3 are 1 and bits 13-16 are scale func (s SpaceUnit) PMAPI() uint32 { return uint32(s) } // Space adds a space unit to the current unit at a specific dimension func (s SpaceUnit) Space(SpaceUnit, int8) MetricUnit { panic("Cannot add another space unit") } // Time adds a time unit to the current unit at a specific dimension func (s SpaceUnit) Time(t TimeUnit, dimension int8) MetricUnit { return (&metricUnit{uint32(s)}).Time(t, dimension) } // Count adds a count unit to the current unit at a specific dimension func (s SpaceUnit) Count(c CountUnit, dimension int8) MetricUnit { return (&metricUnit{uint32(s)}).Count(c, dimension) } // TimeUnit is an enumerated type representing all possible units for representing time. type TimeUnit uint32 // Possible Values for TimeUnit. // for time units bits 4-7 are 1 and bits 17-20 are scale. const ( NanosecondUnit TimeUnit = 1<<24 | iota<<12 MicrosecondUnit MillisecondUnit SecondUnit MinuteUnit HourUnit ) //go:generate stringer -type=TimeUnit // PMAPI returns the PMAPI representation for a TimeUnit. func (t TimeUnit) PMAPI() uint32 { return uint32(t) } // Space adds a space unit to the current unit at a specific dimension func (t TimeUnit) Space(s SpaceUnit, dimension int8) MetricUnit { return (&metricUnit{uint32(t)}).Space(s, dimension) } // Time adds a time unit to the current unit at a specific dimension func (t TimeUnit) Time(TimeUnit, int8) MetricUnit { panic("Cannot add another time unit") } // Count adds a count unit to the current unit at a specific dimension func (t TimeUnit) Count(c CountUnit, dimension int8) MetricUnit { return (&metricUnit{uint32(t)}).Count(c, dimension) } // CountUnit is a type representing a counted quantity. type CountUnit uint32 // OneUnit represents the only CountUnit. // For count units bits 8-11 are 1 and bits 21-24 are scale. const OneUnit CountUnit = 1<<20 | iota<<8 //go:generate stringer -type=CountUnit // PMAPI returns the PMAPI representation for a CountUnit. func (c CountUnit) PMAPI() uint32 { return uint32(c) } // Space adds a space unit to the current unit at a specific dimension func (c CountUnit) Space(s SpaceUnit, dimension int8) MetricUnit { return (&metricUnit{uint32(c)}).Space(s, dimension) } // Time adds a time unit to the current unit at a specific dimension func (c CountUnit) Time(t TimeUnit, dimension int8) MetricUnit { return (&metricUnit{uint32(c)}).Time(t, dimension) } // Count adds a count unit to the current unit at a specific dimension func (c CountUnit) Count(CountUnit, int8) MetricUnit { panic("Cannot add another time unit") } /////////////////////////////////////////////////////////////////////////////// // MetricSemantics represents an enumerated type representing the possible // values for the semantics of a metric. type MetricSemantics int32 // Possible values for MetricSemantics. const ( NoSemantics MetricSemantics = iota CounterSemantics _ InstantSemantics DiscreteSemantics ) //go:generate stringer -type=MetricSemantics /////////////////////////////////////////////////////////////////////////////// // Metric defines the general interface a type needs to implement to qualify // as a valid PCP metric. type Metric interface { // gets the unique id generated for this metric ID() uint32 // gets the name for the metric Name() string // gets the type of a metric Type() MetricType // gets the unit of a metric Unit() MetricUnit // gets the semantics for a metric Semantics() MetricSemantics // gets the description of a metric Description() string } /////////////////////////////////////////////////////////////////////////////// // SingletonMetric defines the interface for a metric that stores only one value. type SingletonMetric interface { Metric // gets the value of the metric Val() interface{} // sets the value of the metric to a value, optionally returns an error on failure Set(interface{}) error // tries to set and panics on error MustSet(interface{}) } /////////////////////////////////////////////////////////////////////////////// // InstanceMetric defines the interface for a metric that stores multiple values // in instances and instance domains. type InstanceMetric interface { Metric // gets the value of a particular instance ValInstance(string) (interface{}, error) // sets the value of a particular instance SetInstance(interface{}, string) error // tries to set the value of a particular instance and panics on error MustSetInstance(interface{}, string) // returns a slice containing all instances in the metric Instances() []string } /////////////////////////////////////////////////////////////////////////////// // PCPMetric defines the interface for a metric that is compatible with PCP. type PCPMetric interface { Metric // a PCPMetric will always have an instance domain, even if it is nil Indom() *PCPInstanceDomain ShortDescription() string LongDescription() string } /////////////////////////////////////////////////////////////////////////////// // PCPMetricItemBitLength is the maximum bit size of a PCP Metric id. // // see: https://github.com/performancecopilot/pcp/blob/main/src/include/pcp/impl.h#L102-L121 const PCPMetricItemBitLength = 10 // pcpMetricDesc is a metric metadata wrapper // each metric type can wrap its metadata by containing a pcpMetricDesc type and // only define its own specific properties assuming pcpMetricDesc will handle the rest. // // when writing, this type is supposed to map directly to the pmDesc struct as defined in PCP core. type pcpMetricDesc struct { id uint32 // unique metric id name string // the name t MetricType // the type of a metric sem MetricSemantics // the semantics u MetricUnit // the unit shortDescription, longDescription string } // newpcpMetricDesc creates a new Metric Description wrapper type. func newpcpMetricDesc(n string, t MetricType, s MetricSemantics, u MetricUnit, desc ...string) (*pcpMetricDesc, error) { if n == "" { return nil, errors.New("Metric name cannot be empty") } if len(n) > StringLength { return nil, errors.New("metric name is too long") } if len(desc) > 2 { return nil, errors.New("only 2 optional strings allowed, short and long descriptions") } shortdesc, longdesc := "", "" if len(desc) > 0 { shortdesc = desc[0] } if len(desc) > 1 { longdesc = desc[1] } return &pcpMetricDesc{ hash(n, PCPMetricItemBitLength), n, t, s, u, shortdesc, longdesc, }, nil } // ID returns the generated id for PCPMetric. func (md *pcpMetricDesc) ID() uint32 { return md.id } // Name returns the generated id for PCPMetric. func (md *pcpMetricDesc) Name() string { return md.name } // Semantics returns the current stored value for PCPMetric. func (md *pcpMetricDesc) Semantics() MetricSemantics { return md.sem } // Unit returns the unit for PCPMetric. func (md *pcpMetricDesc) Unit() MetricUnit { return md.u } // Type returns the type for PCPMetric. func (md *pcpMetricDesc) Type() MetricType { return md.t } // ShortDescription returns the shortdesc value. func (md *pcpMetricDesc) ShortDescription() string { return md.shortDescription } // LongDescription returns the longdesc value. func (md *pcpMetricDesc) LongDescription() string { return md.longDescription } // Description returns the description for PCPMetric. func (md *pcpMetricDesc) Description() string { return md.shortDescription + "\n" + md.longDescription } /////////////////////////////////////////////////////////////////////////////// // updateClosure is a closure that will write the modified value of a metric on disk. type updateClosure func(interface{}) error // newupdateClosure creates a new update closure for an offset, type and buffer. func newupdateClosure(offset int, writer bytewriter.Writer) updateClosure { return func(val interface{}) error { if _, isString := val.(string); isString { writer.MustWrite(make([]byte, StringLength), offset) } _, err := writer.WriteVal(val, offset) return err } } /////////////////////////////////////////////////////////////////////////////// // pcpSingletonMetric defines an embeddable base singleton metric. type pcpSingletonMetric struct { *pcpMetricDesc val interface{} update updateClosure } // newpcpSingletonMetric creates a new instance of pcpSingletonMetric. func newpcpSingletonMetric(val interface{}, desc *pcpMetricDesc) (*pcpSingletonMetric, error) { if !desc.t.IsCompatible(val) { return nil, errors.Errorf("type %v is not compatible with value %v(%T)", desc.t, val, val) } val = desc.t.resolve(val) return &pcpSingletonMetric{desc, val, nil}, nil } // set Sets the current value of pcpSingletonMetric. func (m *pcpSingletonMetric) set(val interface{}) error { if !m.t.IsCompatible(val) { return errors.Errorf("value %v is incompatible with MetricType %v", val, m.t) } val = m.t.resolve(val) if val != m.val { if m.update != nil { err := m.update(val) if err != nil { return err } } m.val = val } return nil } func (m *pcpSingletonMetric) Indom() *PCPInstanceDomain { return nil } /////////////////////////////////////////////////////////////////////////////// // PCPSingletonMetric defines a singleton metric with no instance domain // only a value and a valueoffset. type PCPSingletonMetric struct { *pcpSingletonMetric mutex sync.RWMutex } // NewPCPSingletonMetric creates a new instance of PCPSingletonMetric // it takes 2 extra optional strings as short and long description parameters, // which on not being present are set to blank strings. func NewPCPSingletonMetric(val interface{}, name string, t MetricType, s MetricSemantics, u MetricUnit, desc ...string) (*PCPSingletonMetric, error) { d, err := newpcpMetricDesc(name, t, s, u, desc...) if err != nil { return nil, err } sm, err := newpcpSingletonMetric(val, d) if err != nil { return nil, err } return &PCPSingletonMetric{sm, sync.RWMutex{}}, nil } // Val returns the current Set value of PCPSingletonMetric. func (m *PCPSingletonMetric) Val() interface{} { m.mutex.RLock() defer m.mutex.RUnlock() return m.val } // Set Sets the current value of PCPSingletonMetric. func (m *PCPSingletonMetric) Set(val interface{}) error { m.mutex.Lock() defer m.mutex.Unlock() return m.set(val) } // MustSet is a Set that panics on failure. func (m *PCPSingletonMetric) MustSet(val interface{}) { if err := m.Set(val); err != nil { panic(err) } } func (m *PCPSingletonMetric) String() string { return fmt.Sprintf("Val: %v\n%v", m.val, m.Description()) } /////////////////////////////////////////////////////////////////////////////// // Counter defines a metric that holds a single value that can only be incremented. type Counter interface { Metric Val() int64 Set(int64) error Inc(int64) error MustInc(int64) Up() // same as MustInc(1) } /////////////////////////////////////////////////////////////////////////////// // PCPCounter implements a PCP compatible Counter Metric. type PCPCounter struct { *pcpSingletonMetric mutex sync.RWMutex } // NewPCPCounter creates a new PCPCounter instance. // It requires an initial int64 value and a metric name for construction. // optionally it can also take a couple of description strings that are used as // short and long descriptions respectively. // Internally it creates a PCP SingletonMetric with Int64Type, CounterSemantics // and CountUnit. func NewPCPCounter(val int64, name string, desc ...string) (*PCPCounter, error) { d, err := newpcpMetricDesc(name, Int64Type, CounterSemantics, OneUnit, desc...) if err != nil { return nil, err } sm, err := newpcpSingletonMetric(val, d) if err != nil { return nil, err } return &PCPCounter{sm, sync.RWMutex{}}, nil } // Val returns the current value of the counter. func (c *PCPCounter) Val() int64 { c.mutex.RLock() defer c.mutex.RUnlock() return c.val.(int64) } // Set sets the value of the counter. func (c *PCPCounter) Set(val int64) error { c.mutex.Lock() defer c.mutex.Unlock() v := c.val.(int64) if val < v { return errors.Errorf("cannot set counter to %v, current value is %v and PCP counters cannot go backwards", val, v) } return c.set(val) } // Inc increases the stored counter's value by the passed increment. func (c *PCPCounter) Inc(val int64) error { c.mutex.Lock() defer c.mutex.Unlock() if val < 0 { return errors.New("cannot decrement a counter") } if val == 0 { return nil } v := c.val.(int64) v += val return c.set(v) } // MustInc is Inc that panics on failure. func (c *PCPCounter) MustInc(val int64) { if err := c.Inc(val); err != nil { panic(err) } } // Up increases the counter by 1. func (c *PCPCounter) Up() { c.MustInc(1) } /////////////////////////////////////////////////////////////////////////////// // Gauge defines a metric that holds a single double value that can be // incremented or decremented. type Gauge interface { Metric Val() float64 Set(float64) error MustSet(float64) Inc(float64) error Dec(float64) error MustInc(float64) MustDec(float64) } /////////////////////////////////////////////////////////////////////////////// // PCPGauge defines a PCP compatible Gauge metric type PCPGauge struct { *pcpSingletonMetric mutex sync.RWMutex } // NewPCPGauge creates a new PCPGauge instance. // Tt requires an initial float64 value and a metric name for construction. // Optionally it can also take a couple of description strings that are used as // short and long descriptions respectively. // Internally it creates a PCP SingletonMetric with DoubleType, InstantSemantics // and CountUnit. func NewPCPGauge(val float64, name string, desc ...string) (*PCPGauge, error) { d, err := newpcpMetricDesc(name, DoubleType, InstantSemantics, OneUnit, desc...) if err != nil { return nil, err } sm, err := newpcpSingletonMetric(val, d) if err != nil { return nil, err } return &PCPGauge{sm, sync.RWMutex{}}, nil } // Val returns the current value of the Gauge. func (g *PCPGauge) Val() float64 { g.mutex.RLock() defer g.mutex.RUnlock() return g.val.(float64) } // Set sets the current value of the Gauge. func (g *PCPGauge) Set(val float64) error { g.mutex.Lock() defer g.mutex.Unlock() return g.set(val) } // MustSet will panic if Set fails. func (g *PCPGauge) MustSet(val float64) { if err := g.Set(val); err != nil { panic(err) } } // Inc adds a value to the existing Gauge value. func (g *PCPGauge) Inc(val float64) error { g.mutex.Lock() defer g.mutex.Unlock() if val == 0 { return nil } v := g.val.(float64) return g.set(v + val) } // MustInc will panic if Inc fails. func (g *PCPGauge) MustInc(val float64) { if err := g.Inc(val); err != nil { panic(err) } } // Dec adds a value to the existing Gauge value. func (g *PCPGauge) Dec(val float64) error { return g.Inc(-val) } // MustDec will panic if Dec fails. func (g *PCPGauge) MustDec(val float64) { if err := g.Dec(val); err != nil { panic(err) } } /////////////////////////////////////////////////////////////////////////////// // Timer defines a metric that accumulates time periods // Start signals the beginning of monitoring. // End signals the end of monitoring and adding the elapsed time to the // accumulated time, and returning it. type Timer interface { Metric Start() error Stop() (float64, error) } /////////////////////////////////////////////////////////////////////////////// // PCPTimer implements a PCP compatible Timer // It also functionally implements a metric with elapsed type from PCP type PCPTimer struct { *pcpSingletonMetric mutex sync.Mutex started bool since time.Time } // NewPCPTimer creates a new PCPTimer instance of the specified unit. // It requires a metric name and a TimeUnit for construction. // It can optionally take a couple of description strings. // Internally it uses a PCP SingletonMetric. func NewPCPTimer(name string, unit TimeUnit, desc ...string) (*PCPTimer, error) { d, err := newpcpMetricDesc(name, DoubleType, DiscreteSemantics, unit, desc...) if err != nil { return nil, err } sm, err := newpcpSingletonMetric(float64(0), d) if err != nil { return nil, err } return &PCPTimer{sm, sync.Mutex{}, false, time.Time{}}, nil } // Reset resets the timer to 0 func (t *PCPTimer) Reset() error { t.mutex.Lock() defer t.mutex.Unlock() if t.started { return errors.New("trying to reset an already started timer") } return t.set(float64(0)) } // Start signals the timer to start monitoring. func (t *PCPTimer) Start() error { t.mutex.Lock() defer t.mutex.Unlock() if t.started { return errors.New("trying to start an already started timer") } t.since = time.Now() t.started = true return nil } // Stop signals the timer to end monitoring and return elapsed time so far. func (t *PCPTimer) Stop() (float64, error) { t.mutex.Lock() defer t.mutex.Unlock() if !t.started { return 0, errors.New("trying to stop a stopped timer") } d := time.Since(t.since) var inc float64 switch t.pcpMetricDesc.Unit() { case NanosecondUnit: inc = float64(d.Nanoseconds()) case MicrosecondUnit: inc = float64(d.Nanoseconds()) * 1e-3 case MillisecondUnit: inc = float64(d.Nanoseconds()) * 1e-6 case SecondUnit: inc = d.Seconds() case MinuteUnit: inc = d.Minutes() case HourUnit: inc = d.Hours() } v := t.val.(float64) err := t.set(v + inc) if err != nil { return -1, err } t.started = false return v + inc, nil } /////////////////////////////////////////////////////////////////////////////// type instanceValue struct { val interface{} update updateClosure } func newinstanceValue(val interface{}) *instanceValue { return &instanceValue{val, nil} } // pcpInstanceMetric represents a PCPMetric that can have multiple values // over multiple instances in an instance domain. type pcpInstanceMetric struct { *pcpMetricDesc indom *PCPInstanceDomain vals map[string]*instanceValue } // newpcpInstanceMetric creates a new instance of PCPSingletonMetric. func newpcpInstanceMetric(vals Instances, indom *PCPInstanceDomain, desc *pcpMetricDesc) (*pcpInstanceMetric, error) { if len(vals) != indom.InstanceCount() { return nil, errors.New("values for all instances in the instance domain only should be passed") } mvals := make(map[string]*instanceValue) for name := range indom.instances { val, present := vals[name] if !present { return nil, errors.Errorf("Instance %v not initialized", name) } if !desc.t.IsCompatible(val) { return nil, errors.Errorf("value %v is incompatible with type %v for Instance %v", val, desc.t, name) } val = desc.t.resolve(val) mvals[name] = newinstanceValue(val) } return &pcpInstanceMetric{desc, indom, mvals}, nil } func (m *pcpInstanceMetric) valInstance(instance string) (interface{}, error) { if !m.indom.HasInstance(instance) { return nil, errors.Errorf("%v is not an instance of this metric", instance) } return m.vals[instance].val, nil } // setInstance sets the value for a particular instance of the metric. func (m *pcpInstanceMetric) setInstance(val interface{}, instance string) error { if !m.t.IsCompatible(val) { return errors.New("the value is incompatible with this metrics MetricType") } if !m.indom.HasInstance(instance) { return errors.Errorf("%v is not an instance of this metric", instance) } val = m.t.resolve(val) if m.vals[instance].val != val { if m.vals[instance].update != nil { err := m.vals[instance].update(val) if err != nil { return err } } m.vals[instance].val = val } return nil } // Indom returns the instance domain for the metric. func (m *pcpInstanceMetric) Indom() *PCPInstanceDomain { return m.indom } // Instances returns a slice containing all instances in the InstanceMetric. // Basically a shorthand for metric.Indom().Instances(). func (m *pcpInstanceMetric) Instances() []string { return m.indom.Instances() } /////////////////////////////////////////////////////////////////////////////// // PCPInstanceMetric represents a PCPMetric that can have multiple values // over multiple instances in an instance domain. type PCPInstanceMetric struct { *pcpInstanceMetric mutex sync.RWMutex } // NewPCPInstanceMetric creates a new instance of PCPSingletonMetric. // it takes 2 extra optional strings as short and long description parameters, // which on not being present are set to empty strings. func NewPCPInstanceMetric(vals Instances, name string, indom *PCPInstanceDomain, t MetricType, s MetricSemantics, u MetricUnit, desc ...string) (*PCPInstanceMetric, error) { d, err := newpcpMetricDesc(name, t, s, u, desc...) if err != nil { return nil, err } im, err := newpcpInstanceMetric(vals, indom, d) if err != nil { return nil, err } return &PCPInstanceMetric{im, sync.RWMutex{}}, nil } // ValInstance returns the value for a particular instance of the metric. func (m *PCPInstanceMetric) ValInstance(instance string) (interface{}, error) { m.mutex.RLock() defer m.mutex.RUnlock() return m.valInstance(instance) } // SetInstance sets the value for a particular instance of the metric. func (m *PCPInstanceMetric) SetInstance(val interface{}, instance string) error { m.mutex.Lock() defer m.mutex.Unlock() return m.setInstance(val, instance) } // MustSetInstance is a SetInstance that panics. func (m *PCPInstanceMetric) MustSetInstance(val interface{}, instance string) { if err := m.SetInstance(val, instance); err != nil { panic(err) } } /////////////////////////////////////////////////////////////////////////////// // CounterVector defines a Counter on multiple instances. type CounterVector interface { Metric Val(string) (int64, error) Set(int64, string) error MustSet(int64, string) SetAll(int64) Inc(int64, string) error MustInc(int64, string) IncAll(int64) Up(string) UpAll() } /////////////////////////////////////////////////////////////////////////////// func generateInstanceMetric(vals map[string]interface{}, name string, instances []string, t MetricType, s MetricSemantics, u MetricUnit, desc ...string) (*pcpInstanceMetric, error) { indomname := name + ".indom" indom, err := NewPCPInstanceDomain(indomname, instances) if err != nil { return nil, errors.Errorf("cannot create indom, error: %v", err) } d, err := newpcpMetricDesc(name, t, s, u, desc...) if err != nil { return nil, err } return newpcpInstanceMetric(vals, indom, d) } // PCPCounterVector implements a CounterVector type PCPCounterVector struct { *pcpInstanceMetric mutex sync.RWMutex } // NewPCPCounterVector creates a new instance of a PCPCounterVector. // it requires a metric name and a set of instance names and values as a map. // it can optionally accept a couple of strings as short and long descriptions // of the metric. // Internally it uses a PCP InstanceMetric with Int64Type, CounterSemantics and CountUnit. func NewPCPCounterVector(values map[string]int64, name string, desc ...string) (*PCPCounterVector, error) { vals := make(Instances) for k, v := range values { vals[k] = v } im, err := generateInstanceMetric(vals, name, vals.Keys(), Int64Type, CounterSemantics, OneUnit, desc...) if err != nil { return nil, err } return &PCPCounterVector{im, sync.RWMutex{}}, nil } // Val returns the value of a particular instance of PCPCounterVector. func (c *PCPCounterVector) Val(instance string) (int64, error) { c.mutex.RLock() defer c.mutex.RUnlock() v, err := c.valInstance(instance) if err != nil { return 0, err } return v.(int64), nil } // Set sets the value of a particular instance of PCPCounterVector. func (c *PCPCounterVector) Set(val int64, instance string) error { c.mutex.Lock() defer c.mutex.Unlock() v, err := c.valInstance(instance) if err != nil { return err } if val < v.(int64) { return errors.Errorf("cannot set instance %s to a lesser value %v", instance, val) } return c.setInstance(val, instance) } // MustSet panics if Set fails. func (c *PCPCounterVector) MustSet(val int64, instance string) { if err := c.Set(val, instance); err != nil { panic(err) } } // SetAll sets all instances to the same value and panics on an error. func (c *PCPCounterVector) SetAll(val int64) { for ins := range c.indom.instances { c.MustSet(val, ins) } } // Inc increments the value of a particular instance of PCPCounterVector. func (c *PCPCounterVector) Inc(inc int64, instance string) error { c.mutex.Lock() defer c.mutex.Unlock() if inc < 0 { return errors.New("increment cannot be negative") } if inc == 0 { return nil } v, err := c.valInstance(instance) if err != nil { return err } return c.setInstance(v.(int64)+inc, instance) } // MustInc panics if Inc fails. func (c *PCPCounterVector) MustInc(inc int64, instance string) { if err := c.Inc(inc, instance); err != nil { panic(err) } } // IncAll increments all instances by the same value and panics on an error. func (c *PCPCounterVector) IncAll(val int64) { for ins := range c.indom.instances { c.MustInc(val, ins) } } // Up increments the value of a particular instance ny 1. func (c *PCPCounterVector) Up(instance string) { c.MustInc(1, instance) } // UpAll ups all instances and panics on an error. func (c *PCPCounterVector) UpAll() { c.IncAll(1) } /////////////////////////////////////////////////////////////////////////////// // GaugeVector defines a Gauge on multiple instances type GaugeVector interface { Metric Val(string) (float64, error) Set(float64, string) error MustSet(float64, string) SetAll(float64) Inc(float64, string) error MustInc(float64, string) IncAll(float64) Dec(float64, string) error MustDec(float64, string) DecAll(float64) } /////////////////////////////////////////////////////////////////////////////// // PCPGaugeVector implements a GaugeVector type PCPGaugeVector struct { *pcpInstanceMetric mutex sync.RWMutex } // NewPCPGaugeVector creates a new instance of a PCPGaugeVector. // It requires a name and map of instance names to their values. // Optionally, it can also accept a couple of strings providing more details // about the metric. func NewPCPGaugeVector(values map[string]float64, name string, desc ...string) (*PCPGaugeVector, error) { vals := make(Instances) for k, v := range values { vals[k] = v } im, err := generateInstanceMetric(vals, name, vals.Keys(), DoubleType, InstantSemantics, OneUnit, desc...) if err != nil { return nil, err } return &PCPGaugeVector{im, sync.RWMutex{}}, nil } // Val returns the value of a particular instance of PCPGaugeVector func (g *PCPGaugeVector) Val(instance string) (float64, error) { g.mutex.RLock() defer g.mutex.RUnlock() val, err := g.valInstance(instance) if err != nil { return 0, err } return val.(float64), nil } // Set sets the value of a particular instance of PCPGaugeVector func (g *PCPGaugeVector) Set(val float64, instance string) error { g.mutex.Lock() defer g.mutex.Unlock() return g.setInstance(val, instance) } // MustSet panics if Set fails func (g *PCPGaugeVector) MustSet(val float64, instance string) { if err := g.Set(val, instance); err != nil { panic(err) } } // SetAll sets all instances to the same value and panics on an error func (g *PCPGaugeVector) SetAll(val float64) { for ins := range g.indom.instances { g.MustSet(val, ins) } } // Inc increments the value of a particular instance of PCPGaugeVector func (g *PCPGaugeVector) Inc(inc float64, instance string) error { g.mutex.Lock() defer g.mutex.Unlock() v, err := g.valInstance(instance) if err != nil { return err } return g.setInstance(v.(float64)+inc, instance) } // MustInc panics if Inc fails func (g *PCPGaugeVector) MustInc(inc float64, instance string) { if err := g.Inc(inc, instance); err != nil { panic(err) } } // IncAll increments all instances by the same value and panics on an error func (g *PCPGaugeVector) IncAll(val float64) { for ins := range g.indom.instances { g.MustInc(val, ins) } } // Dec decrements the value of a particular instance of PCPGaugeVector func (g *PCPGaugeVector) Dec(inc float64, instance string) error { return g.Inc(-inc, instance) } // MustDec panics if Dec fails func (g *PCPGaugeVector) MustDec(inc float64, instance string) { g.MustInc(-inc, instance) } // DecAll decrements all instances by the same value and panics on an error func (g *PCPGaugeVector) DecAll(val float64) { g.IncAll(-val) } /////////////////////////////////////////////////////////////////////////////// // Histogram defines a metric that records a distribution of data type Histogram interface { Max() int64 // Maximum value recorded so far Min() int64 // Minimum value recorded so far High() int64 // Highest allowed value Low() int64 // Lowest allowed value Record(int64) error // Records a new value RecordN(int64, int64) error // Records multiple instances of the same value MustRecord(int64) MustRecordN(int64, int64) Mean() float64 // Mean of all recorded data Variance() float64 // Variance of all recorded data StandardDeviation() float64 // StandardDeviation of all recorded data Percentile(float64) int64 // Percentile returns the value at the passed percentile } /////////////////////////////////////////////////////////////////////////////// // PCPHistogram implements a histogram for PCP backed by the coda hale hdrhistogram // https://github.com/HdrHistogram/hdrhistogram-go type PCPHistogram struct { *pcpInstanceMetric mutex sync.RWMutex h *histogram.Histogram } // the maximum and minimum values that can be recorded by a histogram const ( HistogramMin = 0 HistogramMax = 3600000000 ) func normalize(low, high int64, sigfigures int) (int64, int64, int) { if low < HistogramMin { low = HistogramMin } if low > HistogramMax { low = HistogramMax } if high < HistogramMin { high = HistogramMin } if high > HistogramMax { high = HistogramMax } if sigfigures < 1 { sigfigures = 1 } if sigfigures > 5 { sigfigures = 5 } return low, high, sigfigures } // NewPCPHistogram returns a new instance of PCPHistogram. // The lowest value for `low` is 0. // The highest value for `high` is 3,600,000,000. // `low` **must** be less than `high`. // The value of `sigfigures` can be between 1 and 5. // It also requires a unit to be explicitly passed for construction. // Optionally, a couple of description strings may be passed as the short and // long descriptions of the metric. func NewPCPHistogram(name string, low, high int64, sigfigures int, unit MetricUnit, desc ...string) (*PCPHistogram, error) { if low > high { return nil, errors.New("low cannot be larger than high") } low, high, sigfigures = normalize(low, high, sigfigures) h := histogram.New(low, high, sigfigures) vals := make(Instances) for _, s := range histogramInstances { vals[s] = float64(0) } d, err := newpcpMetricDesc(name, DoubleType, InstantSemantics, unit, desc...) if err != nil { return nil, err } m, err := newpcpInstanceMetric(vals, histogramIndom, d) if err != nil { return nil, err } return &PCPHistogram{m, sync.RWMutex{}, h}, nil } // High returns the maximum recordable value. func (h *PCPHistogram) High() int64 { return h.h.LowestTrackableValue() } // Low returns the minimum recordable value. func (h *PCPHistogram) Low() int64 { return h.h.HighestTrackableValue() } // Max returns the maximum recorded value so far. func (h *PCPHistogram) Max() int64 { h.mutex.RLock() defer h.mutex.RUnlock() return int64(h.vals["max"].val.(float64)) } // Min returns the minimum recorded value so far. func (h *PCPHistogram) Min() int64 { h.mutex.RLock() defer h.mutex.RUnlock() return int64(h.vals["min"].val.(float64)) } func (h *PCPHistogram) update() error { updateinstance := func(instance string, val float64) error { if h.vals[instance].val != val { return h.setInstance(val, instance) } return nil } if err := updateinstance("min", float64(h.h.Min())); err != nil { return err } if err := updateinstance("max", float64(h.h.Max())); err != nil { return err } if err := updateinstance("mean", h.h.Mean()); err != nil { return err } stddev := h.h.StdDev() if err := updateinstance("standard_deviation", stddev); err != nil { return err } if err := updateinstance("variance", stddev*stddev); err != nil { return err } return nil } // Record records a new value. func (h *PCPHistogram) Record(val int64) error { h.mutex.Lock() defer h.mutex.Unlock() err := h.h.RecordValue(val) if err != nil { return err } return h.update() } // MustRecord panics if Record fails. func (h *PCPHistogram) MustRecord(val int64) { if err := h.Record(val); err != nil { panic(err) } } // RecordN records multiple instances of the same value. func (h *PCPHistogram) RecordN(val, n int64) error { h.mutex.Lock() defer h.mutex.Unlock() err := h.h.RecordValues(val, n) if err != nil { return err } return h.update() } // MustRecordN panics if RecordN fails. func (h *PCPHistogram) MustRecordN(val, n int64) { if err := h.RecordN(val, n); err != nil { panic(err) } } // Mean returns the mean of all values recorded so far. func (h *PCPHistogram) Mean() float64 { h.mutex.RLock() defer h.mutex.RUnlock() return h.vals["mean"].val.(float64) } // StandardDeviation returns the standard deviation of all values recorded so far. func (h *PCPHistogram) StandardDeviation() float64 { h.mutex.RLock() defer h.mutex.RUnlock() return h.vals["standard_deviation"].val.(float64) } // Variance returns the variance of all values recorded so far. func (h *PCPHistogram) Variance() float64 { h.mutex.RLock() defer h.mutex.RUnlock() return h.vals["variance"].val.(float64) } // Percentile returns the value at the passed percentile. func (h *PCPHistogram) Percentile(p float64) int64 { return h.h.ValueAtQuantile(p) } // HistogramBucket is a single histogram bucket within a fixed range. type HistogramBucket struct { From, To, Count int64 } // Buckets returns a list of histogram buckets. func (h *PCPHistogram) Buckets() []*HistogramBucket { b := h.h.Distribution() buckets := make([]*HistogramBucket, len(b)) for i := 0; i < len(b); i++ { buckets[i] = &HistogramBucket{b[i].From, b[i].To, b[i].Count} } return buckets } golang-github-performancecopilot-speed-4.0.0/metrics_amd64_test.go000066400000000000000000000036721433655324600253030ustar00rootroot00000000000000package speed import ( "math" "testing" ) func TestIsCompatible64(t *testing.T) { cases := []struct { t MetricType v interface{} result bool }{ {Int32Type, math.MaxInt32 + 1, false}, {Int64Type, math.MaxInt32 + 1, true}, {Uint32Type, math.MaxInt32 + 1, true}, {Uint64Type, math.MaxInt32 + 1, true}, {Int32Type, math.MinInt32 - 1, false}, {Int64Type, math.MinInt32 - 1, true}, {Uint32Type, math.MinInt32 - 1, false}, {Uint64Type, math.MinInt32 - 1, false}, {Int32Type, math.MinInt64, false}, {Int64Type, math.MinInt64, true}, {Uint32Type, math.MinInt64, false}, {Uint64Type, math.MinInt64, false}, {Int32Type, math.MaxInt64, false}, {Int64Type, math.MaxInt64, true}, {Uint32Type, math.MaxInt64, false}, {Uint64Type, math.MaxInt64, true}, {Uint32Type, math.MaxUint32 + 1, false}, {Uint64Type, math.MaxUint32 + 1, true}, {Uint32Type, uint(math.MaxUint32 + 1), false}, {Uint64Type, uint(math.MaxUint32 + 1), true}, {Uint64Type, uint(math.MaxUint64), true}, {Uint64Type, uint64(math.MaxUint64), true}, {FloatType, math.MaxFloat32, true}, {FloatType, -math.MaxFloat32, true}, {DoubleType, math.MaxFloat32, true}, {DoubleType, -math.MaxFloat32, true}, // we cannot test for `math.MaxFloat32 + 1` // or even `math.MaxFloat32 + math.MaxUint64` // because in floating point those aren't significant // enough additions, so the comparison will be equal // for more http://stackoverflow.com/q/17588419/3673043 {FloatType, math.MaxFloat32 * 2, false}, {DoubleType, math.MaxFloat32 * 2, true}, {FloatType, math.MaxFloat64, false}, {FloatType, -math.MaxFloat64, false}, {DoubleType, math.MaxFloat64, true}, {DoubleType, -math.MaxFloat64, true}, } for i, c := range cases { r := c.t.IsCompatible(c.v) if r != c.result { f := math.MaxFloat32 println((f + 1) > f) t.Errorf("case %v: %v.IsCompatible(%v(%T)) should be %v, not %v", i+1, c.t, c.v, c.v, c.result, r) } } } golang-github-performancecopilot-speed-4.0.0/metrics_test.go000066400000000000000000000075521433655324600243110ustar00rootroot00000000000000package speed import ( "math" "testing" ) // only tests that work on 32 bit architectures or both go here // tests only working on 64 bit architectures go in _amd64_test.go func TestIsCompatible(t *testing.T) { cases := []struct { t MetricType v interface{} result bool }{ {Int32Type, -1, true}, {Int64Type, -1, true}, {Uint64Type, -1, false}, {Uint32Type, -1, false}, {Int32Type, math.MinInt32, true}, {Int64Type, math.MinInt32, true}, {Uint32Type, math.MinInt32, false}, {Uint64Type, math.MinInt32, false}, {Int32Type, int32(math.MinInt32), true}, {Int64Type, int64(math.MinInt32), true}, {Uint32Type, int32(math.MinInt32), false}, {Uint64Type, int64(math.MinInt32), false}, {Int32Type, math.MaxInt32, true}, {Int64Type, math.MaxInt32, true}, {Uint32Type, math.MaxInt32, true}, {Uint64Type, math.MaxInt32, true}, {Int32Type, int32(math.MaxInt32), true}, {Int64Type, int64(math.MaxInt32), true}, {Uint32Type, int32(math.MaxInt32), false}, {Uint64Type, int64(math.MaxInt32), false}, {Int64Type, int64(math.MaxInt64), true}, {Uint32Type, uint32(math.MaxUint32), true}, {Uint64Type, uint64(math.MaxUint32), true}, {Uint32Type, uint(math.MaxUint32), true}, {Uint32Type, uint32(math.MaxUint32), true}, {Uint64Type, uint64(math.MaxUint64), true}, {FloatType, math.MaxFloat32, true}, {DoubleType, math.MaxFloat32, true}, {FloatType, -math.MaxFloat32, true}, {DoubleType, -math.MaxFloat32, true}, {FloatType, float32(math.MaxFloat32), true}, {DoubleType, float32(math.MaxFloat32), false}, {FloatType, float32(-math.MaxFloat32), true}, {DoubleType, float32(-math.MaxFloat32), false}, {FloatType, float64(math.MaxFloat32), true}, {DoubleType, float64(math.MaxFloat32), true}, {FloatType, float64(-math.MaxFloat32), true}, {DoubleType, float64(-math.MaxFloat32), true}, {StringType, 10, false}, {StringType, 10.10, false}, {StringType, "10", true}, } for _, c := range cases { r := c.t.IsCompatible(c.v) if r != c.result { t.Errorf("%v.IsCompatible(%v(%T)) should be %v, not %v", c.t, c.v, c.v, c.result, r) } } } func TestResolve(t *testing.T) { cases := []struct { t MetricType val, resval interface{} }{ {Int32Type, 10, int32(10)}, {Int64Type, 10, int64(10)}, {Uint32Type, 10, uint32(10)}, {Uint64Type, 10, uint64(10)}, {Int32Type, int32(10), int32(10)}, {Int64Type, int64(10), int64(10)}, {Uint32Type, uint32(10), uint32(10)}, {Uint64Type, uint64(10), uint64(10)}, {Uint32Type, uint(10), uint32(10)}, {Uint64Type, uint(10), uint64(10)}, {Uint32Type, uint32(10), uint32(10)}, {Uint64Type, uint64(10), uint64(10)}, {FloatType, 3.14, float32(3.14)}, {DoubleType, 3.14, float64(3.14)}, {FloatType, float32(3.14), float32(3.14)}, {DoubleType, float64(3.14), float64(3.14)}, } for _, c := range cases { if c.t.resolve(c.val) != c.resval { t.Errorf("expected %T to resolve to %T", c.val, c.resval) } } } func TestComposition(t *testing.T) { ms := MegabyteUnit.Time(SecondUnit, -1) if ms.String() != "MegabyteUnit^1SecondUnit^-1" { t.Errorf("expected ms.String() to be MegabyteUnit^1SecondUnit^-1, got %s", ms.String()) } if ms.PMAPI() != 520237056 { t.Errorf("expected ms.PMAPI() to be 520237056, got %v", ms.PMAPI()) } hz := NewMetricUnit().Time(SecondUnit, -1) if hz.String() != "SecondUnit^-1" { t.Errorf("expected hz.String() to be SecondUnit^-1, got %s", ms.String()) } if hz.PMAPI() != 251670528 { t.Errorf("expected hz.PMAPI() to be 251670528, got %v", hz.PMAPI()) } cs1 := OneUnit.Space(MegabyteUnit, 2).Time(SecondUnit, -2) cs2 := NewMetricUnit().Time(SecondUnit, -2).Space(MegabyteUnit, 2).Count(OneUnit, 1) if cs1.PMAPI() != cs2.PMAPI() { t.Errorf("expected %v to be equal to %v", cs1.PMAPI(), cs2.PMAPI()) } if cs1.String() != cs2.String() { t.Errorf("expected %v to be equal to %v", cs1.String(), cs2.String()) } } golang-github-performancecopilot-speed-4.0.0/metricsemantics_string.go000066400000000000000000000012651433655324600263570ustar00rootroot00000000000000// Code generated by "stringer -type=MetricSemantics"; DO NOT EDIT package speed import "fmt" const ( _MetricSemantics_name_0 = "NoSemanticsCounterSemantics" _MetricSemantics_name_1 = "InstantSemanticsDiscreteSemantics" ) var ( _MetricSemantics_index_0 = [...]uint8{0, 11, 27} _MetricSemantics_index_1 = [...]uint8{0, 16, 33} ) func (i MetricSemantics) String() string { switch { case 0 <= i && i <= 1: return _MetricSemantics_name_0[_MetricSemantics_index_0[i]:_MetricSemantics_index_0[i+1]] case 3 <= i && i <= 4: i -= 3 return _MetricSemantics_name_1[_MetricSemantics_index_1[i]:_MetricSemantics_index_1[i+1]] default: return fmt.Sprintf("MetricSemantics(%d)", i) } } golang-github-performancecopilot-speed-4.0.0/metrictype_string.go000066400000000000000000000007221433655324600253470ustar00rootroot00000000000000// Code generated by "stringer -type=MetricType"; DO NOT EDIT package speed import "fmt" const _MetricType_name = "Int32TypeUint32TypeInt64TypeUint64TypeFloatTypeDoubleTypeStringType" var _MetricType_index = [...]uint8{0, 9, 19, 28, 38, 47, 57, 67} func (i MetricType) String() string { if i < 0 || i >= MetricType(len(_MetricType_index)-1) { return fmt.Sprintf("MetricType(%d)", i) } return _MetricType_name[_MetricType_index[i]:_MetricType_index[i+1]] } golang-github-performancecopilot-speed-4.0.0/mmvdump/000077500000000000000000000000001433655324600227315ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/mmvdump/README.md000066400000000000000000000014451433655324600242140ustar00rootroot00000000000000# mmvdump [![GoDoc](https://godoc.org/github.com/performancecopilot/speed/mmvdump?status.svg)](https://godoc.org/github.com/performancecopilot/speed/mmvdump) Package mmvdump implements a go port of the C mmvdump utility included in PCP Core https://github.com/performancecopilot/pcp/blob/master/src/pmdas/mmv/mmvdump.c It has been written for maximum portability with the C equivalent, without having to use cgo or any other ninja stuff the main difference is that the reader is separate from the cli with the reading primarily implemented in mmvdump.go while the cli is implemented in cmd/mmvdump the cli application is completely go gettable and outputs the same things, in mostly the same way as the C cli app, to try it out, ``` go get github.com/performancecopilot/speed/mmvdump/cmd/mmvdump ```golang-github-performancecopilot-speed-4.0.0/mmvdump/cmd/000077500000000000000000000000001433655324600234745ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/mmvdump/cmd/mmvdump/000077500000000000000000000000001433655324600251615ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/mmvdump/cmd/mmvdump/main.go000066400000000000000000000011101433655324600264250ustar00rootroot00000000000000package main import ( "flag" "fmt" "io/ioutil" "os" "github.com/performancecopilot/speed/v4/mmvdump" ) func main() { flag.Parse() if flag.NArg() < 1 { fmt.Println("usage: mmvdump ") return } file := flag.Arg(0) d, err := ioutil.ReadFile(file) if err != nil { panic(err) } header, tocs, metrics, values, instances, indoms, strings, err := mmvdump.Dump(d) if err != nil { panic(err) } fmt.Printf("File = %v\n", file) if err := mmvdump.Write(os.Stdout, header, tocs, metrics, values, instances, indoms, strings); err != nil { panic(err) } } golang-github-performancecopilot-speed-4.0.0/mmvdump/mmvdump.go000066400000000000000000000207701433655324600247530ustar00rootroot00000000000000// Package mmvdump implements a go port of the C mmvdump utility included in PCP Core // // https://github.com/performancecopilot/pcp/blob/master/src/pmdas/mmv/mmvdump.c // // It has been written for maximum portability with the C equivalent, without having to use cgo or any other ninja stuff // // the main difference is that the reader is separate from the cli with the reading primarily implemented in mmvdump.go while the cli is implemented in cmd/mmvdump // // the cli application is completely go gettable and outputs the same things, in mostly the same way as the C cli app, to try it out, // // ``` // go get github.com/performancecopilot/speed/mmvdump/cmd/mmvdump // ``` package mmvdump import ( "math" "sync" "unsafe" "github.com/pkg/errors" ) func readHeader(data []byte) (*Header, error) { if uint64(len(data)) < HeaderLength { return nil, errors.New("file too small to contain a valid Header") } header := (*Header)(unsafe.Pointer(&data[0])) if m := header.Magic[:3]; string(m) != "MMV" { return nil, errors.Errorf("Bad Magic: %v", string(m)) } if header.G1 != header.G2 { return nil, errors.Errorf("Mismatched version numbers, %v and %v", header.G1, header.G2) } return header, nil } func readToc(data []byte, offset uint64) (*Toc, error) { if uint64(len(data)) < offset+TocLength { return nil, errors.New("Incomplete/Partially Written TOC") } return (*Toc)(unsafe.Pointer(&data[offset])), nil } type itemReaderFunc func([]byte, uint64, int32) (interface{}, error) func readInstance(data []byte, offset uint64, version int32) (interface{}, error) { var InstanceLength = Instance1Length if version == 2 { InstanceLength = Instance2Length } if uint64(len(data)) < offset+InstanceLength { return nil, errors.New("Incomplete/Partially Written Instance") } if version == 1 { return (*Instance1)(unsafe.Pointer(&data[offset])), nil } return (*Instance2)(unsafe.Pointer(&data[offset])), nil } func readInstanceDomain(data []byte, offset uint64, version int32) (interface{}, error) { if uint64(len(data)) < offset+InstanceDomainLength { return nil, errors.New("Incomplete/Partially Written InstanceDomain") } return (*InstanceDomain)(unsafe.Pointer(&data[offset])), nil } func readMetric(data []byte, offset uint64, version int32) (interface{}, error) { var MetricLength = Metric1Length if version == 2 { MetricLength = Metric2Length } if uint64(len(data)) < offset+MetricLength { return nil, errors.New("Incomplete/Partially Written Metric") } if version == 1 { return (*Metric1)(unsafe.Pointer(&data[offset])), nil } return (*Metric2)(unsafe.Pointer(&data[offset])), nil } func readValue(data []byte, offset uint64, version int32) (interface{}, error) { if uint64(len(data)) < offset+ValueLength { return nil, errors.New("Incomplete/Partially Written Value") } return (*Value)(unsafe.Pointer(&data[offset])), nil } func readString(data []byte, offset uint64, version int32) (interface{}, error) { if uint64(len(data)) < offset+StringLength { return nil, errors.New("Incomplete/Partially Written String") } return (*String)(unsafe.Pointer(&data[offset])), nil } func readTocs(data []byte, count int32) ([]*Toc, error) { tocs := make([]*Toc, count) for i := int32(0); i < count; i++ { t, err := readToc(data, HeaderLength+uint64(i)*TocLength) if err != nil { return nil, err } tocs[i] = t } return tocs, nil } func readItems(data []byte, offset uint64, count int32, itemlength uint64, readItem itemReaderFunc, version int32) (map[uint64]interface{}, error) { var wg sync.WaitGroup wg.Add(int(count)) items := make(map[uint64]interface{}) var ( err error m sync.Mutex ) for i := int32(0); i < count; i, offset = i+1, offset+itemlength { go func(offset uint64) { if err == nil { item, ierr := readItem(data, offset, version) if ierr == nil { m.Lock() items[offset] = item m.Unlock() } else { err = ierr } } wg.Done() }(offset) } wg.Wait() if err != nil { return nil, err } return items, nil } func readInstances(data []byte, offset uint64, count int32, version int32) (map[uint64]Instance, error) { InstanceLength := Instance1Length if version == 2 { InstanceLength = Instance2Length } i, err := readItems(data, offset, count, InstanceLength, readInstance, version) if err != nil { return nil, err } instances := make(map[uint64]Instance) for off, val := range i { instances[off] = val.(Instance) } return instances, nil } func readInstanceDomains(data []byte, offset uint64, count int32, version int32) (map[uint64]*InstanceDomain, error) { i, err := readItems(data, offset, count, InstanceDomainLength, readInstanceDomain, version) if err != nil { return nil, err } indoms := make(map[uint64]*InstanceDomain) for off, val := range i { indoms[off] = val.(*InstanceDomain) } return indoms, nil } func readMetrics(data []byte, offset uint64, count int32, version int32) (map[uint64]Metric, error) { var MetricLength = Metric1Length if version == 2 { MetricLength = Metric2Length } m, err := readItems(data, offset, count, MetricLength, readMetric, version) if err != nil { return nil, err } metrics := make(map[uint64]Metric) for off, val := range m { metrics[off] = val.(Metric) } return metrics, nil } func readValues(data []byte, offset uint64, count int32, version int32) (map[uint64]*Value, error) { v, err := readItems(data, offset, count, ValueLength, readValue, version) if err != nil { return nil, err } values := make(map[uint64]*Value) for off, val := range v { values[off] = val.(*Value) } return values, nil } func readStrings(data []byte, offset uint64, count int32, version int32) (map[uint64]*String, error) { s, err := readItems(data, offset, count, StringLength, readString, version) if err != nil { return nil, err } strings := make(map[uint64]*String) for off, val := range s { strings[off] = val.(*String) } return strings, nil } func readComponents(data []byte, tocs []*Toc, version int32) ( metrics map[uint64]Metric, values map[uint64]*Value, instances map[uint64]Instance, indoms map[uint64]*InstanceDomain, strings map[uint64]*String, ierr, inerr, merr, verr, serr error, ) { var wg sync.WaitGroup wg.Add(len(tocs)) for _, toc := range tocs { switch toc.Type { case TocInstances: go func(offset uint64, count int32) { instances, ierr = readInstances(data, offset, count, version) wg.Done() }(toc.Offset, toc.Count) case TocIndoms: go func(offset uint64, count int32) { indoms, inerr = readInstanceDomains(data, offset, count, version) wg.Done() }(toc.Offset, toc.Count) case TocMetrics: go func(offset uint64, count int32) { metrics, merr = readMetrics(data, offset, count, version) wg.Done() }(toc.Offset, toc.Count) case TocValues: go func(offset uint64, count int32) { values, verr = readValues(data, offset, count, version) wg.Done() }(toc.Offset, toc.Count) case TocStrings: go func(offset uint64, count int32) { strings, serr = readStrings(data, offset, count, version) wg.Done() }(toc.Offset, toc.Count) } } wg.Wait() return } // Dump creates a data dump from the passed data func Dump(data []byte) ( h *Header, tocs []*Toc, metrics map[uint64]Metric, values map[uint64]*Value, instances map[uint64]Instance, indoms map[uint64]*InstanceDomain, strings map[uint64]*String, err error, ) { h, err = readHeader(data) if err != nil { return nil, nil, nil, nil, nil, nil, nil, err } tocs, err = readTocs(data, h.Toc) if err != nil { return nil, nil, nil, nil, nil, nil, nil, err } var ierr, inerr, merr, verr, serr error metrics, values, instances, indoms, strings, ierr, inerr, merr, verr, serr = readComponents(data, tocs, h.Version) switch { case ierr != nil: return nil, nil, nil, nil, nil, nil, nil, ierr case inerr != nil: return nil, nil, nil, nil, nil, nil, nil, inerr case merr != nil: return nil, nil, nil, nil, nil, nil, nil, merr case verr != nil: return nil, nil, nil, nil, nil, nil, nil, verr case serr != nil: return nil, nil, nil, nil, nil, nil, nil, serr } return } // FixedVal will infer a fixed size value from the passed data func FixedVal(data uint64, t Type) (interface{}, error) { switch t { case Int32Type: return int32(data), nil case Uint32Type: return uint32(data), nil case Int64Type: return int64(data), nil case Uint64Type: return data, nil case FloatType: return math.Float32frombits(uint32(data)), nil case DoubleType: return math.Float64frombits(data), nil } return nil, errors.New("invalid type") } golang-github-performancecopilot-speed-4.0.0/mmvdump/mmvdump_test.go000066400000000000000000000040071433655324600260050ustar00rootroot00000000000000package mmvdump import ( "bytes" "io/ioutil" "testing" ) func TestMmvDump1(t *testing.T) { d, err := ioutil.ReadFile("testdata/test1.mmv") if err != nil { t.Fatal(err) } h, tocs, metrics, values, instances, indoms, strings, err := Dump(d) if err != nil { t.Error(err) return } if h.G1 != h.G2 { t.Error("Invalid Header") } if len(tocs) != 3 { t.Errorf("expected number of tocs %d, got %d", 3, len(tocs)) } if len(indoms) != 0 { t.Errorf("expected number of indoms %d, got %d", 0, len(indoms)) } if len(strings) != 2 { t.Errorf("expected number of strings %d, got %d", 2, len(strings)) } if len(metrics) != 1 { t.Errorf("expected number of metrics %d, got %d", 1, len(metrics)) } if len(values) != 1 { t.Errorf("expected number of values %d, got %d", 1, len(values)) } if len(instances) != 0 { t.Errorf("expected number of instances %d, got %d", 0, len(instances)) } } func TestInputs(t *testing.T) { for _, c := range []struct { input, output string }{ {"testdata/test1.mmv", "testdata/output1.golden"}, {"testdata/test2.mmv", "testdata/output2.golden"}, {"testdata/test3.mmv", "testdata/output3.golden"}, {"testdata/test4.mmv", "testdata/output4.golden"}, {"testdata/test5.mmv", "testdata/output5.golden"}, } { data, err := ioutil.ReadFile(c.input) if err != nil { t.Fatal(err) } header, tocs, metrics, values, instances, indoms, strings, err := Dump(data) if err != nil { t.Fatal(err) } var b = new(bytes.Buffer) err = Write(b, header, tocs, metrics, values, instances, indoms, strings) if err != nil { t.Fatal(err) } expected, err := ioutil.ReadFile(c.output) if err != nil { t.Fatal(err) } actual := b.Bytes() if !bytes.Equal(expected, actual) { t.Fatalf(` Failed for input %s, expected ------------------------------------------- %s ------------------------------------------- got ------------------------------------------- %s ------------------------------------------- `, c.input, string(expected), string(actual)) } } } golang-github-performancecopilot-speed-4.0.0/mmvdump/pcp.go000066400000000000000000000176661433655324600240620ustar00rootroot00000000000000package mmvdump import "strconv" // MMVVersion is the current mmv format version const MMVVersion = 1 const ( // NameMax is the maximum allowed length of a name NameMax = 64 // StringMax is the maximum allowed length of a string StringMax = 256 // NoIndom is a constant used to indicate absence of an indom from a metric NoIndom = -1 ) // Header describes the data in a MMV header type Header struct { Magic [4]byte Version int32 G1, G2 uint64 Toc int32 Flag int32 Process, Cluster int32 } // TocType is an enumerated type with different types as values type TocType int32 // Values for TocType const ( TocIndoms TocType = iota + 1 TocInstances TocMetrics TocValues TocStrings ) //go:generate stringer --type=TocType // Toc defines the contents in a valid TOC type Toc struct { Type TocType Count int32 Offset uint64 } // Instance is the base type for all instances type Instance interface { Indom() uint64 Internal() int32 Padding() uint32 } // InstanceBase defines the common contents in a valid instance type InstanceBase struct { indom uint64 padding uint32 internal int32 } // Indom returns the indom offset func (i InstanceBase) Indom() uint64 { return i.indom } // Internal returns the internal id func (i InstanceBase) Internal() int32 { return i.internal } // Padding returns the padding value func (i InstanceBase) Padding() uint32 { return i.padding } // Instance1 defines the contents in a valid mmv1 instance type Instance1 struct { InstanceBase External [NameMax]byte } // Instance2 defines the contents in a valid mmv2 instance type Instance2 struct { InstanceBase External uint64 } // InstanceDomain defines the contents in a valid instance domain type InstanceDomain struct { Serial, Count uint32 Offset, Shorttext, Longtext uint64 } // Metric is the base type for all metrics type Metric interface { Item() uint32 Typ() Type Sem() Semantics Unit() Unit Indom() int32 Padding() uint32 ShortText() uint64 LongText() uint64 } // MetricBase defines the common contents in a valid Metric type MetricBase struct { item uint32 typ Type sem Semantics unit Unit indom int32 padding uint32 shorttext, longtext uint64 } // Item returns the item id func (m MetricBase) Item() uint32 { return m.item } // Typ returns the type func (m MetricBase) Typ() Type { return m.typ } // Sem returns the semantics func (m MetricBase) Sem() Semantics { return m.sem } // Unit returns the unit func (m MetricBase) Unit() Unit { return m.unit } // Indom returns the indom id func (m MetricBase) Indom() int32 { return m.indom } // Padding returns the padding value func (m MetricBase) Padding() uint32 { return m.padding } // ShortText returns the shorttext offset func (m MetricBase) ShortText() uint64 { return m.shorttext } // LongText returns the longtext offset func (m MetricBase) LongText() uint64 { return m.longtext } // Metric1 defines the contents in a valid Metric type Metric1 struct { Name [NameMax]byte MetricBase } // Metric2 defines the contents in a valid Metric type Metric2 struct { Name uint64 MetricBase } // Value defines the contents in a PCP Value type Value struct { // uint64 is a holder type here, while printing it is expected that // the user will infer the value using the Val functions Val uint64 Extra int64 Metric uint64 Instance uint64 } // String wraps the payload for a PCP String type String struct { Payload [StringMax]byte } // Type is an enumerated type representing all valid types for a metric type Type int32 // Possible values for a Type const ( NoSupportType Type = iota - 1 Int32Type Uint32Type Int64Type Uint64Type FloatType DoubleType StringType UnknownType Type = 255 ) //go:generate stringer --type=Type // Unit is an enumerated type with all possible units as values type Unit uint32 // Values for Space Units const ( ByteUnit Unit = 1<<28 | iota<<16 KilobyteUnit MegabyteUnit GigabyteUnit TerabyteUnit PetabyteUnit ExabyteUnit ) // Values for Time Units const ( NanosecondUnit Unit = 1<<24 | iota<<12 MicrosecondUnit MillisecondUnit SecondUnit MinuteUnit HourUnit ) // Values for Count Units const ( OneUnit Unit = 1<<20 | iota<<8 ) // SpaceScale gets the Space Scale of a unit // // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#398 func (u Unit) SpaceScale() uint8 { return uint8((u >> 16) & 0xF) } // TimeScale gets the Time Scale of a unit // // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#402 func (u Unit) TimeScale() uint8 { return uint8((u >> 12) & 0xF) } // CountScale gets the Count Scale of a unit // // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#406 func (u Unit) CountScale() uint8 { return uint8((u >> 8) & 0xF) } // SpaceDim gets the space dimension of the unit // the right shift is on int32 to get an arithmetic right shift as // the dimension is serialized in 2s complement form // // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#410 func (u Unit) SpaceDim() int8 { return int8(int32(u) >> 28) } // TimeDim gets the time dimension of the unit // the right shift is on int32 to get an arithmetic right shift as // the dimension is serialized in 2s complement form // // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#410 func (u Unit) TimeDim() int8 { return int8(int32(u<<4) >> 28) } // CountDim gets the count dimension of the unit // the right shift is on int32 to get an arithmetic right shift as // the dimension is serialized in 2s complement form // // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#410 func (u Unit) CountDim() int8 { return int8(int32(u<<8) >> 28) } func abs(dim int8) int8 { if dim < 0 { return -dim } return dim } func stringSpaceDim(scale uint8, dim int8) string { suf := "" if abs(dim) > 1 { suf = "^" + strconv.Itoa(int(dim)) } switch scale { case 0: return "B" + suf case 1: return "KiB" + suf case 2: return "MiB" + suf case 3: return "GiB" + suf case 4: return "TiB" + suf case 5: return "PiB" + suf case 6: return "EiB" + suf default: return "" } } func stringTimeDim(scale uint8, dim int8) string { suf := "" if abs(dim) > 1 { suf = "^" + strconv.Itoa(int(dim)) } switch scale { case 0: return "nsec" + suf case 1: return "usec" + suf case 2: return "msec" + suf case 3: return "sec" + suf case 4: return "min" + suf case 5: return "hr" + suf default: return "" } } func stringCountDim(scale uint8, dim int8) string { suf := "" if abs(dim) > 1 { suf = "^" + strconv.Itoa(int(dim)) } switch scale { case 0: return "count" + suf default: return "" } } // https://docs.rs/hornet/0.1.0/src/hornet/client/metric/mod.rs.html#454 func (u Unit) String() string { ss, sd, ts, td, cs, cd := u.SpaceScale(), u.SpaceDim(), u.TimeScale(), u.TimeDim(), u.CountScale(), u.CountDim() ans := "" if sd > 0 { ans += stringSpaceDim(ss, sd) } if td > 0 { ans += stringTimeDim(ts, td) } if cd > 0 { ans += stringCountDim(cs, cd) } if sd < 0 || td < 0 || cd < 0 { ans += " / " if sd < 0 { ans += stringSpaceDim(ss, sd) } if td < 0 { ans += stringTimeDim(ts, td) } if cd < 0 { ans += stringCountDim(cs, cd) } } return ans } // Semantics represents an enumerated type representing all possible semantics of a metric type Semantics int32 // Values for Semantics const ( NoSemantics Semantics = 0 CounterSemantics _ InstantSemantics DiscreteSemantics ) //go:generate stringer -type=Semantics // Byte Lengths for Different Components const ( HeaderLength uint64 = 40 TocLength uint64 = 16 Metric1Length uint64 = 104 Metric2Length uint64 = 48 ValueLength uint64 = 32 Instance1Length uint64 = 80 Instance2Length uint64 = 24 InstanceDomainLength uint64 = 32 StringLength uint64 = 256 ) golang-github-performancecopilot-speed-4.0.0/mmvdump/semantics_string.go000066400000000000000000000005731433655324600266410ustar00rootroot00000000000000// Code generated by "stringer -type=Semantics"; DO NOT EDIT package mmvdump import "fmt" const _Semantics_name = "NoSemantics" var _Semantics_index = [...]uint8{0, 11} func (i Semantics) String() string { if i < 0 || i >= Semantics(len(_Semantics_index)-1) { return fmt.Sprintf("Semantics(%d)", i) } return _Semantics_name[_Semantics_index[i]:_Semantics_index[i+1]] } golang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/000077500000000000000000000000001433655324600245425ustar00rootroot00000000000000golang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/output1.golden000066400000000000000000000030321433655324600273530ustar00rootroot00000000000000Version = 1 Generated = 1468770536 Toc Count = 3 Cluster = 127 Process = 29956 Flags = 0x2 TOC[0], offset: 40, metric offset: 88 (1 entries) [725/88] simple.counter type=Int32Type (0x0), sem=Semantics(1) (0x1), pad=0x0 units=count (no indom) shorttext=A Simple Metric longtext=This is a simple counter metric to demonstrate the speed API TOC[1], offset: 56, values offset: 192 (1 entries) [725/192] simple.counter = 42 TOC[2], offset: 72, strings offset: 224 (2 entries) [224] A Simple Metric [480] This is a simple counter metric to demonstrate the speed API golang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/output2.golden000066400000000000000000000025761433655324600273700ustar00rootroot00000000000000Version = 1 Generated = 1469335238 Toc Count = 4 Cluster = 1297 Process = 6410 Flags = 0x2 TOC[0], offset: 40, indoms offset: 104 (1 entries) [3094651/104] 3 instances, starting at offset 136 (no shorttext) (no longtext) TOC[1], offset: 56, instances offset: 136 (3 entries) [3094651/136] instance = [-2122300086/javascript] [3094651/216] instance = [1531230383/php] [3094651/296] instance = [1109423947/go] TOC[2], offset: 72, metric offset: 376 (1 entries) [1021/376] language.users type=Uint64Type (0x3), sem=Semantics(1) (0x1), pad=0x0 units=count indom=3094651 (no shorttext) (no longtext) TOC[3], offset: 88, values offset: 480 (3 entries) [1021/480] language.users[1109423947 or "go"] = 8388608 [1021/512] language.users[-2122300086 or "javascript"] = 330 [1021/544] language.users[1531230383 or "php"] = 33 golang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/output3.golden000066400000000000000000000020321433655324600273540ustar00rootroot00000000000000Version = 1 Generated = 1469564258 Toc Count = 3 Cluster = 764 Process = 8672 Flags = 0x2 TOC[0], offset: 40, metric offset: 88 (1 entries) [1022/88] bat.names type=StringType (0x6), sem=Semantics(3) (0x3), pad=0x0 units=count (no indom) (no shorttext) (no longtext) TOC[1], offset: 56, values offset: 192 (1 entries) [1022/192] bat.names = Robin TOC[2], offset: 72, strings offset: 224 (1 entries) [224] Robin golang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/output4.golden000066400000000000000000000020331433655324600273560ustar00rootroot00000000000000Version = 1 Generated = 1469590299 Toc Count = 3 Cluster = 764 Process = 22340 Flags = 0x2 TOC[0], offset: 40, metric offset: 88 (1 entries) [1022/88] bat.names type=StringType (0x6), sem=Semantics(3) (0x3), pad=0x0 units=count (no indom) (no shorttext) (no longtext) TOC[1], offset: 56, values offset: 192 (1 entries) [1022/192] bat.names = TOC[2], offset: 72, strings offset: 224 (1 entries) [224] golang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/output5.golden000066400000000000000000000041141433655324600273610ustar00rootroot00000000000000Version = 1 Generated = 1501135556 Toc Count = 3 Cluster = 0 Process = 15673 Flags = 0x7 TOC[0], offset: 40, metric offset: 88 (3 entries) [150/88] download_speed type=DoubleType (0x5), sem=Semantics(3) (0x3), pad=0x0 units=MiB / sec indom=0 shorttext=Download speed in MiB/sec (no longtext) [372/192] frequency type=FloatType (0x4), sem=Semantics(3) (0x3), pad=0x0 units= / sec indom=0 shorttext=Frequency in Hz (no longtext) [433/296] time type=Int32Type (0x0), sem=Semantics(3) (0x3), pad=0x0 units=hr indom=0 (no shorttext) (no longtext) TOC[1], offset: 56, values offset: 400 (3 entries) [150/400] download_speed = 0.3333333333333333 [372/432] frequency = 0.33333334 [433/464] time = -6 TOC[2], offset: 72, strings offset: 496 (2 entries) [496] Download speed in MiB/sec [752] Frequency in Hz golang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/test1.mmv000066400000000000000000000013401433655324600263210ustar00rootroot00000000000000MMV訋W訋WuXsimple.counter*XA Simple MetricThis is a simple counter metric to demonstrate the speed APIgolang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/test2.mmv000066400000000000000000000011001433655324600263140ustar00rootroot00000000000000MMVFWFW hx{8/hJEjavascripthD[phphKw Bgolanguage.users{8/x(Jx!xgolang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/test3.mmv000066400000000000000000000007401433655324600263260ustar00rootroot00000000000000MMVbŗWbŗW!Xbat.namesXRobingolang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/test4.mmv000066400000000000000000000007401433655324600263270ustar00rootroot00000000000000MMV+W+WDWXbat.namesXgolang-github-performancecopilot-speed-4.0.0/mmvdump/testdata/test5.mmv000066400000000000000000000053601433655324600263330ustar00rootroot00000000000000MMVĂyYĂyY9=Xdownload_speed0frequencyt0timePUUUUUU?X>(Download speed in MiB/secFrequency in Hzgolang-github-performancecopilot-speed-4.0.0/mmvdump/toctype_string.go000066400000000000000000000006501433655324600263360ustar00rootroot00000000000000// Code generated by "stringer --type=TocType"; DO NOT EDIT package mmvdump import "fmt" const _TocType_name = "TocIndomsTocInstancesTocMetricsTocValuesTocStrings" var _TocType_index = [...]uint8{0, 9, 21, 31, 40, 50} func (i TocType) String() string { i -= 1 if i < 0 || i >= TocType(len(_TocType_index)-1) { return fmt.Sprintf("TocType(%d)", i+1) } return _TocType_name[_TocType_index[i]:_TocType_index[i+1]] } golang-github-performancecopilot-speed-4.0.0/mmvdump/type_string.go000066400000000000000000000010611433655324600256250ustar00rootroot00000000000000// Code generated by "stringer --type=Type"; DO NOT EDIT package mmvdump import "fmt" const ( _Type_name_0 = "NoSupportTypeInt32TypeUint32TypeInt64TypeUint64TypeFloatTypeDoubleTypeStringType" _Type_name_1 = "UnknownType" ) var ( _Type_index_0 = [...]uint8{0, 13, 22, 32, 41, 51, 60, 70, 80} _Type_index_1 = [...]uint8{0, 11} ) func (i Type) String() string { switch { case -1 <= i && i <= 6: i -= -1 return _Type_name_0[_Type_index_0[i]:_Type_index_0[i+1]] case i == 255: return _Type_name_1 default: return fmt.Sprintf("Type(%d)", i) } } golang-github-performancecopilot-speed-4.0.0/mmvdump/writer.go000066400000000000000000000150231433655324600245750ustar00rootroot00000000000000package mmvdump import ( "fmt" "io" "github.com/pkg/errors" ) func instanceName(m Instance, header *Header, strings map[uint64]*String) string { if header.Version == 1 { return string(m.(*Instance1).External[:]) } return string(strings[m.(*Instance2).External].Payload[:]) } func writeInstance( w io.Writer, offset uint64, header *Header, instances map[uint64]Instance, indoms map[uint64]*InstanceDomain, strings map[uint64]*String, ) error { i := instances[offset] indom := indoms[i.Indom()] Name := instanceName(i, header, strings) _, err := fmt.Fprintf(w, "\t[%v/%v] instance = [%v/%v]\n", indom.Serial, offset, i.Internal(), Name) return err } func writeInstanceDomain( w io.Writer, offset uint64, indoms map[uint64]*InstanceDomain, strings map[uint64]*String, ) error { indom := indoms[offset] _, err := fmt.Fprintf(w, "\t[%v/%v] %d instances, starting at offset %d\n", indom.Serial, offset, indom.Count, indom.Offset) if err != nil { return err } if indom.Shorttext == 0 { if _, err := fmt.Fprintf(w, "\t\t(no shorttext)\n"); err != nil { return err } } else { if _, err := fmt.Fprintf(w, "\t\tshorttext=%v\n", string(strings[indom.Shorttext].Payload[:])); err != nil { return err } } if indom.Longtext == 0 { if _, err := fmt.Fprintf(w, "\t\t(no longtext)\n"); err != nil { return err } } else { if _, err := fmt.Fprintf(w, "\t\tlongtext=%v\n", string(strings[indom.Longtext].Payload[:])); err != nil { return err } } return nil } func metricName(m Metric, header *Header, strings map[uint64]*String) string { if header.Version == 1 { return string(m.(*Metric1).Name[:]) } return string(strings[m.(*Metric2).Name].Payload[:]) } func writeMetric( w io.Writer, offset uint64, header *Header, metrics map[uint64]Metric, strings map[uint64]*String, ) error { m := metrics[offset] Name := metricName(m, header, strings) if _, err := fmt.Fprintf(w, "\t[%v/%v] %v\n", m.Item(), offset, Name); err != nil { return err } _, err := fmt.Fprintf(w, "\t\ttype=%v (0x%x), sem=%v (0x%x), pad=0x%x\n", m.Typ(), int(m.Typ()), m.Sem(), int(m.Sem()), m.Padding()) if err != nil { return err } if _, err := fmt.Fprintf(w, "\t\tunits=%v\n", m.Unit()); err != nil { return err } if m.Indom() == NoIndom { if _, err := fmt.Fprintf(w, "\t\t(no indom)\n"); err != nil { return err } } else { if _, err := fmt.Fprintf(w, "\t\tindom=%d\n", m.Indom()); err != nil { return err } } if m.ShortText() == 0 { if _, err := fmt.Fprintf(w, "\t\t(no shorttext)\n"); err != nil { return err } } else { if _, err := fmt.Fprintf(w, "\t\tshorttext=%v\n", string(strings[m.ShortText()].Payload[:])); err != nil { return err } } if m.LongText() == 0 { if _, err := fmt.Fprintf(w, "\t\t(no longtext)\n"); err != nil { return err } } else { if _, err := fmt.Fprintf(w, "\t\tlongtext=%v\n", string(strings[m.LongText()].Payload[:])); err != nil { return err } } return nil } func writeValue( w io.Writer, offset uint64, header *Header, metrics map[uint64]Metric, values map[uint64]*Value, instances map[uint64]Instance, strings map[uint64]*String, ) error { v := values[offset] m := metrics[v.Metric] if _, err := fmt.Fprintf(w, "\t[%v/%v] %v", m.Item(), offset, metricName(m, header, strings)); err != nil { return err } var ( a interface{} err error ) if m.Typ() != StringType { a, err = FixedVal(v.Val, m.Typ()) if err != nil { return err } } else { v, ok := strings[uint64(v.Extra)] if !ok { return errors.Errorf("invalid string address") } a = string(v.Payload[:]) } if m.Indom() != NoIndom && m.Indom() != 0 { i := instances[v.Instance] if _, err := fmt.Fprintf(w, "[%d or \"%s\"]", i.Internal(), instanceName(i, header, strings)); err != nil { return err } } _, err = fmt.Fprintf(w, " = %v\n", a) return err } func writeString(w io.Writer, offset uint64, strings map[uint64]*String) error { _, err := fmt.Fprintf(w, "\t[%v] %v\n", offset, string(strings[offset].Payload[:])) return err } func writeComponents( w io.Writer, header *Header, tocs []*Toc, metrics map[uint64]Metric, values map[uint64]*Value, instances map[uint64]Instance, indoms map[uint64]*InstanceDomain, strings map[uint64]*String, ) error { var ( toff = HeaderLength itemtype string itemsize uint64 writeItem func(uint64) error InstanceLength, MetricLength uint64 ) if header.Version == 1 { InstanceLength = Instance1Length MetricLength = Metric1Length } else { InstanceLength = Instance2Length MetricLength = Metric2Length } for ti, toc := range tocs { switch toc.Type { case TocInstances: itemtype = "instances" itemsize = InstanceLength writeItem = func(off uint64) error { return writeInstance(w, off, header, instances, indoms, strings) } case TocIndoms: itemtype = "indoms" itemsize = InstanceDomainLength writeItem = func(off uint64) error { return writeInstanceDomain(w, off, indoms, strings) } case TocMetrics: itemtype = "metric" itemsize = MetricLength writeItem = func(off uint64) error { return writeMetric(w, off, header, metrics, strings) } case TocValues: itemtype = "values" itemsize = ValueLength writeItem = func(off uint64) error { return writeValue(w, off, header, metrics, values, instances, strings) } case TocStrings: itemtype = "strings" itemsize = StringLength writeItem = func(off uint64) error { return writeString(w, off, strings) } } if _, err := fmt.Fprintf(w, "TOC[%v], offset: %v, %v offset: %v (%v entries)\n", ti, toff, itemtype, toc.Offset, toc.Count); err != nil { return err } for i, offset := int32(0), toc.Offset; i < toc.Count; i, offset = i+1, offset+itemsize { if err := writeItem(offset); err != nil { return err } } if _, err := fmt.Fprintln(w); err != nil { return err } toff += TocLength } return nil } // Write creates a writable representation of a MMV dump // and writes it to the passed writer. func Write( w io.Writer, header *Header, tocs []*Toc, metrics map[uint64]Metric, values map[uint64]*Value, instances map[uint64]Instance, indoms map[uint64]*InstanceDomain, strings map[uint64]*String, ) error { if _, err := fmt.Fprintf(w, `Version = %v Generated = %v Toc Count = %v Cluster = %v Process = %v Flags = 0x%x `, header.Version, header.G1, header.Toc, header.Cluster, header.Process, int(header.Flag)); err != nil { return err } return writeComponents(w, header, tocs, metrics, values, instances, indoms, strings) } golang-github-performancecopilot-speed-4.0.0/mmvflag_string.go000066400000000000000000000007731433655324600246210ustar00rootroot00000000000000// Code generated by "stringer -type=MMVFlag"; DO NOT EDIT package speed import "fmt" const ( _MMVFlag_name_0 = "NoPrefixFlagProcessFlag" _MMVFlag_name_1 = "SentinelFlag" ) var ( _MMVFlag_index_0 = [...]uint8{0, 12, 23} _MMVFlag_index_1 = [...]uint8{0, 12} ) func (i MMVFlag) String() string { switch { case 1 <= i && i <= 2: i -= 1 return _MMVFlag_name_0[_MMVFlag_index_0[i]:_MMVFlag_index_0[i+1]] case i == 4: return _MMVFlag_name_1 default: return fmt.Sprintf("MMVFlag(%d)", i) } } golang-github-performancecopilot-speed-4.0.0/registry.go000066400000000000000000000201661433655324600234500ustar00rootroot00000000000000package speed import ( "fmt" "regexp" "sync" "github.com/pkg/errors" ) // Registry defines a valid set of instance domains and metrics type Registry interface { // checks if an instance domain of the passed name is already present or not HasInstanceDomain(name string) bool // checks if an metric of the passed name is already present or not HasMetric(name string) bool // returns the number of Metrics in the current registry MetricCount() int // returns the number of Values in the current registry ValuesCount() int // returns the number of Instance Domains in the current registry InstanceDomainCount() int // returns the number of instances across all instance domains in the current registry InstanceCount() int // returns the number of non null strings initialized in the current registry StringCount() int // adds a InstanceDomain object to the writer AddInstanceDomain(InstanceDomain) error // adds a InstanceDomain object after constructing it using passed name and instances AddInstanceDomainByName(name string, instances []string) (InstanceDomain, error) // adds a Metric object to the writer AddMetric(Metric) error // adds a Metric object after parsing the passed string for Instances and InstanceDomains AddMetricByString(name string, val interface{}, t MetricType, s MetricSemantics, u MetricUnit) (Metric, error) } // PCPRegistry implements a registry for PCP as the client type PCPRegistry struct { instanceDomains map[string]*PCPInstanceDomain // a cache for instanceDomains metrics map[string]PCPMetric // a cache for metrics // locks indomlock sync.RWMutex metricslock sync.RWMutex // offsets instanceoffset int indomoffset int metricsoffset int valuesoffset int stringsoffset int // counts instanceCount int valueCount int stringcount int mapped bool version2 bool // a flag that maintains whether we need to write mmv version 2 } // NewPCPRegistry creates a new PCPRegistry object func NewPCPRegistry() *PCPRegistry { return &PCPRegistry{ instanceDomains: make(map[string]*PCPInstanceDomain), metrics: make(map[string]PCPMetric), } } // InstanceCount returns the number of instances across all indoms in the registry func (r *PCPRegistry) InstanceCount() int { r.indomlock.RLock() defer r.indomlock.RUnlock() return r.instanceCount } // InstanceDomainCount returns the number of instance domains in the registry func (r *PCPRegistry) InstanceDomainCount() int { r.indomlock.RLock() defer r.indomlock.RUnlock() return len(r.instanceDomains) } // MetricCount returns the number of metrics in the registry func (r *PCPRegistry) MetricCount() int { r.metricslock.RLock() defer r.metricslock.RUnlock() return len(r.metrics) } // ValuesCount returns the number of values in the registry func (r *PCPRegistry) ValuesCount() int { return r.valueCount } // StringCount returns the number of strings in the registry func (r *PCPRegistry) StringCount() int { if r.version2 { return r.stringcount + r.MetricCount() + r.InstanceCount() } return r.stringcount } // HasInstanceDomain returns true if the registry already has an indom of the specified name func (r *PCPRegistry) HasInstanceDomain(name string) bool { r.indomlock.RLock() defer r.indomlock.RUnlock() _, present := r.instanceDomains[name] return present } // HasMetric returns true if the registry already has a metric of the specified name func (r *PCPRegistry) HasMetric(name string) bool { r.metricslock.RLock() defer r.metricslock.RUnlock() _, present := r.metrics[name] return present } // AddInstanceDomain will add a new instance domain to the current registry func (r *PCPRegistry) AddInstanceDomain(indom InstanceDomain) error { if r.HasInstanceDomain(indom.Name()) { return errors.New("InstanceDomain is already defined for the current registry") } r.indomlock.Lock() defer r.indomlock.Unlock() if r.mapped { return errors.New("Cannot add an indom when a mapping is active") } r.instanceDomains[indom.Name()] = indom.(*PCPInstanceDomain) r.instanceCount += indom.InstanceCount() if !r.version2 { for _, v := range indom.Instances() { if len(v) > MaxV1NameLength { r.version2 = true } } } if indom.(*PCPInstanceDomain).shortDescription != "" { r.stringcount++ } if indom.(*PCPInstanceDomain).longDescription != "" { r.stringcount++ } return nil } func (r *PCPRegistry) addMetric(m PCPMetric) { r.metrics[m.Name()] = m if len(m.Name()) > MaxV1NameLength && !r.version2 { r.version2 = true } currentValues := 1 if m.Indom() != nil { currentValues = m.Indom().InstanceCount() } r.valueCount += currentValues if m.Type() == StringType { r.stringcount += currentValues } if m.ShortDescription() != "" { r.stringcount++ } if m.LongDescription() != "" { r.stringcount++ } } // AddMetric will add a new metric to the current registry func (r *PCPRegistry) AddMetric(m Metric) error { if r.mapped { return errors.New("cannot add a metric when a mapping is active") } if r.HasMetric(m.Name()) { return errors.New("metric is already defined for the current registry") } pcpm := m.(PCPMetric) // if it is an indom metric if pcpm.Indom() != nil && !r.HasInstanceDomain(pcpm.Indom().Name()) { err := r.AddInstanceDomain(pcpm.Indom()) if err != nil { return err } } r.metricslock.Lock() defer r.metricslock.Unlock() r.addMetric(pcpm) return nil } // AddInstanceDomainByName adds an instance domain using passed parameters func (r *PCPRegistry) AddInstanceDomainByName(name string, instances []string) (InstanceDomain, error) { if r.HasInstanceDomain(name) { return nil, errors.New("The InstanceDomain already exists for this registry") } indom, err := NewPCPInstanceDomain(name, instances) if err != nil { return nil, err } err = r.AddInstanceDomain(indom) if err != nil { return nil, err } return indom, nil } const id = "[\\p{L}\\p{N}_]+" var instancesPattern = fmt.Sprintf("(%v)((,\\s?(%v))*)", id, id) var pattern = fmt.Sprintf("\\A((%v)(\\.%v)*?)(\\[(%v)\\])?((\\.%v)*)\\z", id, id, instancesPattern, id) var ireg, _ = regexp.Compile(id) var reg, _ = regexp.Compile(pattern) func parseString(s string) (metric string, indom string, instances []string, err error) { if !reg.MatchString(s) { return "", "", nil, errors.New("Invalid String") } matches := reg.FindStringSubmatch(s) n := len(matches) indom = matches[1] metric = indom + matches[n-2] iarr := matches[5] if iarr != "" { instances = ireg.FindAllString(iarr, -1) } else { instances = nil indom = "" } return } func (r *PCPRegistry) addSingletonMetricByString(name string, val interface{}, t MetricType, s MetricSemantics, u MetricUnit) (Metric, error) { m, err := NewPCPSingletonMetric(val, name, t, s, u) if err != nil { return nil, err } err = r.AddMetric(m) if err != nil { return nil, err } return m, nil } func (r *PCPRegistry) addInstanceMetricByString(name string, val interface{}, indom string, instances []string, t MetricType, s MetricSemantics, u MetricUnit) (Metric, error) { // instance metric mp, ok := val.(Instances) if !ok { return nil, errors.New("to define an instance metric, a Instances type is required") } var ( id InstanceDomain err error ) if !r.HasInstanceDomain(indom) { id, err = r.AddInstanceDomainByName(indom, instances) if err != nil { return nil, err } } else if r.instanceDomains[indom].MatchInstances(instances) { id = r.instanceDomains[indom] } else { return nil, errors.Errorf("a different instance domain under the name %v already exists in the registry", indom) } m, err := NewPCPInstanceMetric(mp, name, id.(*PCPInstanceDomain), t, s, u) if err != nil { return nil, err } err = r.AddMetric(m) if err != nil { return nil, err } return m, nil } // AddMetricByString dynamically creates a PCPMetric func (r *PCPRegistry) AddMetricByString(str string, val interface{}, t MetricType, s MetricSemantics, u MetricUnit) (Metric, error) { metric, indom, instances, err := parseString(str) if err != nil { return nil, err } if instances == nil { return r.addSingletonMetricByString(metric, val, t, s, u) } return r.addInstanceMetricByString(metric, val, indom, instances, t, s, u) } golang-github-performancecopilot-speed-4.0.0/registry_test.go000066400000000000000000000076731433655324600245170ustar00rootroot00000000000000package speed import "testing" func TestIdentifierRegex(t *testing.T) { cases := []struct { val, indom, metric string instances []string }{ {"sheep[baabaablack].bagsfull.count", "sheep", "sheep.bagsfull.count", []string{"baabaablack"}}, {"sheep[limpy].legs.available", "sheep", "sheep.legs.available", []string{"limpy"}}, {"cow.how.now", "", "cow.how.now", nil}, {"sheep[limpy,grumpy,chumpy].legs.available", "sheep", "sheep.legs.available", []string{"limpy", "grumpy", "chumpy"}}, {"a", "", "a", []string{}}, {"a_b", "", "a_b", []string{}}, {"a_b._i", "", "a_b._i", []string{}}, {"a_b[c_d, e_f, g_h]._i", "a_b", "a_b._i", []string{"c_d", "e_f", "g_h"}}, } for _, c := range cases { m, id, i, err := parseString(c.val) if err != nil { t.Errorf("Error: '%v' while parsing %v", err, c.val) continue } if id != c.indom { t.Errorf("Wrong InstanceDomain for %v, expected %v, got %v", c.val, c.indom, id) } if m != c.metric { t.Errorf("Wrong Metric for %v, expected %v, got %v", c.val, c.metric, m) } if len(i) != len(c.instances) { t.Errorf("Wrong number of Instances for %v, expected %v, got %v", c.val, len(c.instances), i) } else { m := make(map[string]bool) for x := 0; x < len(i); x++ { m[i[x]] = true } for x := 0; x < len(i); x++ { _, present := m[c.instances[x]] if !present { t.Errorf("Instance %v not found in input", c.instances[x]) } } } } } func TestStringSingletonConstruction(t *testing.T) { r := NewPCPRegistry() m, err := r.AddMetricByString("cow.how.now", 10, Int32Type, CounterSemantics, OneUnit) if err != nil { t.Error("Cannot parse, error", err) return } sm, ok := m.(*PCPSingletonMetric) if !ok { t.Error("Expected a PCPSingletonMetric") } if sm.Name() != "cow.how.now" { t.Errorf("Expected metric name to be %v, got %v", "cow.how.now", sm.Name()) } if sm.Val() != int32(10) { t.Errorf("Expected metric value to be %v, got %v", 10, sm.Val()) } if r.InstanceCount() != 0 { t.Error("Expected Instance Count to be 0") } if r.InstanceDomainCount() != 0 { t.Error("Expected Instance Domain Count to be 0") } if r.MetricCount() != 1 { t.Error("Expected Metric Count to be 1") } } func TestStringInstanceConstruction(t *testing.T) { r := NewPCPRegistry() m, err := r.AddMetricByString("sheep[limpy,grumpy,chumpy].legs.available", Instances{ "limpy": 10, "grumpy": 20, "chumpy": 30, }, Int32Type, CounterSemantics, OneUnit) if err != nil { t.Error("Cannot parse, error", err) } im := m.(*PCPInstanceMetric) if im.Name() != "sheep.legs.available" { t.Errorf("Expected metric name to be %v, got %v", "sheep.legs.available", im.Name()) } for i, v := range map[string]int32{"limpy": 10, "grumpy": 20, "chumpy": 30} { val, err := im.ValInstance(i) if err != nil { t.Errorf("error retrieving instance %v value", i) } if val != v { t.Errorf("wrong value for instance %v, expected %v, got %v", i, v, val) } } if r.InstanceCount() != 3 { t.Errorf("Expected Instance Count to be 3, got %v", r.InstanceCount()) } if r.InstanceDomainCount() != 1 { t.Errorf("Expected Instance Domain Count to be 1, got %v", r.InstanceDomainCount()) } if r.MetricCount() != 1 { t.Errorf("Expected Metric Count to be 1, got %v", r.MetricCount()) } if r.ValuesCount() != 3 { t.Errorf("Expected Value Count to be 3, got %v", r.ValuesCount()) } } func TestMMV2MetricRegistration(t *testing.T) { r := NewPCPRegistry() m, err := NewPCPSingletonMetric(10, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", Int32Type, CounterSemantics, OneUnit) if err != nil { t.Errorf("cannot create metric, error: %v", err) return } err = r.AddMetric(m) if err != nil { t.Errorf("cannot add metric to registry, error: %v", err) return } if r.StringCount() != 1 { t.Errorf("expected the metric name to be registered in the strings section") } } golang-github-performancecopilot-speed-4.0.0/spaceunit_string.go000066400000000000000000000020731433655324600251560ustar00rootroot00000000000000// Code generated by "stringer -type=SpaceUnit"; DO NOT EDIT package speed import "fmt" const ( _SpaceUnit_name_0 = "ByteUnit" _SpaceUnit_name_1 = "KilobyteUnit" _SpaceUnit_name_2 = "MegabyteUnit" _SpaceUnit_name_3 = "GigabyteUnit" _SpaceUnit_name_4 = "TerabyteUnit" _SpaceUnit_name_5 = "PetabyteUnit" _SpaceUnit_name_6 = "ExabyteUnit" ) var ( _SpaceUnit_index_0 = [...]uint8{0, 8} _SpaceUnit_index_1 = [...]uint8{0, 12} _SpaceUnit_index_2 = [...]uint8{0, 12} _SpaceUnit_index_3 = [...]uint8{0, 12} _SpaceUnit_index_4 = [...]uint8{0, 12} _SpaceUnit_index_5 = [...]uint8{0, 12} _SpaceUnit_index_6 = [...]uint8{0, 11} ) func (i SpaceUnit) String() string { switch { case i == 268435456: return _SpaceUnit_name_0 case i == 268500992: return _SpaceUnit_name_1 case i == 268566528: return _SpaceUnit_name_2 case i == 268632064: return _SpaceUnit_name_3 case i == 268697600: return _SpaceUnit_name_4 case i == 268763136: return _SpaceUnit_name_5 case i == 268828672: return _SpaceUnit_name_6 default: return fmt.Sprintf("SpaceUnit(%d)", i) } } golang-github-performancecopilot-speed-4.0.0/speed.go000066400000000000000000000030121433655324600226670ustar00rootroot00000000000000// Package speed implements a golang client for the Performance Co-Pilot // instrumentation API. // // It is based on the C/Perl/Python API implemented in PCP core as well as the // Java API implemented by `parfait`, a separate project. // // Some examples on using the API are implemented as executable go programs in the // `examples` subdirectory. package speed import ( "fmt" "hash/fnv" "os" "github.com/pkg/errors" ) // Version is the last tagged version of the package const Version = "4.0.0" var histogramInstances = []string{"min", "max", "mean", "variance", "standard_deviation"} var histogramIndom *PCPInstanceDomain // init maintains a central location of all things that happen when the package is initialized // instead of everything being scattered in multiple source files func init() { if err := initConfig(); err != nil { fmt.Fprintln(os.Stderr, errors.Errorf("error initializing config. maybe PCP isn't installed properly")) } var err error histogramIndom, err = NewPCPInstanceDomain("histogram", histogramInstances) if err != nil { fmt.Fprintln(os.Stderr, errors.Errorf("could not initialize an instance domain for histograms")) } } // generate a unique hash for a string of the specified bit length // NOTE: make sure this is as fast as possible // // see: http://programmers.stackexchange.com/a/145633 func hash(s string, b uint32) uint32 { h := fnv.New32a() _, err := h.Write([]byte(s)) if err != nil { panic(err) } val := h.Sum32() if b == 0 { return val } return val & ((1 << b) - 1) } golang-github-performancecopilot-speed-4.0.0/speed_test.go000066400000000000000000000014101433655324600237260ustar00rootroot00000000000000package speed import ( "strings" "testing" ) func BenchmarkGetHash(b *testing.B) { strings := []string{ "a", "abcdefghijklmnopqrstuvwxyz", "aaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy", } l := len(strings) for i := 0; i < b.N; i++ { _ = hash(strings[i%l], 0) } } //lint:ignore U1000 keeping for now type testWriter struct { message string t testing.TB } func (w *testWriter) Write(b []byte) (int, error) { s := string(b) if !strings.Contains(s, w.message) { w.t.Error("expected log'", string(b), "' to contain", w.message) } return len(b), nil } golang-github-performancecopilot-speed-4.0.0/timeunit_string.go000066400000000000000000000016501433655324600250210ustar00rootroot00000000000000// Code generated by "stringer -type=TimeUnit"; DO NOT EDIT package speed import "fmt" const ( _TimeUnit_name_0 = "NanosecondUnit" _TimeUnit_name_1 = "MicrosecondUnit" _TimeUnit_name_2 = "MillisecondUnit" _TimeUnit_name_3 = "SecondUnit" _TimeUnit_name_4 = "MinuteUnit" _TimeUnit_name_5 = "HourUnit" ) var ( _TimeUnit_index_0 = [...]uint8{0, 14} _TimeUnit_index_1 = [...]uint8{0, 15} _TimeUnit_index_2 = [...]uint8{0, 15} _TimeUnit_index_3 = [...]uint8{0, 10} _TimeUnit_index_4 = [...]uint8{0, 10} _TimeUnit_index_5 = [...]uint8{0, 8} ) func (i TimeUnit) String() string { switch { case i == 16777216: return _TimeUnit_name_0 case i == 16781312: return _TimeUnit_name_1 case i == 16785408: return _TimeUnit_name_2 case i == 16789504: return _TimeUnit_name_3 case i == 16793600: return _TimeUnit_name_4 case i == 16797696: return _TimeUnit_name_5 default: return fmt.Sprintf("TimeUnit(%d)", i) } }