-
Notifications
You must be signed in to change notification settings - Fork 274
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tool for benchmarking compression of wire messages
- Loading branch information
1 parent
6bcd06e
commit 87bb8d7
Showing
8 changed files
with
651 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
The idea is to record the wire messages of the profiling agent and see how well they compress using different | ||
compressors and what the CPU impact is. | ||
|
||
To record the wire messages, you need to run the profiling agent with the `-bench-proto-dir` flag. | ||
This will write the wire messages into the given directory. The directory will be created if it does not exist. | ||
|
||
You can then use the `protobench` tool to compress the wire messages and see how well they compress and how much | ||
CPU time it takes to compress them. | ||
|
||
To run the profiling agent, first have a receiving endpoint, e.g. `devfiler` listening on localhost:11000. | ||
Then run the profiling agent with the `-bench-proto-dir` flag: | ||
```shell | ||
sudo ./opentelemetry-ebpf-profiler -bench-proto-dir=/tmp/protobuf -collection-agent=127.0.0.1:11000 -disable-tls | ||
``` | ||
The wire messages are written to `protobuf/`, one file per message. | ||
|
||
To compress the wire messages and generate a bar chart, run the `protobench` tool: | ||
```shell | ||
cd tools/protobench | ||
go run ./... -bench-proto-dir=/tmp/protobuf -output-file=results.png | ||
``` | ||
If you don't see any errors, the tool will generate a PNG file with a bar chart showing the compression ratio and | ||
compression time for each compressor. | ||
The extension `.csv` can be used to generate a CSV file with the raw data instead of a PNG file. | ||
No `-output-file` flag will display the results in the terminal. | ||
|
||
Of course, you can also use the `protobench` tool to compare compression of any other files. | ||
|
||
### Example PNG output | ||
|
||
![Example output](example.png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Apache License 2.0. | ||
* See the file "LICENSE" for details. | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"errors" | ||
"flag" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/peterbourgon/ff/v3" | ||
) | ||
|
||
const ( | ||
defaultArgBenchProtoDir = "" | ||
defaultArgOutputFile = "" | ||
) | ||
|
||
// Help strings for command line arguments | ||
var ( | ||
benchProtoDirHelp = "Directory to store raw protobuf wire messages." | ||
outputFileHelp = "Output file to store the benchmark results (*.csv or *.png)." | ||
) | ||
|
||
type arguments struct { | ||
benchProtoDir string | ||
outputFile string | ||
|
||
fs *flag.FlagSet | ||
} | ||
|
||
func (args *arguments) SanityCheck() error { | ||
if args.benchProtoDir == "" { | ||
return errors.New("no protobuf message directory specified") | ||
} | ||
|
||
if args.outputFile != "" { | ||
switch filepath.Ext(args.outputFile) { | ||
case ".csv", ".png": | ||
default: | ||
return errors.New("output file must be either a .csv or .png file") | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Package-scope variable, so that conditionally compiled other components can refer | ||
// to the same flagset. | ||
|
||
func parseArgs() (*arguments, error) { | ||
var args arguments | ||
|
||
fs := flag.NewFlagSet("protobench", flag.ExitOnError) | ||
|
||
fs.StringVar(&args.benchProtoDir, "bench-proto-dir", defaultArgBenchProtoDir, | ||
benchProtoDirHelp) | ||
|
||
fs.StringVar(&args.outputFile, "output-file", defaultArgOutputFile, | ||
outputFileHelp) | ||
|
||
fs.Usage = func() { | ||
fs.PrintDefaults() | ||
} | ||
|
||
args.fs = fs | ||
|
||
return &args, ff.Parse(fs, os.Args[1:], | ||
ff.WithEnvVarPrefix("OTEL_PROTOBENCH"), | ||
ff.WithConfigFileFlag("config"), | ||
ff.WithConfigFileParser(ff.PlainParser), | ||
ff.WithAllowMissingConfigFile(true), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
|
||
"github.com/andybalholm/brotli" | ||
"github.com/klauspost/compress/gzip" | ||
"github.com/klauspost/compress/s2" | ||
"github.com/klauspost/compress/zstd" | ||
"github.com/pierrec/lz4/v4" | ||
) | ||
|
||
type compressor interface { | ||
// compress compresses the content and writes it to the pre-allocated buffer. | ||
compress([]byte, *bytes.Buffer) (int64, error) | ||
id() string | ||
} | ||
|
||
type noneCompressor struct { | ||
name string | ||
} | ||
|
||
func (n noneCompressor) id() string { return n.name } | ||
func (noneCompressor) compress(content []byte, _ *bytes.Buffer) (int64, error) { | ||
return int64(len(content)), nil | ||
} | ||
|
||
type gzipCompressor struct { | ||
name string | ||
level int | ||
} | ||
|
||
func (g gzipCompressor) id() string { return g.name } | ||
func (g gzipCompressor) compress(content []byte, buf *bytes.Buffer) (int64, error) { | ||
encoder, err := gzip.NewWriterLevel(buf, g.level) | ||
if err != nil { | ||
return 0, err | ||
} | ||
defer encoder.Close() | ||
|
||
if _, err = encoder.Write(content); err != nil { | ||
return 0, err | ||
} | ||
if err := encoder.Close(); err != nil { | ||
return 0, err | ||
} | ||
|
||
encoder.Flush() | ||
|
||
return int64(buf.Len()), nil | ||
} | ||
|
||
type zstdCompressor struct { | ||
name string | ||
level zstd.EncoderLevel | ||
} | ||
|
||
func (z zstdCompressor) id() string { return z.name } | ||
|
||
func (z zstdCompressor) compress(content []byte, buf *bytes.Buffer) (int64, error) { | ||
encoder, err := zstd.NewWriter(buf, zstd.WithEncoderLevel(z.level)) | ||
if err != nil { | ||
return 0, err | ||
} | ||
defer encoder.Close() | ||
|
||
if _, err = encoder.Write(content); err != nil { | ||
return 0, err | ||
} | ||
|
||
encoder.Flush() | ||
|
||
return int64(buf.Len()), nil | ||
} | ||
|
||
type brotliCompressor struct { | ||
name string | ||
level int | ||
} | ||
|
||
func (b brotliCompressor) id() string { return b.name } | ||
|
||
func (b brotliCompressor) compress(content []byte, buf *bytes.Buffer) (int64, error) { | ||
encoder := brotli.NewWriterLevel(buf, b.level) | ||
defer encoder.Close() | ||
|
||
if _, err := encoder.Write(content); err != nil { | ||
return 0, err | ||
} | ||
|
||
encoder.Flush() | ||
|
||
return int64(buf.Len()), nil | ||
} | ||
|
||
type s2Compressor struct { | ||
name string | ||
level s2.WriterOption | ||
} | ||
|
||
func (s s2Compressor) id() string { return s.name } | ||
|
||
func (s s2Compressor) compress(content []byte, buf *bytes.Buffer) (int64, error) { | ||
encoder := s2.NewWriter(buf, s.level) | ||
defer encoder.Close() | ||
|
||
if _, err := encoder.Write(content); err != nil { | ||
return 0, err | ||
} | ||
|
||
encoder.Flush() | ||
|
||
return int64(buf.Len()), nil | ||
} | ||
|
||
type lz4Compressor struct { | ||
name string | ||
level lz4.CompressionLevel | ||
} | ||
|
||
func (l lz4Compressor) id() string { return l.name } | ||
|
||
func (l lz4Compressor) compress(content []byte, buf *bytes.Buffer) (int64, error) { | ||
encoder := lz4.NewWriter(buf) | ||
defer encoder.Close() | ||
|
||
err := encoder.Apply(lz4.CompressionLevelOption(l.level)) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
if _, err = encoder.Write(content); err != nil { | ||
return 0, err | ||
} | ||
|
||
encoder.Flush() | ||
|
||
return int64(buf.Len()), nil | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module github.com/open-telemetry/opentelemetry-ebpf-profiler/tools/protobench | ||
|
||
go 1.22.6 | ||
|
||
require ( | ||
github.com/andybalholm/brotli v1.1.0 | ||
github.com/klauspost/compress v1.17.9 | ||
github.com/peterbourgon/ff/v3 v3.4.0 | ||
github.com/pierrec/lz4/v4 v4.1.21 | ||
gonum.org/v1/plot v0.14.0 | ||
) | ||
|
||
require ( | ||
git.sr.ht/~sbinet/gg v0.5.0 // indirect | ||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect | ||
github.com/campoy/embedmd v1.0.0 // indirect | ||
github.com/go-fonts/liberation v0.3.1 // indirect | ||
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 // indirect | ||
github.com/go-pdf/fpdf v0.8.0 // indirect | ||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
golang.org/x/image v0.11.0 // indirect | ||
golang.org/x/text v0.12.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= | ||
git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= | ||
git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= | ||
git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= | ||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= | ||
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= | ||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= | ||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= | ||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= | ||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= | ||
github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= | ||
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= | ||
github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= | ||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= | ||
github.com/go-fonts/latin-modern v0.3.1 h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM= | ||
github.com/go-fonts/latin-modern v0.3.1/go.mod h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0= | ||
github.com/go-fonts/liberation v0.3.1 h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK/ceM= | ||
github.com/go-fonts/liberation v0.3.1/go.mod h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY= | ||
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= | ||
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM= | ||
github.com/go-pdf/fpdf v0.8.0 h1:IJKpdaagnWUeSkUFUjTcSzTppFxmv8ucGQyNPQWxYOQ= | ||
github.com/go-pdf/fpdf v0.8.0/go.mod h1:gfqhcNwXrsd3XYKte9a7vM3smvU/jB4ZRDrmWSxpfdc= | ||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= | ||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= | ||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= | ||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= | ||
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= | ||
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= | ||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= | ||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | ||
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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= | ||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= | ||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= | ||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= | ||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||
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/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sync v0.1.0/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= | ||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= | ||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= | ||
gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= | ||
gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= | ||
gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= | ||
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= | ||
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= | ||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= |
Oops, something went wrong.