Skip to content

Commit 1844cf7

Browse files
committed
Tool for benchmarking compression of wire messages
1 parent d9fd463 commit 1844cf7

File tree

8 files changed

+651
-0
lines changed

8 files changed

+651
-0
lines changed

tools/protobench/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
The idea is to record the wire messages of the profiling agent and see how well they compress using different
2+
compressors and what the CPU impact is.
3+
4+
To record the wire messages, you need to run the profiling agent with the `-bench-proto-dir` flag.
5+
This will write the wire messages into the given directory. The directory will be created if it does not exist.
6+
7+
You can then use the `protobench` tool to compress the wire messages and see how well they compress and how much
8+
CPU time it takes to compress them.
9+
10+
To run the profiling agent, first have a receiving endpoint, e.g. `devfiler` listening on localhost:11000.
11+
Then run the profiling agent with the `-bench-proto-dir` flag:
12+
```shell
13+
sudo ./opentelemetry-ebpf-profiler -bench-proto-dir=/tmp/protobuf -collection-agent=127.0.0.1:11000 -disable-tls
14+
```
15+
The wire messages are written to `protobuf/`, one file per message.
16+
17+
To compress the wire messages and generate a bar chart, run the `protobench` tool:
18+
```shell
19+
cd tools/protobench
20+
go run ./... -bench-proto-dir=/tmp/protobuf -output-file=results.png
21+
```
22+
If you don't see any errors, the tool will generate a PNG file with a bar chart showing the compression ratio and
23+
compression time for each compressor.
24+
The extension `.csv` can be used to generate a CSV file with the raw data instead of a PNG file.
25+
No `-output-file` flag will display the results in the terminal.
26+
27+
Of course, you can also use the `protobench` tool to compare compression of any other files.
28+
29+
### Example PNG output
30+
31+
![Example output](example.png)

tools/protobench/cli_flags.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Apache License 2.0.
4+
* See the file "LICENSE" for details.
5+
*/
6+
7+
package main
8+
9+
import (
10+
"errors"
11+
"flag"
12+
"os"
13+
"path/filepath"
14+
15+
"github.com/peterbourgon/ff/v3"
16+
)
17+
18+
const (
19+
defaultArgBenchProtoDir = ""
20+
defaultArgOutputFile = ""
21+
)
22+
23+
// Help strings for command line arguments
24+
var (
25+
benchProtoDirHelp = "Directory to store raw protobuf wire messages."
26+
outputFileHelp = "Output file to store the benchmark results (*.csv or *.png)."
27+
)
28+
29+
type arguments struct {
30+
benchProtoDir string
31+
outputFile string
32+
33+
fs *flag.FlagSet
34+
}
35+
36+
func (args *arguments) SanityCheck() error {
37+
if args.benchProtoDir == "" {
38+
return errors.New("no protobuf message directory specified")
39+
}
40+
41+
if args.outputFile != "" {
42+
switch filepath.Ext(args.outputFile) {
43+
case ".csv", ".png":
44+
default:
45+
return errors.New("output file must be either a .csv or .png file")
46+
}
47+
}
48+
49+
return nil
50+
}
51+
52+
// Package-scope variable, so that conditionally compiled other components can refer
53+
// to the same flagset.
54+
55+
func parseArgs() (*arguments, error) {
56+
var args arguments
57+
58+
fs := flag.NewFlagSet("protobench", flag.ExitOnError)
59+
60+
fs.StringVar(&args.benchProtoDir, "bench-proto-dir", defaultArgBenchProtoDir,
61+
benchProtoDirHelp)
62+
63+
fs.StringVar(&args.outputFile, "output-file", defaultArgOutputFile,
64+
outputFileHelp)
65+
66+
fs.Usage = func() {
67+
fs.PrintDefaults()
68+
}
69+
70+
args.fs = fs
71+
72+
return &args, ff.Parse(fs, os.Args[1:],
73+
ff.WithEnvVarPrefix("OTEL_PROTOBENCH"),
74+
ff.WithConfigFileFlag("config"),
75+
ff.WithConfigFileParser(ff.PlainParser),
76+
ff.WithAllowMissingConfigFile(true),
77+
)
78+
}

tools/protobench/compressor.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
6+
"github.com/andybalholm/brotli"
7+
"github.com/klauspost/compress/gzip"
8+
"github.com/klauspost/compress/s2"
9+
"github.com/klauspost/compress/zstd"
10+
"github.com/pierrec/lz4/v4"
11+
)
12+
13+
type compressor interface {
14+
// compress compresses the content and writes it to the pre-allocated buffer.
15+
compress([]byte, *bytes.Buffer) (int64, error)
16+
id() string
17+
}
18+
19+
type noneCompressor struct {
20+
name string
21+
}
22+
23+
func (n noneCompressor) id() string { return n.name }
24+
func (noneCompressor) compress(content []byte, _ *bytes.Buffer) (int64, error) {
25+
return int64(len(content)), nil
26+
}
27+
28+
type gzipCompressor struct {
29+
name string
30+
level int
31+
}
32+
33+
func (g gzipCompressor) id() string { return g.name }
34+
func (g gzipCompressor) compress(content []byte, buf *bytes.Buffer) (int64, error) {
35+
encoder, err := gzip.NewWriterLevel(buf, g.level)
36+
if err != nil {
37+
return 0, err
38+
}
39+
defer encoder.Close()
40+
41+
if _, err = encoder.Write(content); err != nil {
42+
return 0, err
43+
}
44+
if err := encoder.Close(); err != nil {
45+
return 0, err
46+
}
47+
48+
encoder.Flush()
49+
50+
return int64(buf.Len()), nil
51+
}
52+
53+
type zstdCompressor struct {
54+
name string
55+
level zstd.EncoderLevel
56+
}
57+
58+
func (z zstdCompressor) id() string { return z.name }
59+
60+
func (z zstdCompressor) compress(content []byte, buf *bytes.Buffer) (int64, error) {
61+
encoder, err := zstd.NewWriter(buf, zstd.WithEncoderLevel(z.level))
62+
if err != nil {
63+
return 0, err
64+
}
65+
defer encoder.Close()
66+
67+
if _, err = encoder.Write(content); err != nil {
68+
return 0, err
69+
}
70+
71+
encoder.Flush()
72+
73+
return int64(buf.Len()), nil
74+
}
75+
76+
type brotliCompressor struct {
77+
name string
78+
level int
79+
}
80+
81+
func (b brotliCompressor) id() string { return b.name }
82+
83+
func (b brotliCompressor) compress(content []byte, buf *bytes.Buffer) (int64, error) {
84+
encoder := brotli.NewWriterLevel(buf, b.level)
85+
defer encoder.Close()
86+
87+
if _, err := encoder.Write(content); err != nil {
88+
return 0, err
89+
}
90+
91+
encoder.Flush()
92+
93+
return int64(buf.Len()), nil
94+
}
95+
96+
type s2Compressor struct {
97+
name string
98+
level s2.WriterOption
99+
}
100+
101+
func (s s2Compressor) id() string { return s.name }
102+
103+
func (s s2Compressor) compress(content []byte, buf *bytes.Buffer) (int64, error) {
104+
encoder := s2.NewWriter(buf, s.level)
105+
defer encoder.Close()
106+
107+
if _, err := encoder.Write(content); err != nil {
108+
return 0, err
109+
}
110+
111+
encoder.Flush()
112+
113+
return int64(buf.Len()), nil
114+
}
115+
116+
type lz4Compressor struct {
117+
name string
118+
level lz4.CompressionLevel
119+
}
120+
121+
func (l lz4Compressor) id() string { return l.name }
122+
123+
func (l lz4Compressor) compress(content []byte, buf *bytes.Buffer) (int64, error) {
124+
encoder := lz4.NewWriter(buf)
125+
defer encoder.Close()
126+
127+
err := encoder.Apply(lz4.CompressionLevelOption(l.level))
128+
if err != nil {
129+
return 0, err
130+
}
131+
132+
if _, err = encoder.Write(content); err != nil {
133+
return 0, err
134+
}
135+
136+
encoder.Flush()
137+
138+
return int64(buf.Len()), nil
139+
}

tools/protobench/example.png

45.5 KB
Loading

tools/protobench/go.mod

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module github.com/open-telemetry/opentelemetry-ebpf-profiler/tools/protobench
2+
3+
go 1.22.6
4+
5+
require (
6+
github.com/andybalholm/brotli v1.1.0
7+
github.com/klauspost/compress v1.17.9
8+
github.com/peterbourgon/ff/v3 v3.4.0
9+
github.com/pierrec/lz4/v4 v4.1.21
10+
gonum.org/v1/plot v0.14.0
11+
)
12+
13+
require (
14+
git.sr.ht/~sbinet/gg v0.5.0 // indirect
15+
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect
16+
github.com/campoy/embedmd v1.0.0 // indirect
17+
github.com/go-fonts/liberation v0.3.1 // indirect
18+
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 // indirect
19+
github.com/go-pdf/fpdf v0.8.0 // indirect
20+
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
21+
github.com/pmezard/go-difflib v1.0.0 // indirect
22+
golang.org/x/image v0.11.0 // indirect
23+
golang.org/x/text v0.12.0 // indirect
24+
)

tools/protobench/go.sum

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo=
2+
git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE=
3+
git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8=
4+
git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo=
5+
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
6+
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
7+
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
8+
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
9+
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
10+
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
11+
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
12+
github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY=
13+
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
14+
github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ=
15+
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
16+
github.com/go-fonts/latin-modern v0.3.1 h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM=
17+
github.com/go-fonts/latin-modern v0.3.1/go.mod h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0=
18+
github.com/go-fonts/liberation v0.3.1 h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK/ceM=
19+
github.com/go-fonts/liberation v0.3.1/go.mod h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY=
20+
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs=
21+
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM=
22+
github.com/go-pdf/fpdf v0.8.0 h1:IJKpdaagnWUeSkUFUjTcSzTppFxmv8ucGQyNPQWxYOQ=
23+
github.com/go-pdf/fpdf v0.8.0/go.mod h1:gfqhcNwXrsd3XYKte9a7vM3smvU/jB4ZRDrmWSxpfdc=
24+
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
25+
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
26+
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
27+
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
28+
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
29+
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
30+
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
31+
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
32+
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
33+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
34+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
35+
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
36+
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
37+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
38+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
39+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
40+
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
41+
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
42+
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
43+
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
44+
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
45+
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
46+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
47+
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
48+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
49+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
50+
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
51+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
52+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
53+
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
54+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
55+
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
56+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
57+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
58+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
59+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
60+
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
61+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
62+
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
63+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
64+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
65+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
66+
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
67+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
68+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
69+
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
70+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
71+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
72+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
73+
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
74+
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
75+
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
76+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
77+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
78+
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
79+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
80+
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
81+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
82+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
83+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
84+
gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=
85+
gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU=
86+
gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE=
87+
gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU=
88+
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
89+
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
90+
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

0 commit comments

Comments
 (0)