├── benchfmt ├── testdata │ ├── bent │ │ ├── 20200101T024818.BaseNl.stdout │ │ ├── 20200101T024818.TipNl.stdout │ │ ├── 20200101T030626.Basel.stdout │ │ ├── 20200101T030626.Tipl.stdout │ │ ├── 20200101T024818.BaseNl.build │ │ ├── 20200101T024818.TipNl.build │ │ ├── 20200101T030626.Tipl.build │ │ └── 20200101T030626.Basel.build │ └── files │ │ ├── b │ │ └── a ├── internal │ └── bytesconv │ │ ├── README.md │ │ └── ftoa.go ├── writer_test.go ├── units_test.go ├── units.go └── files_test.go ├── cmd ├── benchstat │ ├── testdata │ │ ├── .gitignore │ │ ├── csvErrors.stderr │ │ ├── smallSample.txt │ │ ├── csvErrors.stdout │ │ ├── issue19634.stdout │ │ ├── smallSample.stdout │ │ ├── units.stdout │ │ ├── csvOldNew.stdout │ │ ├── units.txt │ │ ├── issue19565.stdout │ │ ├── issue19634.txt │ │ ├── issue19565.txt │ │ ├── zero.txt │ │ ├── bench_test.go │ │ ├── old.txt │ │ ├── zero.stdout │ │ ├── new.txt │ │ └── crcSizeVsPoly.stdout │ ├── doc_test.go │ └── internal │ │ └── texttab │ │ └── table_test.go └── benchfilter │ └── main.go ├── storage ├── appengine │ ├── README.md │ ├── app.yaml │ ├── static │ │ ├── upload.html │ │ └── index.html │ └── .gcloudignore ├── db │ ├── dbtest │ │ ├── nocloud.go │ │ ├── dbtest.go │ │ └── cloud.go │ ├── export_test.go │ ├── schema.sql │ ├── sqlite3 │ │ └── sqlite3.go │ └── query_test.go ├── app │ ├── appengine.go │ ├── local.go │ ├── app.go │ ├── query.go │ └── upload_test.go ├── query │ ├── query_test.go │ └── query.go ├── fs │ ├── gcs │ │ └── gcs.go │ ├── local │ │ ├── local_test.go │ │ └── local.go │ └── fs.go └── localperfdata │ └── app.go ├── analysis ├── doc.go ├── appengine │ ├── app.yaml │ ├── README.md │ ├── .gcloudignore │ ├── app.go │ └── template │ │ ├── index.html │ │ └── trend.html ├── app │ ├── appengine.go │ ├── local.go │ ├── trend_test.go │ ├── parse_test.go │ ├── index.go │ ├── kza_test.go │ ├── parse.go │ └── app.go └── localperf │ └── app.go ├── benchseries └── doc.go ├── benchproc ├── internal │ └── parse │ │ ├── doc.go │ │ ├── projection_test.go │ │ ├── tree.go │ │ ├── filter_test.go │ │ ├── projection.go │ │ └── filter.go ├── nonsingular_test.go ├── nonsingular.go ├── doc.go ├── keyheader_test.go ├── sort_test.go ├── keyheader.go ├── key.go ├── filter_test.go ├── extract_test.go └── sort.go ├── internal ├── stats │ ├── sample_test.go │ ├── beta_test.go │ ├── package.go │ ├── tdist.go │ ├── normaldist_test.go │ ├── deltadist.go │ ├── mathx.go │ ├── ttest_test.go │ ├── alg.go │ ├── beta.go │ ├── utest_test.go │ ├── tdist_test.go │ ├── util_test.go │ └── normaldist.go ├── basedir │ └── basedir.go └── diff │ └── diff.go ├── benchstat ├── doc.go ├── sort.go ├── delta.go ├── html.go ├── sort_test.go └── scaler.go ├── benchunit ├── parse_test.go ├── tidy_test.go ├── parse.go └── tidy.go ├── benchmath ├── mktables.go ├── anormal.go ├── aexact.go └── sample_test.go ├── CONTRIBUTING.md ├── PATENTS ├── LICENSE ├── README.md └── go.mod /benchfmt/testdata/bent/20200101T024818.BaseNl.stdout: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /benchfmt/testdata/bent/20200101T024818.TipNl.stdout: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /benchfmt/testdata/bent/20200101T030626.Basel.stdout: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /benchfmt/testdata/bent/20200101T030626.Tipl.stdout: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /benchfmt/testdata/files/b: -------------------------------------------------------------------------------- 1 | BenchmarkZ 1 1 ns/op 2 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | *.got-stdout 2 | *.got-stderr 3 | -------------------------------------------------------------------------------- /benchfmt/testdata/files/a: -------------------------------------------------------------------------------- 1 | BenchmarkX 1 1 ns/op 2 | BenchmarkY 1 1 ns/op 3 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/csvErrors.stderr: -------------------------------------------------------------------------------- 1 | B6: benchmarks vary in .fullname 2 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/smallSample.txt: -------------------------------------------------------------------------------- 1 | note: before 2 | 3 | BenchmarkX 1 100 ns/op 4 | 5 | note: after 6 | 7 | BenchmarkX 1 101 ns/op 8 | -------------------------------------------------------------------------------- /storage/appengine/README.md: -------------------------------------------------------------------------------- 1 | # perfdata.golang.org 2 | 3 | Deploy: 4 | 5 | ``` 6 | gcloud app deploy --project=golang-org app.yaml 7 | ``` 8 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/csvErrors.stdout: -------------------------------------------------------------------------------- 1 | goos: linux 2 | goarch: amd64 3 | pkg: golang.org/x/perf/cmd/benchstat/testdata 4 | ,new.txt, 5 | ,sec/op,CI 6 | Encode,2.2530000000000003e-06,37% 7 | geomean,2.2530000000000016e-06, 8 | -------------------------------------------------------------------------------- /analysis/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Deprecated: Moved to golang.org/x/build/perf. 6 | package analysis 7 | -------------------------------------------------------------------------------- /analysis/appengine/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go113 2 | service: perf 3 | 4 | handlers: 5 | - url: /_ah/remote_api 6 | script: auto 7 | - url: /.* 8 | script: auto 9 | secure: always 10 | env_variables: 11 | STORAGE_URL_BASE: 'https://perfdata.golang.org' 12 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/issue19634.stdout: -------------------------------------------------------------------------------- 1 | │ before │ after │ 2 | │ sec/op │ sec/op vs base │ 3 | FloatSub/100-4 115.00n ± ∞ ¹ 78.80n ± ∞ ¹ -31.48% (p=0.008 n=5) 4 | ¹ need >= 6 samples for confidence interval at level 0.95 5 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/smallSample.stdout: -------------------------------------------------------------------------------- 1 | │ before │ after │ 2 | │ sec/op │ sec/op vs base │ 3 | X 100.0n ± ∞ ¹ 101.0n ± ∞ ¹ ~ (p=1.000 n=1) ² 4 | ¹ need >= 6 samples for confidence interval at level 0.95 5 | ² need >= 4 samples to detect a difference at alpha level 0.05 6 | -------------------------------------------------------------------------------- /benchfmt/internal/bytesconv/README.md: -------------------------------------------------------------------------------- 1 | This is a partial copy of strconv.Parse* from Go 1.13.6, converted to 2 | use []byte (and stripped of the overly complex extFloat fast-path). 3 | It makes me sad that we have to do this, but see golang.org/issue/2632. 4 | We can eliminate this if golang.org/issue/43752 (or more generally, 5 | golang.org/issue/2205) gets fixed. 6 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/units.stdout: -------------------------------------------------------------------------------- 1 | │ before │ after │ 2 | │ text-bytes │ text-bytes vs base │ 3 | Size 100.0 ± 0% 105.0 ± 0% +5.00% (n=1) 4 | NonExact 101.0 ± 1% ¹ 101.0 ± 0% 0.00% (n=3) 5 | geomean 100.5 103.0 +2.47% 6 | ¹ exact distribution expected, but values range from 100 to 101 7 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/csvOldNew.stdout: -------------------------------------------------------------------------------- 1 | goos: linux 2 | goarch: amd64 3 | pkg: golang.org/x/perf/cmd/benchstat/testdata 4 | ,old.txt,,new.txt,,, 5 | ,sec/op,CI,sec/op,CI,vs base,P 6 | Encode/format=json-48,1.7180000000000001e-06,1%,1.4225000000000001e-06,1%,-17.20%,p=0.000 n=10 7 | Encode/format=gob-48,3.0655e-06,0%,3.0700000000000003e-06,2%,~,p=0.446 n=10 8 | geomean,2.294891936453654e-06,,2.089754770302007e-06,,-8.94%, 9 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/units.txt: -------------------------------------------------------------------------------- 1 | Unit text-bytes assume=exact 2 | 3 | note: before 4 | 5 | BenchmarkSize 1 100 text-bytes 6 | 7 | BenchmarkNonExact 1 100 text-bytes 8 | BenchmarkNonExact 1 101 text-bytes 9 | BenchmarkNonExact 1 101 text-bytes 10 | 11 | note: after 12 | 13 | BenchmarkSize 1 105 text-bytes 14 | 15 | BenchmarkNonExact 1 101 text-bytes 16 | BenchmarkNonExact 1 101 text-bytes 17 | BenchmarkNonExact 1 101 text-bytes 18 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/issue19565.stdout: -------------------------------------------------------------------------------- 1 | │ before │ after │ 2 | │ sec/op │ sec/op vs base │ 3 | A 100.0n ± 0% 100.0n ± 0% ~ (p=1.000 n=6) ¹ 4 | B 10.00µ ± 0% 5 | C 10.00µ ± 0% 6 | geomean 1.000µ 1.000µ +0.00% ² 7 | ¹ all samples are equal 8 | ² benchmark set differs from baseline; geomeans may not be comparable 9 | -------------------------------------------------------------------------------- /benchseries/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // The benchseries package generates performance differentials and analysis tailored 6 | // to the output of tools and benchmarks in golang.org/x/benchmarks. 7 | // 8 | // WARNING: The API is experimental, unstable, and subject to change at any time. 9 | package benchseries 10 | -------------------------------------------------------------------------------- /storage/db/dbtest/nocloud.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !cloud && !plan9 6 | 7 | package dbtest 8 | 9 | import ( 10 | "testing" 11 | ) 12 | 13 | // createEmptyDB makes a new, empty database for the test. 14 | func createEmptyDB(t *testing.T) (driver, dsn string, cleanup func()) { 15 | return "sqlite3", ":memory:", nil 16 | } 17 | -------------------------------------------------------------------------------- /storage/db/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package db 6 | 7 | import ( 8 | "database/sql" 9 | "time" 10 | ) 11 | 12 | func DBSQL(db *DB) *sql.DB { 13 | return db.sql 14 | } 15 | 16 | func SetNow(t time.Time) { 17 | if t.IsZero() { 18 | now = time.Now 19 | return 20 | } 21 | now = func() time.Time { return t } 22 | } 23 | -------------------------------------------------------------------------------- /benchproc/internal/parse/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package parse implements parsers for golang.org/x/perf/benchproc/syntax. 6 | // 7 | // Currently this package is internal to benchproc, but if we ever 8 | // migrate perf.golang.org to this expression syntax, it will be 9 | // valuable to construct database queries from the same grammar. 10 | package parse 11 | -------------------------------------------------------------------------------- /storage/appengine/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go113 2 | service: perfdata 3 | instance_class: F4_HIGHMEM 4 | 5 | handlers: 6 | - url: /_ah/remote_api 7 | script: auto 8 | - url: /.* 9 | script: auto 10 | secure: always 11 | env_variables: 12 | CLOUDSQL_CONNECTION_NAME: 'golang-org:us-central1:golang-org' 13 | CLOUDSQL_USER: 'root' 14 | CLOUDSQL_PASSWORD: '' 15 | CLOUDSQL_DATABASE: 'perfdata' 16 | GCS_BUCKET: 'golang-perfdata' 17 | PERFDATA_VIEW_URL_BASE: 'https://perf.golang.org/search?q=upload:' 18 | -------------------------------------------------------------------------------- /storage/appengine/static/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Upload Performance Results 5 | 6 | 7 |

Upload one or more benchmark files.

8 |
9 |
10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /analysis/appengine/README.md: -------------------------------------------------------------------------------- 1 | # perf.golang.org 2 | 3 | Deploy: 4 | 5 | 1. `gcloud app deploy --project=golang-org --no-promote app.yaml` 6 | 7 | 2. Find the new version in the 8 | [Cloud Console](https://console.cloud.google.com/appengine/versions?project=golang-org&serviceId=perf). 9 | 10 | 3. Check that the deployed version is working (click the website link in the version list). 11 | 12 | 4. If all is well, click "Migrate Traffic" to move 100% of the perf.golang.org traffic to the new version. 13 | 14 | 5. You're done. 15 | -------------------------------------------------------------------------------- /internal/stats/sample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import "testing" 8 | 9 | func TestSamplePercentile(t *testing.T) { 10 | s := Sample{Xs: []float64{15, 20, 35, 40, 50}} 11 | testFunc(t, "Percentile", s.Percentile, map[float64]float64{ 12 | -1: 15, 13 | 0: 15, 14 | .05: 15, 15 | .30: 19.666666666666666, 16 | .40: 27, 17 | .95: 50, 18 | 1: 50, 19 | 2: 50, 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /benchstat/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package benchstat is deprecated. 6 | // 7 | // This package contains the underlying implementation of an old 8 | // version of the benchstat command. 9 | // 10 | // Deprecated: The latest version of benchstat can be found at 11 | // golang.org/x/perf/cmd/benchstat. To work with benchmark data, see 12 | // golang.org/x/perf/benchproc and golang.org/x/perf/benchmath. 13 | package benchstat 14 | -------------------------------------------------------------------------------- /analysis/app/appengine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build appengine 6 | 7 | package app 8 | 9 | import ( 10 | "context" 11 | "net/http" 12 | 13 | "google.golang.org/appengine" 14 | "google.golang.org/appengine/log" 15 | ) 16 | 17 | // requestContext returns the Context object for a given request. 18 | func requestContext(r *http.Request) context.Context { 19 | return appengine.NewContext(r) 20 | } 21 | 22 | var infof = log.Infof 23 | var errorf = log.Errorf 24 | -------------------------------------------------------------------------------- /storage/app/appengine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build appengine 6 | 7 | package app 8 | 9 | import ( 10 | "context" 11 | "net/http" 12 | 13 | "google.golang.org/appengine" 14 | "google.golang.org/appengine/log" 15 | ) 16 | 17 | // requestContext returns the Context object for a given request. 18 | func requestContext(r *http.Request) context.Context { 19 | return appengine.NewContext(r) 20 | } 21 | 22 | var infof = log.Infof 23 | var errorf = log.Errorf 24 | -------------------------------------------------------------------------------- /storage/app/local.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !appengine 6 | 7 | package app 8 | 9 | import ( 10 | "context" 11 | "log" 12 | "net/http" 13 | ) 14 | 15 | // requestContext returns the Context object for a given request. 16 | func requestContext(r *http.Request) context.Context { 17 | return r.Context() 18 | } 19 | 20 | func infof(_ context.Context, format string, args ...interface{}) { 21 | log.Printf(format, args...) 22 | } 23 | 24 | var errorf = infof 25 | -------------------------------------------------------------------------------- /analysis/app/local.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !appengine 6 | 7 | package app 8 | 9 | import ( 10 | "context" 11 | "log" 12 | "net/http" 13 | ) 14 | 15 | // requestContext returns the Context object for a given request. 16 | func requestContext(r *http.Request) context.Context { 17 | return r.Context() 18 | } 19 | 20 | func infof(_ context.Context, format string, args ...interface{}) { 21 | log.Printf(format, args...) 22 | } 23 | 24 | var errorf = infof 25 | -------------------------------------------------------------------------------- /benchfmt/internal/bytesconv/ftoa.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Binary to decimal floating point conversion. 6 | // Algorithm: 7 | // 1) store mantissa in multiprecision decimal 8 | // 2) shift decimal by exponent 9 | // 3) read digits out & format 10 | 11 | package bytesconv 12 | 13 | // TODO: move elsewhere? 14 | type floatInfo struct { 15 | mantbits uint 16 | expbits uint 17 | bias int 18 | } 19 | 20 | var float32info = floatInfo{23, 8, -127} 21 | var float64info = floatInfo{52, 11, -1023} 22 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/issue19634.txt: -------------------------------------------------------------------------------- 1 | note: before 2 | 3 | BenchmarkFloatSub/100-4 20000000 115 ns/op 4 | BenchmarkFloatSub/100-4 20000000 114 ns/op 5 | BenchmarkFloatSub/100-4 20000000 115 ns/op 6 | BenchmarkFloatSub/100-4 20000000 115 ns/op 7 | BenchmarkFloatSub/100-4 20000000 115 ns/op 8 | 9 | note: after 10 | 11 | BenchmarkFloatSub/100-4 20000000 78.8 ns/op 12 | BenchmarkFloatSub/100-4 20000000 78.8 ns/op 13 | BenchmarkFloatSub/100-4 20000000 78.8 ns/op 14 | BenchmarkFloatSub/100-4 20000000 78.8 ns/op 15 | BenchmarkFloatSub/100-4 20000000 78.8 ns/op 16 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/issue19565.txt: -------------------------------------------------------------------------------- 1 | note: before 2 | 3 | BenchmarkA 10 100 ns/op 4 | BenchmarkA 10 100 ns/op 5 | BenchmarkA 10 100 ns/op 6 | BenchmarkA 10 100 ns/op 7 | BenchmarkA 10 100 ns/op 8 | BenchmarkA 10 100 ns/op 9 | BenchmarkB 10 10000 ns/op 10 | BenchmarkB 10 10000 ns/op 11 | BenchmarkB 10 10000 ns/op 12 | BenchmarkB 10 10000 ns/op 13 | BenchmarkB 10 10000 ns/op 14 | BenchmarkB 10 10000 ns/op 15 | 16 | note: after 17 | 18 | BenchmarkA 10 100 ns/op 19 | BenchmarkA 10 100 ns/op 20 | BenchmarkA 10 100 ns/op 21 | BenchmarkA 10 100 ns/op 22 | BenchmarkA 10 100 ns/op 23 | BenchmarkA 10 100 ns/op 24 | BenchmarkC 10 10000 ns/op 25 | BenchmarkC 10 10000 ns/op 26 | BenchmarkC 10 10000 ns/op 27 | BenchmarkC 10 10000 ns/op 28 | BenchmarkC 10 10000 ns/op 29 | BenchmarkC 10 10000 ns/op 30 | -------------------------------------------------------------------------------- /storage/db/schema.sql: -------------------------------------------------------------------------------- 1 | -- The intended production Cloud SQL schema. Committed here only as a 2 | -- form of notes (see the actual current schema in 3 | -- db.go:createTables). 4 | 5 | CREATE TABLE Uploads ( 6 | UploadId SERIAL PRIMARY KEY AUTO_INCREMENT 7 | ); 8 | CREATE TABLE Records ( 9 | UploadId BIGINT UNSIGNED, 10 | RecordId BIGINT UNSIGNED, 11 | Contents BLOB, 12 | PRIMARY KEY (UploadId, RecordId), 13 | FOREIGN KEY (UploadId) REFERENCES Uploads(UploadId) 14 | ); 15 | CREATE TABLE RecordLabels ( 16 | UploadId BIGINT UNSIGNED, 17 | RecordId BIGINT UNSIGNED, 18 | Name VARCHAR(255), 19 | Value VARCHAR(8192), 20 | INDEX (Name(100), Value(100)), 21 | FOREIGN KEY (UploadId, RecordId) REFERENCES Records(UploadId, RecordId) 22 | ); 23 | -------------------------------------------------------------------------------- /analysis/appengine/.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud Platform 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | .gcloudignore 10 | # If you would like to upload your .git directory, .gitignore file or files 11 | # from your .gitignore file, remove the corresponding line 12 | # below: 13 | .git 14 | .gitignore 15 | 16 | # Binaries for programs and plugins 17 | *.exe 18 | *.exe~ 19 | *.dll 20 | *.so 21 | *.dylib 22 | # Test binary, build with `go test -c` 23 | *.test 24 | # Output of the go coverage tool, specifically when used with LiteIDE 25 | *.out -------------------------------------------------------------------------------- /storage/appengine/.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud Platform 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | .gcloudignore 10 | # If you would like to upload your .git directory, .gitignore file or files 11 | # from your .gitignore file, remove the corresponding line 12 | # below: 13 | .git 14 | .gitignore 15 | 16 | # Binaries for programs and plugins 17 | *.exe 18 | *.exe~ 19 | *.dll 20 | *.so 21 | *.dylib 22 | # Test binary, build with `go test -c` 23 | *.test 24 | # Output of the go coverage tool, specifically when used with LiteIDE 25 | *.out -------------------------------------------------------------------------------- /internal/stats/beta_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestBetaInc(t *testing.T) { 12 | // Example values from MATLAB betainc documentation. 13 | testFunc(t, "I_0.5(%v, 3)", 14 | func(a float64) float64 { return mathBetaInc(0.5, a, 3) }, 15 | map[float64]float64{ 16 | 0: 1.00000000000000, 17 | 1: 0.87500000000000, 18 | 2: 0.68750000000000, 19 | 3: 0.50000000000000, 20 | 4: 0.34375000000000, 21 | 5: 0.22656250000000, 22 | 6: 0.14453125000000, 23 | 7: 0.08984375000000, 24 | 8: 0.05468750000000, 25 | 9: 0.03271484375000, 26 | 10: 0.01928710937500, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /storage/db/sqlite3/sqlite3.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build cgo 6 | 7 | // Package sqlite3 provides the sqlite3 driver for 8 | // x/perf/storage/db. It must be imported instead of go-sqlite3 to 9 | // ensure foreign keys are properly honored. 10 | package sqlite3 11 | 12 | import ( 13 | "database/sql" 14 | 15 | sqlite3 "github.com/mattn/go-sqlite3" 16 | "golang.org/x/perf/storage/db" 17 | ) 18 | 19 | func init() { 20 | db.RegisterOpenHook("sqlite3", func(db *sql.DB) error { 21 | db.Driver().(*sqlite3.SQLiteDriver).ConnectHook = func(c *sqlite3.SQLiteConn) error { 22 | _, err := c.Exec("PRAGMA foreign_keys = ON;", nil) 23 | return err 24 | } 25 | return nil 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /benchunit/parse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchunit 6 | 7 | import "testing" 8 | 9 | func TestClassOf(t *testing.T) { 10 | test := func(unit string, cls Class) { 11 | t.Helper() 12 | got := ClassOf(unit) 13 | if got != cls { 14 | t.Errorf("for %s, want %s, got %s", unit, cls, got) 15 | } 16 | } 17 | test("ns/op", Decimal) 18 | test("sec/op", Decimal) 19 | test("sec/B", Decimal) 20 | test("sec/B/B", Decimal) 21 | test("sec/disk-B", Decimal) 22 | 23 | test("B/op", Binary) 24 | test("bytes/op", Binary) 25 | test("B/s", Binary) 26 | test("B/sec", Binary) 27 | test("sec/B*B", Binary) // Discouraged 28 | test("disk-B/sec", Binary) 29 | test("disk-B/sec", Binary) 30 | } 31 | -------------------------------------------------------------------------------- /internal/stats/package.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package stats implements several statistical distributions, 6 | // hypothesis tests, and functions for descriptive statistics. 7 | // 8 | // Currently stats is fairly small, but for what it does implement, it 9 | // focuses on high quality, fast implementations with good, idiomatic 10 | // Go APIs. 11 | // 12 | // This is a trimmed fork of github.com/aclements/go-moremath/stats. 13 | package stats 14 | 15 | import ( 16 | "errors" 17 | "math" 18 | ) 19 | 20 | var inf = math.Inf(1) 21 | var nan = math.NaN() 22 | 23 | // TODO: Put all errors in the same place and maybe unify them. 24 | 25 | var ( 26 | ErrSamplesEqual = errors.New("all samples are equal") 27 | ) 28 | -------------------------------------------------------------------------------- /storage/query/query_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package query 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestSplitQueryWords(t *testing.T) { 13 | for _, test := range []struct { 14 | q string 15 | want []string 16 | }{ 17 | {"hello world", []string{"hello", "world"}}, 18 | {"hello\\ world", []string{"hello world"}}, 19 | {`"key:value two" and\ more`, []string{"key:value two", "and more"}}, 20 | {`one" two"\ three four`, []string{"one two three", "four"}}, 21 | {`"4'7\""`, []string{`4'7"`}}, 22 | } { 23 | have := SplitWords(test.q) 24 | if !reflect.DeepEqual(have, test.want) { 25 | t.Errorf("splitQueryWords(%q) = %+v, want %+v", test.q, have, test.want) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/stats/tdist.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import "math" 8 | 9 | // A TDist is a Student's t-distribution with V degrees of freedom. 10 | type TDist struct { 11 | V float64 12 | } 13 | 14 | func (t TDist) PDF(x float64) float64 { 15 | return math.Exp(lgamma((t.V+1)/2)-lgamma(t.V/2)) / 16 | math.Sqrt(t.V*math.Pi) * math.Pow(1+(x*x)/t.V, -(t.V+1)/2) 17 | } 18 | 19 | func (t TDist) CDF(x float64) float64 { 20 | if x == 0 { 21 | return 0.5 22 | } else if x > 0 { 23 | return 1 - 0.5*mathBetaInc(t.V/(t.V+x*x), t.V/2, 0.5) 24 | } else if x < 0 { 25 | return 1 - t.CDF(-x) 26 | } else { 27 | return math.NaN() 28 | } 29 | } 30 | 31 | func (t TDist) Bounds() (float64, float64) { 32 | return -4, 4 33 | } 34 | -------------------------------------------------------------------------------- /benchunit/tidy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchunit 6 | 7 | import "testing" 8 | 9 | func TestTidy(t *testing.T) { 10 | test := func(unit, tidied string, factor float64) { 11 | t.Helper() 12 | gotFactor, got := Tidy(1, unit) 13 | if got != tidied || gotFactor != factor { 14 | t.Errorf("for %s, want *%f %s, got *%f %s", unit, factor, tidied, gotFactor, got) 15 | } 16 | } 17 | 18 | test("ns/op", "sec/op", 1e-9) 19 | test("x-ns/op", "x-sec/op", 1e-9) 20 | test("MB/s", "B/s", 1e6) 21 | test("x-MB/s", "x-B/s", 1e6) 22 | test("B/op", "B/op", 1) 23 | test("x-B/op", "x-B/op", 1) 24 | test("x-allocs/op", "x-allocs/op", 1) 25 | 26 | test("op/ns", "op/ns", 1) 27 | test("MB*MB/s", "B*B/s", 1e6*1e6) 28 | test("MB/MB", "B/MB", 1e6) 29 | } 30 | -------------------------------------------------------------------------------- /internal/stats/normaldist_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "testing" 11 | ) 12 | 13 | func TestNormalDist(t *testing.T) { 14 | d := StdNormal 15 | 16 | testFunc(t, fmt.Sprintf("%+v.PDF", d), d.PDF, map[float64]float64{ 17 | -10000: 0, // approx 18 | -1: 1 / math.Sqrt(2*math.Pi) * math.Exp(-0.5), 19 | 0: 1 / math.Sqrt(2*math.Pi), 20 | 1: 1 / math.Sqrt(2*math.Pi) * math.Exp(-0.5), 21 | 10000: 0, // approx 22 | }) 23 | 24 | testFunc(t, fmt.Sprintf("%+v.CDF", d), d.CDF, map[float64]float64{ 25 | -10000: 0, // approx 26 | 0: 0.5, 27 | 10000: 1, // approx 28 | }) 29 | 30 | d2 := NormalDist{Mu: 2, Sigma: 5} 31 | testInvCDF(t, d, false) 32 | testInvCDF(t, d2, false) 33 | } 34 | -------------------------------------------------------------------------------- /analysis/app/trend_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package app 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/aclements/go-gg/table" 11 | "golang.org/x/perf/internal/diff" 12 | ) 13 | 14 | func TestTableToJS(t *testing.T) { 15 | in := table.TableFromStrings( 16 | []string{"text", "num"}, 17 | [][]string{ 18 | {"hello", "15.1"}, 19 | {"world", "20"}, 20 | }, true) 21 | have := tableToJS(in, []column{{Name: "text"}, {Name: "num"}}) 22 | want := `{cols: [{"id":"text","type":"string","label":"text"}, 23 | {"id":"num","type":"number","label":"num"}], 24 | rows: [{c:[{v: "hello"}, {v: 15.1}]}, 25 | {c:[{v: "world"}, {v: 20}]}]}` 26 | if d := diff.Diff(string(have), want); d != "" { 27 | t.Errorf("tableToJS returned wrong JS (- have, + want):\n%s", d) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /benchmath/mktables.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build ignore 6 | 7 | // Mktables pre-computes statistical tables. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | 13 | "github.com/aclements/go-moremath/stats" 14 | ) 15 | 16 | func main() { 17 | var s1, s2 []float64 18 | 19 | // Compute minimal P-value for the U-test given different 20 | // sample sizes. 21 | fmt.Printf("var uTestMinP = []float64{\n") 22 | for n := 1; n < 10; n++ { 23 | // The P-value is minimized when the order statistic 24 | // is maximally separated. 25 | s1 = append(s1, -1) 26 | s2 = append(s2, 1) 27 | res, err := stats.MannWhitneyUTest(s1, s2, stats.LocationDiffers) 28 | if err != nil { 29 | panic(err) 30 | } 31 | fmt.Printf("\t%d: %v,\n", n, res.P) 32 | } 33 | fmt.Printf("}\n") 34 | } 35 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/zero.txt: -------------------------------------------------------------------------------- 1 | Unit a-bytes assume=exact 2 | Unit b-bytes assume=exact 3 | Unit c-bytes assume=exact 4 | Unit x assume=exact 5 | Unit y assume=exact 6 | 7 | note: before 8 | 9 | # We double each benchmark so benchstat shows a geomean row. 10 | 11 | BenchmarkImperceptible 1 1234567890 a-bytes 171717171716 b-bytes 99999930 c-bytes 12 | BenchmarkImperceptible2 1 1234567890 a-bytes 171717171716 b-bytes 99999930 c-bytes 13 | # Ratio should be treated as 1. 14 | BenchmarkZeroOverZero 1 0 x 15 | BenchmarkZeroOverZero2 1 0 x 16 | # Ratio should be treated as uncomputable. 17 | BenchmarkNonZeroOverZero 1 0 y 18 | BenchmarkNonZeroOverZero2 1 0 y 19 | 20 | note: after 21 | 22 | BenchmarkImperceptible 1 1234567890 a-bytes 171717171717 b-bytes 99999929 c-bytes 23 | BenchmarkImperceptible2 1 1234567890 a-bytes 171717171717 b-bytes 99999929 c-bytes 24 | BenchmarkZeroOverZero 1 0 x 25 | BenchmarkZeroOverZero2 1 0 x 26 | BenchmarkNonZeroOverZero 1 100 y 27 | BenchmarkNonZeroOverZero2 1 100 y 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Go 2 | 3 | Go is an open source project. 4 | 5 | It is the work of hundreds of contributors. We appreciate your help! 6 | 7 | ## Filing issues 8 | 9 | When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions: 10 | 11 | 1. What version of Go are you using (`go version`)? 12 | 2. What operating system and processor architecture are you using? 13 | 3. What did you do? 14 | 4. What did you expect to see? 15 | 5. What did you see instead? 16 | 17 | General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker. 18 | The gophers there will answer or ask you to file an issue if you've tripped over a bug. 19 | 20 | ## Contributing code 21 | 22 | Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) 23 | before sending patches. 24 | 25 | Unless otherwise noted, the Go source files are distributed under 26 | the BSD-style license found in the LICENSE file. 27 | -------------------------------------------------------------------------------- /benchfmt/writer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchfmt 6 | 7 | import ( 8 | "bytes" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestWriter(t *testing.T) { 14 | const input = `BenchmarkOne 1 1 ns/op 15 | Unit ns/op a=1 16 | Unit ns/op b=2 17 | Unit MB/s c=3 18 | 19 | key: val 20 | key1: val1 21 | 22 | BenchmarkOne 1 1 ns/op 23 | 24 | key: 25 | 26 | BenchmarkOne 1 1 ns/op 27 | 28 | key: a 29 | 30 | BenchmarkOne 1 1 ns/op 31 | 32 | key1: val2 33 | key: b 34 | 35 | BenchmarkOne 1 1 ns/op 36 | BenchmarkTwo 1 1 no-tidy-B/op 37 | ` 38 | 39 | out := new(strings.Builder) 40 | w := NewWriter(out) 41 | r := NewReader(bytes.NewReader([]byte(input)), "test") 42 | for r.Scan() { 43 | if err := w.Write(r.Result()); err != nil { 44 | t.Fatal(err) 45 | } 46 | } 47 | 48 | if out.String() != input { 49 | t.Fatalf("want:\n%sgot:\n%s", input, out.String()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "encoding/gob" 9 | "encoding/json" 10 | "io/ioutil" 11 | "testing" 12 | ) 13 | 14 | // Example benchmark used in package documentation. 15 | func BenchmarkEncode(b *testing.B) { 16 | data := makeTree(4) 17 | 18 | b.Run("format=json", func(b *testing.B) { 19 | for i := 0; i < b.N; i++ { 20 | e := json.NewEncoder(ioutil.Discard) 21 | err := e.Encode(data) 22 | if err != nil { 23 | b.Fatal(err) 24 | } 25 | } 26 | }) 27 | 28 | b.Run("format=gob", func(b *testing.B) { 29 | for i := 0; i < b.N; i++ { 30 | e := gob.NewEncoder(ioutil.Discard) 31 | err := e.Encode(data) 32 | if err != nil { 33 | b.Fatal(err) 34 | } 35 | } 36 | }) 37 | } 38 | 39 | type tree struct { 40 | L, R *tree 41 | } 42 | 43 | func makeTree(depth int) *tree { 44 | if depth <= 0 { 45 | return nil 46 | } 47 | return &tree{makeTree(depth - 1), makeTree(depth - 1)} 48 | } 49 | -------------------------------------------------------------------------------- /storage/fs/gcs/gcs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package gcs implements the fs.FS interface using Google Cloud Storage. 6 | package gcs 7 | 8 | import ( 9 | "context" 10 | 11 | "cloud.google.com/go/storage" 12 | "golang.org/x/perf/storage/fs" 13 | ) 14 | 15 | // impl is an fs.FS backed by Google Cloud Storage. 16 | type impl struct { 17 | bucket *storage.BucketHandle 18 | } 19 | 20 | // NewFS constructs an FS that writes to the provided bucket. 21 | // On AppEngine, ctx must be a request-derived Context. 22 | func NewFS(ctx context.Context, bucketName string) (fs.FS, error) { 23 | client, err := storage.NewClient(ctx) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return &impl{client.Bucket(bucketName)}, nil 28 | } 29 | 30 | func (fs *impl) NewWriter(ctx context.Context, name string, metadata map[string]string) (fs.Writer, error) { 31 | w := fs.bucket.Object(name).NewWriter(ctx) 32 | // TODO(quentin): Do these need "x-goog-meta-" prefixes? 33 | w.Metadata = metadata 34 | return w, nil 35 | } 36 | -------------------------------------------------------------------------------- /storage/query/query.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package query provides tools for parsing a query. 6 | package query 7 | 8 | // SplitWords splits q into words using shell syntax (whitespace 9 | // can be escaped with double quotes or with a backslash). 10 | func SplitWords(q string) []string { 11 | var words []string 12 | word := make([]byte, len(q)) 13 | w := 0 14 | quoting := false 15 | for r := 0; r < len(q); r++ { 16 | switch c := q[r]; { 17 | case c == '"' && quoting: 18 | quoting = false 19 | case quoting: 20 | if c == '\\' { 21 | r++ 22 | } 23 | if r < len(q) { 24 | word[w] = q[r] 25 | w++ 26 | } 27 | case c == '"': 28 | quoting = true 29 | case c == ' ', c == '\t': 30 | if w > 0 { 31 | words = append(words, string(word[:w])) 32 | } 33 | w = 0 34 | case c == '\\': 35 | r++ 36 | fallthrough 37 | default: 38 | if r < len(q) { 39 | word[w] = q[r] 40 | w++ 41 | } 42 | } 43 | } 44 | if w > 0 { 45 | words = append(words, string(word[:w])) 46 | } 47 | return words 48 | } 49 | -------------------------------------------------------------------------------- /analysis/app/parse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package app 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestParseQueryString(t *testing.T) { 13 | tests := []struct { 14 | q string 15 | wantPrefix string 16 | wantParts []string 17 | }{ 18 | {"prefix | one vs two", "prefix", []string{"one", "two"}}, 19 | {"prefix one vs two", "", []string{"prefix one", "two"}}, 20 | {"anything else", "", []string{"anything else"}}, 21 | {`one vs "two vs three"`, "", []string{"one", `"two vs three"`}}, 22 | {"mixed\ttabs \"and\tspaces\"", "", []string{"mixed tabs \"and\tspaces\""}}, 23 | } 24 | for _, test := range tests { 25 | t.Run(test.q, func(t *testing.T) { 26 | havePrefix, haveParts := parseQueryString(test.q) 27 | if havePrefix != test.wantPrefix { 28 | t.Errorf("parseQueryString returned prefix %q, want %q", havePrefix, test.wantPrefix) 29 | } 30 | if !reflect.DeepEqual(haveParts, test.wantParts) { 31 | t.Errorf("parseQueryString returned parts %#v, want %#v", haveParts, test.wantParts) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /benchstat/sort.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchstat 6 | 7 | import ( 8 | "math" 9 | "sort" 10 | ) 11 | 12 | // An Order defines a sort order for a table. 13 | // It reports whether t.Rows[i] should appear before t.Rows[j]. 14 | type Order func(t *Table, i, j int) bool 15 | 16 | // ByName sorts tables by the Benchmark name column 17 | func ByName(t *Table, i, j int) bool { 18 | return t.Rows[i].Benchmark < t.Rows[j].Benchmark 19 | } 20 | 21 | // ByDelta sorts tables by the Delta column, 22 | // reversing the order when larger is better (for "speed" results). 23 | func ByDelta(t *Table, i, j int) bool { 24 | return math.Abs(t.Rows[i].PctDelta)*float64(t.Rows[i].Change) < 25 | math.Abs(t.Rows[j].PctDelta)*float64(t.Rows[j].Change) 26 | } 27 | 28 | // Reverse returns the reverse of the given order. 29 | func Reverse(order Order) Order { 30 | return func(t *Table, i, j int) bool { return order(t, j, i) } 31 | } 32 | 33 | // Sort sorts a Table t (in place) by the given order. 34 | func Sort(t *Table, order Order) { 35 | sort.SliceStable(t.Rows, func(i, j int) bool { return order(t, i, j) }) 36 | } 37 | -------------------------------------------------------------------------------- /storage/fs/local/local_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package local 6 | 7 | import ( 8 | "context" 9 | "os" 10 | "path/filepath" 11 | "testing" 12 | 13 | "golang.org/x/perf/internal/diff" 14 | ) 15 | 16 | func TestNewWriter(t *testing.T) { 17 | ctx := context.Background() 18 | 19 | dir, err := os.MkdirTemp("", "local_test") 20 | if err != nil { 21 | t.Fatalf("TempDir = %v", err) 22 | } 23 | defer os.RemoveAll(dir) 24 | 25 | fs := NewFS(dir) 26 | 27 | w, err := fs.NewWriter(ctx, "dir/file", map[string]string{"key": "value", "key2": "value2"}) 28 | if err != nil { 29 | t.Fatalf("NewWriter = %v", err) 30 | } 31 | 32 | want := "hello world" 33 | 34 | if _, err := w.Write([]byte(want)); err != nil { 35 | t.Fatalf("Write = %v", err) 36 | } 37 | 38 | if err := w.Close(); err != nil { 39 | t.Fatalf("Close = %v", err) 40 | } 41 | 42 | have, err := os.ReadFile(filepath.Join(dir, "dir/file")) 43 | if err != nil { 44 | t.Fatalf("ReadFile = %v", err) 45 | } 46 | if d := diff.Diff(string(have), want); d != "" { 47 | t.Errorf("file contents differ. have (-)/want (+)\n%s", d) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /storage/db/query_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package db 6 | 7 | import "testing" 8 | 9 | func TestParseWord(t *testing.T) { 10 | tests := []struct { 11 | word string 12 | want part 13 | wantErr bool 14 | }{ 15 | {"key:value", part{"key", equals, "value", ""}, false}, 16 | {"key>value", part{"key", gt, "value", ""}, false}, 17 | {"key= d.T { 35 | return 1 36 | } 37 | return 0 38 | } 39 | 40 | func (d DeltaDist) cdfEach(xs []float64) []float64 { 41 | res := make([]float64, len(xs)) 42 | for i, x := range xs { 43 | res[i] = d.CDF(x) 44 | } 45 | return res 46 | } 47 | 48 | func (d DeltaDist) InvCDF(y float64) float64 { 49 | if y < 0 || y > 1 { 50 | return nan 51 | } 52 | return d.T 53 | } 54 | 55 | func (d DeltaDist) Bounds() (float64, float64) { 56 | return d.T - 1, d.T + 1 57 | } 58 | -------------------------------------------------------------------------------- /analysis/app/index.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package app 6 | 7 | import ( 8 | "html/template" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | 13 | "golang.org/x/perf/storage" 14 | ) 15 | 16 | // index redirects / to /search. 17 | func (a *App) index(w http.ResponseWriter, r *http.Request) { 18 | ctx := requestContext(r) 19 | 20 | tmpl, err := os.ReadFile(filepath.Join(a.BaseDir, "template/index.html")) 21 | if err != nil { 22 | http.Error(w, err.Error(), 500) 23 | return 24 | } 25 | 26 | t, err := template.New("main").Parse(string(tmpl)) 27 | if err != nil { 28 | http.Error(w, err.Error(), 500) 29 | return 30 | } 31 | 32 | var uploads []storage.UploadInfo 33 | ul := a.StorageClient.ListUploads(ctx, "", []string{"by", "upload-time"}, 16) 34 | defer ul.Close() 35 | for ul.Next() { 36 | uploads = append(uploads, ul.Info()) 37 | } 38 | if err := ul.Err(); err != nil { 39 | errorf(ctx, "failed to fetch recent uploads: %v", err) 40 | } 41 | 42 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 43 | if err := t.Execute(w, struct{ RecentUploads []storage.UploadInfo }{uploads}); err != nil { 44 | http.Error(w, err.Error(), 500) 45 | return 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /benchmath/anormal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchmath 6 | 7 | import "github.com/aclements/go-moremath/stats" 8 | 9 | // AssumeNormal is an assumption that a sample is normally distributed. 10 | // The summary statistic is the sample mean and comparisons are done 11 | // using the two-sample t-test. 12 | var AssumeNormal = assumeNormal{} 13 | 14 | type assumeNormal struct{} 15 | 16 | var _ Assumption = assumeNormal{} 17 | 18 | func (assumeNormal) SummaryLabel() string { 19 | return "mean" 20 | } 21 | 22 | func (assumeNormal) Summary(s *Sample, confidence float64) Summary { 23 | // TODO: Perform a normality test. 24 | 25 | sample := s.sample() 26 | mean, lo, hi := sample.MeanCI(confidence) 27 | 28 | return Summary{ 29 | Center: mean, 30 | Lo: lo, 31 | Hi: hi, 32 | Confidence: confidence, 33 | } 34 | } 35 | 36 | func (assumeNormal) Compare(s1, s2 *Sample) Comparison { 37 | t, err := stats.TwoSampleWelchTTest(s1.sample(), s2.sample(), stats.LocationDiffers) 38 | if err != nil { 39 | // The t-test failed. Report as if there's no 40 | // significant difference, along with the error. 41 | return Comparison{P: 1, N1: len(s1.Values), N2: len(s2.Values), Warnings: []error{err}} 42 | } 43 | return Comparison{P: t.P, N1: len(s1.Values), N2: len(s2.Values)} 44 | } 45 | -------------------------------------------------------------------------------- /storage/fs/local/local.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package local implements the fs.FS interface using local files. 6 | // Metadata is not stored separately; the header of each file should 7 | // contain metadata as written by storage/app. 8 | package local 9 | 10 | import ( 11 | "context" 12 | "os" 13 | "path/filepath" 14 | 15 | "golang.org/x/perf/storage/fs" 16 | ) 17 | 18 | // impl is an fs.FS backed by local disk. 19 | type impl struct { 20 | root string 21 | } 22 | 23 | // NewFS constructs an FS that writes to the provided directory. 24 | func NewFS(root string) fs.FS { 25 | return &impl{root} 26 | } 27 | 28 | // NewWriter creates a file and assigns metadata as extended filesystem attributes. 29 | func (fs *impl) NewWriter(ctx context.Context, name string, metadata map[string]string) (fs.Writer, error) { 30 | if err := os.MkdirAll(filepath.Join(fs.root, filepath.Dir(name)), 0777); err != nil { 31 | return nil, err 32 | } 33 | f, err := os.Create(filepath.Join(fs.root, name)) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return &wrapper{f}, nil 38 | } 39 | 40 | type wrapper struct { 41 | *os.File 42 | } 43 | 44 | // CloseWithError closes the file and attempts to unlink it. 45 | func (w *wrapper) CloseWithError(error) error { 46 | w.Close() 47 | return os.Remove(w.Name()) 48 | } 49 | -------------------------------------------------------------------------------- /analysis/app/kza_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package app 6 | 7 | import ( 8 | "math/rand" 9 | "testing" 10 | ) 11 | 12 | // Aeq returns true if expect and got are equal to 8 significant 13 | // figures (1 part in 100 million). 14 | func Aeq(expect, got float64) bool { 15 | if expect < 0 && got < 0 { 16 | expect, got = -expect, -got 17 | } 18 | return expect*0.99999999 <= got && got*0.99999999 <= expect 19 | } 20 | 21 | func TestMovingAverage(t *testing.T) { 22 | // Test MovingAverage against the obvious (but slow) 23 | // implementation. 24 | xs := make([]float64, 100) 25 | for iter := 0; iter < 10; iter++ { 26 | for i := range xs { 27 | xs[i] = rand.Float64() 28 | } 29 | m := 1 + 2*rand.Intn(100) 30 | ys1, ys2 := MovingAverage(xs, m), slowMovingAverage(xs, m) 31 | 32 | // TODO: Use stuff from mathtest. 33 | for i, y1 := range ys1 { 34 | if !Aeq(y1, ys2[i]) { 35 | t.Fatalf("want %v, got %v", ys2, ys1) 36 | } 37 | } 38 | } 39 | } 40 | 41 | func slowMovingAverage(xs []float64, m int) []float64 { 42 | ys := make([]float64, len(xs)) 43 | for i := range ys { 44 | psum, n := 0.0, 0 45 | for j := i - (m-1)/2; j <= i+(m-1)/2; j++ { 46 | if 0 <= j && j < len(xs) { 47 | psum += xs[j] 48 | n++ 49 | } 50 | } 51 | ys[i] = psum / float64(n) 52 | } 53 | return ys 54 | } 55 | -------------------------------------------------------------------------------- /benchproc/nonsingular.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchproc 6 | 7 | // NonSingularFields returns the subset of Fields for which at least two of keys 8 | // have different values. 9 | // 10 | // This is useful for warning the user if aggregating a set of results has 11 | // resulted in potentially hiding important configuration differences. Typically 12 | // these keys are "residue" keys produced by ProjectionParser.Residue. 13 | func NonSingularFields(keys []Key) []*Field { 14 | // TODO: This is generally used on residue keys, but those might 15 | // just have ".fullname" (generally with implicit exclusions). 16 | // Telling the user that a set of benchmarks varies in ".fullname" 17 | // isn't nearly as useful as listing out the specific subfields. We 18 | // should synthesize "/N" subfields for ".fullname" for this (this 19 | // makes even more sense if we make .fullname a group field that 20 | // already has these subfields). 21 | 22 | if len(keys) <= 1 { 23 | // There can't be any differences. 24 | return nil 25 | } 26 | var out []*Field 27 | fields := commonProjection(keys).FlattenedFields() 28 | for _, f := range fields { 29 | base := keys[0].Get(f) 30 | for _, k := range keys[1:] { 31 | if k.Get(f) != base { 32 | out = append(out, f) 33 | break 34 | } 35 | } 36 | } 37 | return out 38 | } 39 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/old.txt: -------------------------------------------------------------------------------- 1 | goos: linux 2 | goarch: amd64 3 | pkg: golang.org/x/perf/cmd/benchstat/testdata 4 | BenchmarkEncode/format=json-48 690848 1726 ns/op 5 | BenchmarkEncode/format=json-48 684861 1723 ns/op 6 | BenchmarkEncode/format=json-48 693285 1707 ns/op 7 | BenchmarkEncode/format=json-48 677692 1707 ns/op 8 | BenchmarkEncode/format=json-48 692130 1713 ns/op 9 | BenchmarkEncode/format=json-48 684164 1729 ns/op 10 | BenchmarkEncode/format=json-48 682500 1736 ns/op 11 | BenchmarkEncode/format=json-48 677509 1707 ns/op 12 | BenchmarkEncode/format=json-48 687295 1705 ns/op 13 | BenchmarkEncode/format=json-48 695533 1774 ns/op 14 | BenchmarkEncode/format=gob-48 372699 3069 ns/op 15 | BenchmarkEncode/format=gob-48 394740 3075 ns/op 16 | BenchmarkEncode/format=gob-48 391335 3069 ns/op 17 | BenchmarkEncode/format=gob-48 383588 3067 ns/op 18 | BenchmarkEncode/format=gob-48 385885 3207 ns/op 19 | BenchmarkEncode/format=gob-48 389970 3064 ns/op 20 | BenchmarkEncode/format=gob-48 393361 3064 ns/op 21 | BenchmarkEncode/format=gob-48 393882 3058 ns/op 22 | BenchmarkEncode/format=gob-48 396171 3059 ns/op 23 | BenchmarkEncode/format=gob-48 397812 3062 ns/op 24 | PASS 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /analysis/localperf/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Localperf runs an HTTP server for benchmark analysis. 6 | // 7 | // Usage: 8 | // 9 | // localperf [-addr address] [-storage url] [-base_dir ../appengine] 10 | package main 11 | 12 | import ( 13 | "flag" 14 | "fmt" 15 | "log" 16 | "net/http" 17 | "os" 18 | 19 | "golang.org/x/perf/analysis/app" 20 | "golang.org/x/perf/internal/basedir" 21 | "golang.org/x/perf/storage" 22 | ) 23 | 24 | var ( 25 | addr = flag.String("addr", "localhost:8080", "serve HTTP on `address`") 26 | storageURL = flag.String("storage", "https://perfdata.golang.org", "storage server base `url`") 27 | baseDir = flag.String("base_dir", basedir.Find("golang.org/x/perf/analysis/appengine"), "base `directory` for templates") 28 | ) 29 | 30 | func usage() { 31 | fmt.Fprintf(os.Stderr, `Usage of localperf: 32 | localperf [flags] 33 | `) 34 | flag.PrintDefaults() 35 | os.Exit(2) 36 | } 37 | 38 | func main() { 39 | log.SetPrefix("localperf: ") 40 | flag.Usage = usage 41 | flag.Parse() 42 | if flag.NArg() != 0 { 43 | flag.Usage() 44 | } 45 | 46 | if *baseDir == "" { 47 | log.Print("base_dir is required and could not be automatically found") 48 | flag.Usage() 49 | } 50 | 51 | app := &app.App{ 52 | StorageClient: &storage.Client{BaseURL: *storageURL}, 53 | BaseDir: *baseDir, 54 | } 55 | app.RegisterOnMux(http.DefaultServeMux) 56 | 57 | log.Printf("Listening on %s", *addr) 58 | 59 | log.Fatal(http.ListenAndServe(*addr, nil)) 60 | } 61 | -------------------------------------------------------------------------------- /benchproc/internal/parse/projection_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package parse 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestParseProjection(t *testing.T) { 13 | check := func(proj string, want ...string) { 14 | t.Helper() 15 | p, err := ParseProjection(proj) 16 | if err != nil { 17 | t.Errorf("%s: unexpected error %s", proj, err) 18 | return 19 | } 20 | var got []string 21 | for _, part := range p { 22 | got = append(got, part.String()) 23 | } 24 | if !reflect.DeepEqual(got, want) { 25 | t.Errorf("%s: got %v, want %v", proj, got, want) 26 | } 27 | } 28 | checkErr := func(proj, error string, pos int) { 29 | t.Helper() 30 | _, err := ParseProjection(proj) 31 | if se, _ := err.(*SyntaxError); se == nil || se.Msg != error || se.Off != pos { 32 | t.Errorf("%s: want error %s at %d; got %s", proj, error, pos, err) 33 | } 34 | } 35 | 36 | check("") 37 | check("a,b", "a", "b") 38 | check("a, b", "a", "b") 39 | check("a b", "a", "b") 40 | checkErr("a,,b", "expected key", 2) 41 | check("a,.name", "a", ".name") 42 | check("a,/b", "a", "/b") 43 | 44 | check("a@alpha, b@num", "a@alpha", "b@num") 45 | checkErr("a@", "expected named sort order or parenthesized list", 2) 46 | checkErr("a@,b", "expected named sort order or parenthesized list", 2) 47 | 48 | check("a@(1 2), b@(3 4)", "a@(1 2)", "b@(3 4)") 49 | checkErr("a@(", "missing )", 3) 50 | checkErr("a@(,", "missing )", 3) 51 | checkErr("a@()", "nothing to match", 3) 52 | } 53 | -------------------------------------------------------------------------------- /storage/db/dbtest/cloud.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build cloud && !plan9 6 | 7 | package dbtest 8 | 9 | import ( 10 | "crypto/rand" 11 | "database/sql" 12 | "encoding/base64" 13 | "flag" 14 | "fmt" 15 | "testing" 16 | 17 | _ "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/mysql" 18 | ) 19 | 20 | var cloud = flag.Bool("cloud", false, "connect to Cloud SQL database instead of in-memory SQLite") 21 | var cloudsql = flag.String("cloudsql", "golang-org:us-central1:golang-org", "name of Cloud SQL instance to run tests on") 22 | 23 | // createEmptyDB makes a new, empty database for the test. 24 | func createEmptyDB(t *testing.T) (driver, dsn string, cleanup func()) { 25 | if !*cloud { 26 | return "sqlite3", ":memory:", nil 27 | } 28 | buf := make([]byte, 6) 29 | if _, err := rand.Read(buf); err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | name := "perfdata-test-" + base64.RawURLEncoding.EncodeToString(buf) 34 | 35 | prefix := fmt.Sprintf("root:@cloudsql(%s)/", *cloudsql) 36 | 37 | db, err := sql.Open("mysql", prefix) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | if _, err := db.Exec(fmt.Sprintf("CREATE DATABASE `%s`", name)); err != nil { 43 | db.Close() 44 | t.Fatal(err) 45 | } 46 | 47 | t.Logf("Using database %q", name) 48 | 49 | return "mysql", prefix + name + "?interpolateParams=true", func() { 50 | if _, err := db.Exec(fmt.Sprintf("DROP DATABASE `%s`", name)); err != nil { 51 | t.Error(err) 52 | } 53 | db.Close() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/zero.stdout: -------------------------------------------------------------------------------- 1 | │ before │ after │ 2 | │ a-bytes │ a-bytes vs base │ 3 | Imperceptible 1.150Gi ± 0% 1.150Gi ± 0% 0.00% (n=1) 4 | Imperceptible2 1.150Gi ± 0% 1.150Gi ± 0% 0.00% (n=1) 5 | geomean 1.150Gi 1.150Gi +0.00% 6 | 7 | │ before │ after │ 8 | │ b-bytes │ b-bytes vs base │ 9 | Imperceptible 159.9Gi ± 0% 159.9Gi ± 0% +0.00% (n=1) 10 | Imperceptible2 159.9Gi ± 0% 159.9Gi ± 0% +0.00% (n=1) 11 | geomean 159.9Gi 159.9Gi +0.00% 12 | 13 | │ before │ after │ 14 | │ c-bytes │ c-bytes vs base │ 15 | Imperceptible 95.37Mi ± 0% 95.37Mi ± 0% -0.00% (n=1) 16 | Imperceptible2 95.37Mi ± 0% 95.37Mi ± 0% -0.00% (n=1) 17 | geomean 95.37Mi 95.37Mi -0.00% 18 | 19 | │ before │ after │ 20 | │ x │ x vs base │ 21 | ZeroOverZero 0.000 ± 0% 0.000 ± 0% 0.00% (n=1) 22 | ZeroOverZero2 0.000 ± 0% 0.000 ± 0% 0.00% (n=1) 23 | geomean ¹ +0.00% ¹ 24 | ¹ summaries must be >0 to compute geomean 25 | 26 | │ before │ after │ 27 | │ y │ y vs base │ 28 | NonZeroOverZero 0.0 ± 0% 100.0 ± 0% ? (n=1) 29 | NonZeroOverZero2 0.0 ± 0% 100.0 ± 0% ? (n=1) 30 | geomean ¹ 100.0 ? 31 | ¹ summaries must be >0 to compute geomean 32 | -------------------------------------------------------------------------------- /analysis/app/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package app 6 | 7 | import "strings" 8 | 9 | // parseQueryString splits a user-entered query into one or more storage server queries. 10 | // The supported query formats are: 11 | // 12 | // prefix | one vs two - parsed as "prefix", {"one", "two"} 13 | // prefix one vs two - parsed as "", {"prefix one", "two"} 14 | // anything else - parsed as "", {"anything else"} 15 | // 16 | // The vs and | separators must not be quoted. 17 | func parseQueryString(q string) (string, []string) { 18 | var queries []string 19 | var parts []string 20 | var prefix string 21 | quoting := false 22 | for r := 0; r < len(q); { 23 | switch c := q[r]; { 24 | case c == '"' && quoting: 25 | quoting = false 26 | r++ 27 | case quoting: 28 | if c == '\\' { 29 | r++ 30 | } 31 | r++ 32 | case c == '"': 33 | quoting = true 34 | r++ 35 | case c == ' ', c == '\t': 36 | switch part := q[:r]; { 37 | case part == "|" && prefix == "": 38 | prefix = strings.Join(parts, " ") 39 | parts = nil 40 | case part == "vs": 41 | queries = append(queries, strings.Join(parts, " ")) 42 | parts = nil 43 | default: 44 | parts = append(parts, part) 45 | } 46 | q = q[r+1:] 47 | r = 0 48 | default: 49 | if c == '\\' { 50 | r++ 51 | } 52 | r++ 53 | } 54 | } 55 | if len(q) > 0 { 56 | parts = append(parts, q) 57 | } 58 | if len(parts) > 0 { 59 | queries = append(queries, strings.Join(parts, " ")) 60 | } 61 | return prefix, queries 62 | } 63 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/new.txt: -------------------------------------------------------------------------------- 1 | new.txt is with Go 1.17. old.txt format=json is with Go 1.16 and 2 | format=gob is with 1.17 (since this is just example data, I wanted to 3 | capture both a significant change and a non-significant change). 4 | 5 | goos: linux 6 | goarch: amd64 7 | pkg: golang.org/x/perf/cmd/benchstat/testdata 8 | BenchmarkEncode/format=json-48 714387 1423 ns/op 9 | BenchmarkEncode/format=json-48 845445 1416 ns/op 10 | BenchmarkEncode/format=json-48 815714 1411 ns/op 11 | BenchmarkEncode/format=json-48 828824 1413 ns/op 12 | BenchmarkEncode/format=json-48 834070 1412 ns/op 13 | BenchmarkEncode/format=json-48 828123 1426 ns/op 14 | BenchmarkEncode/format=json-48 834493 1422 ns/op 15 | BenchmarkEncode/format=json-48 838406 1424 ns/op 16 | BenchmarkEncode/format=json-48 836227 1447 ns/op 17 | BenchmarkEncode/format=json-48 830835 1425 ns/op 18 | BenchmarkEncode/format=gob-48 394441 3075 ns/op 19 | BenchmarkEncode/format=gob-48 393207 3065 ns/op 20 | BenchmarkEncode/format=gob-48 392374 3059 ns/op 21 | BenchmarkEncode/format=gob-48 396037 3065 ns/op 22 | BenchmarkEncode/format=gob-48 393255 3060 ns/op 23 | BenchmarkEncode/format=gob-48 382629 3081 ns/op 24 | BenchmarkEncode/format=gob-48 389558 3186 ns/op 25 | BenchmarkEncode/format=gob-48 392668 3135 ns/op 26 | BenchmarkEncode/format=gob-48 392313 3087 ns/op 27 | BenchmarkEncode/format=gob-48 394274 3062 ns/op 28 | PASS 29 | -------------------------------------------------------------------------------- /internal/basedir/basedir.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package basedir finds templates and static files associated with a binary. 6 | package basedir 7 | 8 | import ( 9 | "bytes" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "runtime" 14 | "strings" 15 | ) 16 | 17 | // Find locates a directory for the given package. 18 | // pkg should be the directory that contains the templates and/or static directories. 19 | // If pkg cannot be found, an empty string will be returned. 20 | func Find(pkg string) string { 21 | cmd := exec.Command("go", "list", "-e", "-f", "{{.Dir}}", pkg) 22 | if out, err := cmd.Output(); err == nil && len(out) > 0 { 23 | return string(bytes.TrimRight(out, "\r\n")) 24 | } 25 | gopath := os.Getenv("GOPATH") 26 | if gopath == "" { 27 | gopath = defaultGOPATH() 28 | } 29 | if gopath != "" { 30 | for _, dir := range strings.Split(gopath, ":") { 31 | p := filepath.Join(dir, pkg) 32 | if _, err := os.Stat(p); err == nil { 33 | return p 34 | } 35 | } 36 | } 37 | return "" 38 | } 39 | 40 | // Copied from go/build/build.go 41 | func defaultGOPATH() string { 42 | env := "HOME" 43 | if runtime.GOOS == "windows" { 44 | env = "USERPROFILE" 45 | } else if runtime.GOOS == "plan9" { 46 | env = "home" 47 | } 48 | if home := os.Getenv(env); home != "" { 49 | def := filepath.Join(home, "go") 50 | if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) { 51 | // Don't set the default GOPATH to GOROOT, 52 | // as that will trigger warnings from the go tool. 53 | return "" 54 | } 55 | return def 56 | } 57 | return "" 58 | } 59 | -------------------------------------------------------------------------------- /analysis/appengine/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This binary contains an App Engine app for perf.golang.org 6 | package main 7 | 8 | import ( 9 | "context" 10 | "log" 11 | "net/http" 12 | "os" 13 | "time" 14 | 15 | "golang.org/x/perf/analysis/app" 16 | "golang.org/x/perf/storage" 17 | "google.golang.org/appengine" 18 | ) 19 | 20 | func mustGetenv(k string) string { 21 | v := os.Getenv(k) 22 | if v == "" { 23 | log.Panicf("%s environment variable not set.", k) 24 | } 25 | return v 26 | } 27 | 28 | // appHandler is the default handler, registered to serve "/". 29 | // It creates a new App instance using the appengine Context and then 30 | // dispatches the request to the App. The environment variable 31 | // STORAGE_URL_BASE must be set in app.yaml with the name of the bucket to 32 | // write to. 33 | func appHandler(w http.ResponseWriter, r *http.Request) { 34 | ctx := appengine.NewContext(r) 35 | // urlfetch defaults to 5s timeout if the context has no timeout. 36 | // The underlying request has a 60 second timeout, so we might as well propagate that here. 37 | // (Why doesn't appengine do that for us?) 38 | ctx, cancel := context.WithTimeout(ctx, 60*time.Second) 39 | defer cancel() 40 | app := &app.App{ 41 | BaseDir: "analysis/appengine", // relative to module root 42 | StorageClient: &storage.Client{ 43 | BaseURL: mustGetenv("STORAGE_URL_BASE"), 44 | HTTPClient: http.DefaultClient, 45 | }, 46 | } 47 | mux := http.NewServeMux() 48 | app.RegisterOnMux(mux) 49 | mux.ServeHTTP(w, r) 50 | } 51 | 52 | func main() { 53 | http.HandleFunc("/", appHandler) 54 | appengine.Main() 55 | } 56 | -------------------------------------------------------------------------------- /benchmath/aexact.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchmath 6 | 7 | import "fmt" 8 | 9 | // AssumeExact is an assumption that a value can be measured exactly 10 | // and thus has no distribution and does not require repeated sampling. 11 | // It reports a warning if not all values in a sample are equal. 12 | var AssumeExact = assumeExact{} 13 | 14 | type assumeExact struct{} 15 | 16 | var _ Assumption = assumeExact{} 17 | 18 | func (assumeExact) SummaryLabel() string { 19 | // Really the summary is the mode, but the point of this 20 | // assumption is that the summary is the exact value. 21 | return "exact" 22 | } 23 | 24 | func (assumeExact) Summary(s *Sample, confidence float64) Summary { 25 | // Find the sample's mode. This checks if all samples are the 26 | // same, and lets us return a reasonable summary even if they 27 | // aren't all the same. 28 | val, count := s.Values[0], 1 29 | modeVal, modeCount := val, count 30 | for _, v := range s.Values[1:] { 31 | if v == val { 32 | count++ 33 | if count > modeCount { 34 | modeVal, modeCount = val, count 35 | } 36 | } else { 37 | val, count = v, 1 38 | } 39 | } 40 | summary := Summary{Center: modeVal, Lo: s.Values[0], Hi: s.Values[len(s.Values)-1], Confidence: 1} 41 | 42 | if modeCount != len(s.Values) { 43 | // They're not all the same. Report a warning. 44 | summary.Warnings = []error{fmt.Errorf("exact distribution expected, but values range from %v to %v", s.Values[0], s.Values[len(s.Values)-1])} 45 | } 46 | return summary 47 | } 48 | 49 | func (assumeExact) Compare(s1, s2 *Sample) Comparison { 50 | return Comparison{P: 0, N1: len(s1.Values), N2: len(s2.Values)} 51 | } 52 | -------------------------------------------------------------------------------- /analysis/app/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package app implements the performance data analysis server. 6 | package app 7 | 8 | import ( 9 | "net/http" 10 | 11 | "golang.org/x/perf/storage" 12 | ) 13 | 14 | // App manages the analysis server logic. 15 | // Construct an App instance and call RegisterOnMux to connect it with an HTTP server. 16 | type App struct { 17 | // StorageClient is used to talk to the storage server. 18 | StorageClient *storage.Client 19 | 20 | // BaseDir is the directory containing the "template" directory. 21 | // If empty, the current directory will be used. 22 | BaseDir string 23 | } 24 | 25 | // RegisterOnMux registers the app's URLs on mux. 26 | func (a *App) RegisterOnMux(mux *http.ServeMux) { 27 | mux.HandleFunc("/", a.index) 28 | mux.HandleFunc("/search", a.search) 29 | mux.HandleFunc("/compare", a.compare) 30 | mux.HandleFunc("/trend", a.trend) 31 | } 32 | 33 | // search handles /search. 34 | // This currently just runs the compare handler, until more analysis methods are implemented. 35 | func (a *App) search(w http.ResponseWriter, r *http.Request) { 36 | if err := r.ParseForm(); err != nil { 37 | http.Error(w, err.Error(), 500) 38 | return 39 | } 40 | if r.Header.Get("Accept") == "text/plain" || r.Header.Get("X-Benchsave") == "1" { 41 | // TODO(quentin): Switch to real Accept negotiation when golang/go#19307 is resolved. 42 | // Benchsave sends both of these headers. 43 | a.textCompare(w, r) 44 | return 45 | } 46 | // TODO(quentin): Intelligently choose an analysis method 47 | // based on the results from the query, once there is more 48 | // than one analysis method. 49 | //q := r.Form.Get("q") 50 | a.compare(w, r) 51 | } 52 | -------------------------------------------------------------------------------- /internal/diff/diff.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package diff 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "os/exec" 11 | "runtime" 12 | ) 13 | 14 | func writeTempFile(dir, prefix string, data []byte) (string, error) { 15 | file, err := os.CreateTemp(dir, prefix) 16 | if err != nil { 17 | return "", err 18 | } 19 | _, err = file.Write(data) 20 | if err1 := file.Close(); err == nil { 21 | err = err1 22 | } 23 | if err != nil { 24 | os.Remove(file.Name()) 25 | return "", err 26 | } 27 | return file.Name(), nil 28 | } 29 | 30 | // Diff returns a human-readable description of the differences between s1 and s2. 31 | // If the "diff" command is available, it returns the output of unified diff on s1 and s2. 32 | // If the result is non-empty, the strings differ or the diff command failed. 33 | func Diff(s1, s2 string) string { 34 | if s1 == s2 { 35 | return "" 36 | } 37 | if _, err := exec.LookPath("diff"); err != nil { 38 | return fmt.Sprintf("diff command unavailable\nold: %q\nnew: %q", s1, s2) 39 | } 40 | f1, err := writeTempFile("", "benchfmt_test", []byte(s1)) 41 | if err != nil { 42 | return err.Error() 43 | } 44 | defer os.Remove(f1) 45 | 46 | f2, err := writeTempFile("", "benchfmt_test", []byte(s2)) 47 | if err != nil { 48 | return err.Error() 49 | } 50 | defer os.Remove(f2) 51 | 52 | cmd := "diff" 53 | if runtime.GOOS == "plan9" { 54 | cmd = "/bin/ape/diff" 55 | } 56 | 57 | data, err := exec.Command(cmd, "-u", f1, f2).CombinedOutput() 58 | if len(data) > 0 { 59 | // diff exits with a non-zero status when the files don't match. 60 | // Ignore that failure as long as we get output. 61 | err = nil 62 | } 63 | if err != nil { 64 | data = append(data, []byte(err.Error())...) 65 | } 66 | return string(data) 67 | } 68 | -------------------------------------------------------------------------------- /analysis/appengine/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Go Performance Result Dashboard 6 | 21 | 22 | 23 | 27 | 33 |
34 |

The Go Performance Dashboard provides a centralized 35 | resource for sharing and analyzing benchmark results. To get 36 | started, upload benchmark results using 37 | go get -u golang.org/x/perf/cmd/benchsave 38 | and 39 | benchsave old.txt new.txt 40 | or upload via the web at 41 | https://perfdata-dot-golang-org.appspot.com/upload.

42 | {{with .RecentUploads}} 43 |

Recent Uploads

44 | 45 | 46 | 47 | 48 | 49 | {{range .}} 50 | 51 | 52 | 53 | 54 | 55 | {{end}} 56 | 57 |
RecordsUploadBy
{{.Count}}{{index .LabelValues "upload-time"}}{{.LabelValues.by}}
58 | {{end}} 59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /benchmath/sample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchmath 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | ) 11 | 12 | func TestSummaryFormat(t *testing.T) { 13 | check := func(center, lo, hi float64, want string) { 14 | t.Helper() 15 | s := Summary{Center: center, Lo: lo, Hi: hi} 16 | got := s.PctRangeString() 17 | if got != want { 18 | t.Errorf("for %v CI [%v, %v], got %s, want %s", center, lo, hi, got, want) 19 | } 20 | } 21 | inf := math.Inf(1) 22 | 23 | check(1, 0.5, 1.1, "50%") 24 | check(1, 0.9, 1.5, "50%") 25 | check(1, 1, 1, "0%") 26 | 27 | check(-1, -0.5, -1.1, "50%") 28 | check(-1, -0.9, -1.5, "50%") 29 | check(-1, -1, -1, "0%") 30 | 31 | check(1, -inf, 1, "∞") 32 | check(1, 1, inf, "∞") 33 | 34 | check(1, -1, 1, "?") 35 | check(1, -1, -1, "?") 36 | check(-1, -1, 1, "?") 37 | check(-1, 1, -1, "?") 38 | check(0, -1, 1, "?") 39 | 40 | check(0, 0, 0, "0%") 41 | } 42 | 43 | func TestComparisonFormat(t *testing.T) { 44 | check := func(p float64, n1, n2 int, want string) { 45 | t.Helper() 46 | got := Comparison{P: p, N1: n1, N2: n2}.String() 47 | if got != want { 48 | t.Errorf("for %v,%v,%v, got %s, want %s", p, n1, n2, got, want) 49 | } 50 | } 51 | check(0.5, 1, 2, "p=0.500 n=1+2") 52 | check(0.5, 2, 2, "p=0.500 n=2") 53 | check(0, 1, 2, "n=1+2") 54 | check(0, 2, 2, "n=2") 55 | 56 | checkD := func(p, old, new, alpha float64, want string) { 57 | got := Comparison{P: p, Alpha: alpha}.FormatDelta(old, new) 58 | if got != want { 59 | t.Errorf("for p=%v %v=>%v @%v, got %s, want %s", p, old, new, alpha, got, want) 60 | } 61 | } 62 | checkD(0.5, 0, 0, 0.05, "~") 63 | checkD(0.01, 0, 0, 0.05, "0.00%") 64 | checkD(0.01, 1, 1, 0.05, "0.00%") 65 | checkD(0.01, 0, 1, 0.05, "?") 66 | checkD(0.01, 1, 1.5, 0.05, "+50.00%") 67 | checkD(0.01, 1, 0.5, 0.05, "-50.00%") 68 | } 69 | -------------------------------------------------------------------------------- /internal/stats/mathx.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import "math" 8 | 9 | // mathSign returns the sign of x: -1 if x < 0, 0 if x == 0, 1 if x > 0. 10 | // If x is NaN, it returns NaN. 11 | func mathSign(x float64) float64 { 12 | if x == 0 { 13 | return 0 14 | } else if x < 0 { 15 | return -1 16 | } else if x > 0 { 17 | return 1 18 | } 19 | return nan 20 | } 21 | 22 | const smallFactLimit = 20 // 20! => 62 bits 23 | var smallFact [smallFactLimit + 1]int64 24 | 25 | func init() { 26 | smallFact[0] = 1 27 | fact := int64(1) 28 | for n := int64(1); n <= smallFactLimit; n++ { 29 | fact *= n 30 | smallFact[n] = fact 31 | } 32 | } 33 | 34 | // mathChoose returns the binomial coefficient of n and k. 35 | func mathChoose(n, k int) float64 { 36 | if k == 0 || k == n { 37 | return 1 38 | } 39 | if k < 0 || n < k { 40 | return 0 41 | } 42 | if n <= smallFactLimit { // Implies k <= smallFactLimit 43 | // It's faster to do several integer multiplications 44 | // than it is to do an extra integer division. 45 | // Remarkably, this is also faster than pre-computing 46 | // Pascal's triangle (presumably because this is very 47 | // cache efficient). 48 | numer := int64(1) 49 | for n1 := int64(n - (k - 1)); n1 <= int64(n); n1++ { 50 | numer *= n1 51 | } 52 | denom := smallFact[k] 53 | return float64(numer / denom) 54 | } 55 | 56 | return math.Exp(lchoose(n, k)) 57 | } 58 | 59 | // mathLchoose returns math.Log(mathChoose(n, k)). 60 | func mathLchoose(n, k int) float64 { 61 | if k == 0 || k == n { 62 | return 0 63 | } 64 | if k < 0 || n < k { 65 | return math.NaN() 66 | } 67 | return lchoose(n, k) 68 | } 69 | 70 | func lchoose(n, k int) float64 { 71 | a, _ := math.Lgamma(float64(n + 1)) 72 | b, _ := math.Lgamma(float64(k + 1)) 73 | c, _ := math.Lgamma(float64(n - k + 1)) 74 | return a - b - c 75 | } 76 | -------------------------------------------------------------------------------- /storage/localperfdata/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build cgo 6 | 7 | // Localperfdata runs an HTTP server for benchmark storage. 8 | // 9 | // Usage: 10 | // 11 | // localperfdata [-addr address] [-view_url_base url] [-base_dir ../appengine] [-dsn file.db] 12 | package main 13 | 14 | import ( 15 | "flag" 16 | "log" 17 | "net/http" 18 | 19 | "golang.org/x/perf/internal/basedir" 20 | "golang.org/x/perf/storage/app" 21 | "golang.org/x/perf/storage/db" 22 | _ "golang.org/x/perf/storage/db/sqlite3" 23 | "golang.org/x/perf/storage/fs" 24 | "golang.org/x/perf/storage/fs/local" 25 | ) 26 | 27 | var ( 28 | addr = flag.String("addr", ":8080", "serve HTTP on `address`") 29 | viewURLBase = flag.String("view_url_base", "", "/upload response with `URL` for viewing") 30 | dsn = flag.String("dsn", ":memory:", "sqlite `dsn`") 31 | data = flag.String("data", "", "data `directory` (in-memory if empty)") 32 | baseDir = flag.String("base_dir", basedir.Find("golang.org/x/perf/storage/appengine"), "base `directory` for static files") 33 | ) 34 | 35 | func main() { 36 | flag.Parse() 37 | 38 | if *baseDir == "" { 39 | log.Print("base_dir is required and could not be automatically found") 40 | flag.Usage() 41 | } 42 | 43 | db, err := db.OpenSQL("sqlite3", *dsn) 44 | if err != nil { 45 | log.Fatalf("open database: %v", err) 46 | } 47 | var fs fs.FS = fs.NewMemFS() 48 | 49 | if *data != "" { 50 | fs = local.NewFS(*data) 51 | } 52 | 53 | app := &app.App{ 54 | DB: db, 55 | FS: fs, 56 | ViewURLBase: *viewURLBase, 57 | Auth: func(http.ResponseWriter, *http.Request) (string, error) { return "", nil }, 58 | BaseDir: *baseDir, 59 | } 60 | app.RegisterOnMux(http.DefaultServeMux) 61 | 62 | log.Printf("Listening on %s", *addr) 63 | 64 | log.Fatal(http.ListenAndServe(*addr, nil)) 65 | } 66 | -------------------------------------------------------------------------------- /cmd/benchstat/testdata/crcSizeVsPoly.stdout: -------------------------------------------------------------------------------- 1 | pkg: hash/crc32 2 | goarch: amd64 3 | goos: darwin 4 | note: hw acceleration enabled 5 | │ IEEE │ Castagnoli │ Koopman │ 6 | │ sec/op │ sec/op vs base │ sec/op vs base │ 7 | 15 44.40n ± 2% 16.30n ± 2% -63.29% (p=0.000 n=10) 35.60n ± 1% -19.82% (p=0.000 n=10) 8 | 40 42.45n ± 3% 17.45n ± 3% -58.89% (p=0.000 n=10) 87.55n ± 2% +106.24% (p=0.000 n=10) 9 | 512 56.75n ± 3% 39.85n ± 2% -29.78% (p=0.000 n=10) 1073.00n ± 3% +1790.75% (p=0.000 n=10) 10 | 1kB 94.90n ± 5% 66.30n ± 3% -30.14% (p=0.000 n=10) 2346.50n ± 4% +2372.60% (p=0.000 n=10) 11 | 4kB 298.0n ± 1% 157.0n ± 4% -47.32% (p=0.000 n=10) 8964.0n ± 4% +2908.05% (p=0.000 n=10) 12 | 32kB 2.145µ ± 4% 1.218µ ± 2% -43.23% (p=0.000 n=10) 73.206µ ± 4% +3313.64% (p=0.000 n=10) 13 | geomean 136.6n 72.37n -47.01% 1.314µ +862.25% 14 | 15 | │ IEEE │ Castagnoli │ Koopman │ 16 | │ B/s │ B/s vs base │ B/s vs base │ 17 | 15 322.1Mi ± 2% 876.8Mi ± 2% +172.18% (p=0.000 n=10) 402.1Mi ± 1% +24.82% (p=0.000 n=10) 18 | 40 898.1Mi ± 3% 2186.1Mi ± 2% +143.41% (p=0.000 n=10) 435.9Mi ± 2% -51.47% (p=0.000 n=10) 19 | 512 8602.5Mi ± 3% 12245.1Mi ± 2% +42.34% (p=0.000 n=10) 454.7Mi ± 2% -94.71% (p=0.000 n=10) 20 | 1kB 10289.1Mi ± 6% 14730.6Mi ± 3% +43.17% (p=0.000 n=10) 416.1Mi ± 4% -95.96% (p=0.000 n=10) 21 | 4kB 13089.6Mi ± 1% 24772.0Mi ± 4% +89.25% (p=0.000 n=10) 435.9Mi ± 4% -96.67% (p=0.000 n=10) 22 | 32kB 14567.5Mi ± 4% 25658.0Mi ± 2% +76.13% (p=0.000 n=10) 426.9Mi ± 4% -97.07% (p=0.000 n=10) 23 | geomean 4.022Gi 7.586Gi +88.60% 428.3Mi -89.60% 24 | -------------------------------------------------------------------------------- /storage/app/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package app implements the performance data storage server. Combine 6 | // an App with a database and filesystem to get an HTTP server. 7 | package app 8 | 9 | import ( 10 | "errors" 11 | "net/http" 12 | "path/filepath" 13 | 14 | "golang.org/x/perf/storage/db" 15 | "golang.org/x/perf/storage/fs" 16 | ) 17 | 18 | // App manages the storage server logic. Construct an App instance 19 | // using a literal with DB and FS objects and call RegisterOnMux to 20 | // connect it with an HTTP server. 21 | type App struct { 22 | DB *db.DB 23 | FS fs.FS 24 | 25 | // Auth obtains the username for the request. 26 | // If necessary, it can write its own response (e.g. a 27 | // redirect) and return ErrResponseWritten. 28 | Auth func(http.ResponseWriter, *http.Request) (string, error) 29 | 30 | // ViewURLBase will be used to construct a URL to return as 31 | // "viewurl" in the response from /upload. If it is non-empty, 32 | // the upload ID will be appended to ViewURLBase. 33 | ViewURLBase string 34 | 35 | // BaseDir is the directory containing the "template" directory. 36 | // If empty, the current directory will be used. 37 | BaseDir string 38 | } 39 | 40 | // ErrResponseWritten can be returned by App.Auth to abort the normal /upload handling. 41 | var ErrResponseWritten = errors.New("response written") 42 | 43 | // RegisterOnMux registers the app's URLs on mux. 44 | func (a *App) RegisterOnMux(mux *http.ServeMux) { 45 | // TODO(quentin): Should we just make the App itself be an http.Handler? 46 | mux.HandleFunc("/", a.index) 47 | mux.HandleFunc("/upload", a.upload) 48 | mux.HandleFunc("/search", a.search) 49 | mux.HandleFunc("/uploads", a.uploads) 50 | } 51 | 52 | // index serves the readme on / 53 | func (a *App) index(w http.ResponseWriter, r *http.Request) { 54 | http.ServeFile(w, r, filepath.Join(a.BaseDir, "static/index.html")) 55 | } 56 | -------------------------------------------------------------------------------- /cmd/benchfilter/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // benchfilter reads Go benchmark results from input files, filters 6 | // them, and writes filtered benchmark results to stdout. If no inputs 7 | // are provided, it reads from stdin. 8 | // 9 | // The filter language is described at 10 | // https://pkg.go.dev/golang.org/x/perf/cmd/benchstat#Filtering 11 | package main 12 | 13 | import ( 14 | "flag" 15 | "fmt" 16 | "log" 17 | "os" 18 | 19 | "golang.org/x/perf/benchfmt" 20 | "golang.org/x/perf/benchproc" 21 | ) 22 | 23 | func usage() { 24 | fmt.Fprintf(flag.CommandLine.Output(), `Usage: benchfilter query [inputs...] 25 | 26 | benchfilter reads Go benchmark results from input files, filters them, 27 | and writes filtered benchmark results to stdout. If no inputs are 28 | provided, it reads from stdin. 29 | 30 | The filter language is described at 31 | https://pkg.go.dev/golang.org/x/perf/cmd/benchstat#hdr-Filtering 32 | `) 33 | flag.PrintDefaults() 34 | } 35 | 36 | func main() { 37 | log.SetPrefix("") 38 | log.SetFlags(0) 39 | 40 | flag.Usage = usage 41 | flag.Parse() 42 | if flag.NArg() < 1 { 43 | usage() 44 | os.Exit(2) 45 | } 46 | 47 | // TODO: Consider adding filtering on values, like "@ns/op>=100". 48 | 49 | filter, err := benchproc.NewFilter(flag.Arg(0)) 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | writer := benchfmt.NewWriter(os.Stdout) 55 | files := benchfmt.Files{Paths: flag.Args()[1:], AllowStdin: true, AllowLabels: true} 56 | for files.Scan() { 57 | rec := files.Result() 58 | switch rec := rec.(type) { 59 | case *benchfmt.SyntaxError: 60 | // Non-fatal result parse error. Warn 61 | // but keep going. 62 | fmt.Fprintln(os.Stderr, rec) 63 | continue 64 | case *benchfmt.Result: 65 | if ok, err := filter.Apply(rec); !ok { 66 | if err != nil { 67 | // Print the reason we rejected this result. 68 | fmt.Fprintln(os.Stderr, err) 69 | } 70 | continue 71 | } 72 | } 73 | 74 | err = writer.Write(rec) 75 | if err != nil { 76 | log.Fatal("writing output: ", err) 77 | } 78 | } 79 | if err := files.Err(); err != nil { 80 | log.Fatal(err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /analysis/appengine/template/trend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Performance Result Comparison 6 | 7 | 22 | 23 | 24 | 28 | 34 |
35 | {{if not .Q}} 36 |

Recent Uploads

37 | 38 | 39 | {{range .TrendUploads}} 40 | 41 | {{end}} 42 |
Upload IDtrend
{{.UploadID}}{{.LabelValues.trend}}
43 | {{else}} 44 | {{with .Error}} 45 |

{{.}}

46 | {{else}} 47 |
48 | 70 | {{end}} 71 | {{end}} 72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /benchstat/delta.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Significance tests. 6 | 7 | package benchstat 8 | 9 | import ( 10 | "errors" 11 | 12 | "golang.org/x/perf/internal/stats" 13 | ) 14 | 15 | // A DeltaTest compares the old and new metrics and returns the 16 | // expected probability that they are drawn from the same distribution. 17 | // 18 | // If a probability cannot be computed, the DeltaTest returns an 19 | // error explaining why. Common errors include ErrSamplesEqual 20 | // (all samples are equal), ErrSampleSize (there aren't enough samples), 21 | // and ErrZeroVariance (the sample has zero variance). 22 | // 23 | // As a special case, the missing test NoDeltaTest returns -1, nil. 24 | type DeltaTest func(old, new *Metrics) (float64, error) 25 | 26 | // Errors returned by DeltaTest. 27 | var ( 28 | ErrSamplesEqual = errors.New("all equal") 29 | ErrSampleSize = errors.New("too few samples") 30 | ErrZeroVariance = errors.New("zero variance") 31 | ) 32 | 33 | // NoDeltaTest applies no delta test; it returns -1, nil. 34 | func NoDeltaTest(old, new *Metrics) (pval float64, err error) { 35 | return -1, nil 36 | } 37 | 38 | // TTest is a DeltaTest using the two-sample Welch t-test. 39 | func TTest(old, new *Metrics) (pval float64, err error) { 40 | t, err := stats.TwoSampleWelchTTest(stats.Sample{Xs: old.RValues}, stats.Sample{Xs: new.RValues}, stats.LocationDiffers) 41 | if err != nil { 42 | return -1, convertErr(err) 43 | } 44 | return t.P, nil 45 | } 46 | 47 | // UTest is a DeltaTest using the Mann-Whitney U test. 48 | func UTest(old, new *Metrics) (pval float64, err error) { 49 | u, err := stats.MannWhitneyUTest(old.RValues, new.RValues, stats.LocationDiffers) 50 | if err != nil { 51 | return -1, convertErr(err) 52 | } 53 | return u.P, nil 54 | } 55 | 56 | // convertErr converts from the stats package's internal errors 57 | // to errors exported by this package and expected from 58 | // a DeltaTest. 59 | // Using different errors makes it possible for clients to use 60 | // package benchstat without access to the internal stats package, 61 | // and it also gives us a chance to use shorter error messages. 62 | func convertErr(err error) error { 63 | switch err { 64 | case stats.ErrZeroVariance: 65 | return ErrZeroVariance 66 | case stats.ErrSampleSize: 67 | return ErrSampleSize 68 | case stats.ErrSamplesEqual: 69 | return ErrSamplesEqual 70 | } 71 | return err 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go benchmark analysis tools 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/golang.org/x/perf.svg)](https://pkg.go.dev/golang.org/x/perf) 4 | 5 | This subrepository holds tools and packages for analyzing [Go 6 | benchmark results](https://golang.org/design/14313-benchmark-format), 7 | such as the output of [testing package 8 | benchmarks](https://pkg.go.dev/testing). 9 | 10 | ## Tools 11 | 12 | This subrepository contains command-line tools for analyzing benchmark 13 | result data. 14 | 15 | [cmd/benchstat](cmd/benchstat) computes statistical summaries and A/B 16 | comparisons of Go benchmarks. 17 | 18 | [cmd/benchfilter](cmd/benchfilter) filters the contents of benchmark 19 | result files. 20 | 21 | [cmd/benchsave](cmd/benchsave) publishes benchmark results to 22 | [perf.golang.org](https://perf.golang.org). 23 | 24 | To install all of these commands, run 25 | `go install golang.org/x/perf/cmd/...@latest`. 26 | You can also 27 | `git clone https://go.googlesource.com/perf` and run 28 | `go install ./cmd/...` in the checkout. 29 | 30 | ## Packages 31 | 32 | Underlying the above tools are several packages for working with 33 | benchmark data. These are designed to work together, but can also be 34 | used independently. 35 | 36 | [benchfmt](benchfmt) reads and writes the Go benchmark format. 37 | 38 | [benchunit](benchunit) manipulates benchmark units and formats numbers 39 | in those units. 40 | 41 | [benchproc](benchproc) provides tools for filtering, grouping, and 42 | sorting benchmark results. 43 | 44 | [benchmath](benchmath) provides tools for computing statistics over 45 | distributions of benchmark measurements. 46 | 47 | ## Deprecated packages 48 | 49 | The following packages are deprecated and no longer supported: 50 | 51 | [storage](storage) contains a deprecated version of the 52 | https://perfdata.golang.org/ benchmark result storage system. These 53 | packages have moved to https://golang.org/x/build. 54 | 55 | [analysis](analysis) contains a deprecated version of the 56 | https://perf.golang.org/ benchmark result analysis system. These 57 | packages have moved to https://golang.org/x/build. 58 | 59 | ## Report Issues / Send Patches 60 | 61 | This repository uses Gerrit for code changes. To learn how to submit changes to 62 | this repository, see https://go.dev/doc/contribute. 63 | 64 | The git repository is https://go.googlesource.com/perf. 65 | 66 | The main issue tracker for the perf repository is located at 67 | https://go.dev/issues. Prefix your issue with "x/perf:" in the 68 | subject line, so it is easy to find. 69 | -------------------------------------------------------------------------------- /benchproc/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package benchproc provides tools for filtering, grouping, and 6 | // sorting benchmark results. 7 | // 8 | // This package supports a pipeline processing model based around 9 | // domain-specific languages for filtering and projecting benchmark 10 | // results. These languages are described in "go doc 11 | // golang.org/x/perf/benchproc/syntax". 12 | // 13 | // The typical steps for processing a stream of benchmark 14 | // results are: 15 | // 16 | // 1. Read a stream of benchfmt.Results from one or more input sources. 17 | // Command-line tools will often do this using benchfmt.Files. 18 | // 19 | // 2. For each benchfmt.Result: 20 | // 21 | // 2a. Optionally transform the benchfmt.Result to add or modify keys 22 | // according to a particular tool's needs. Custom keys often start with 23 | // "." to distinguish them from file or sub-name keys. For example, 24 | // benchfmt.Files adds a ".file" key. 25 | // 26 | // 2b. Optionally filter the benchfmt.Result according to a user-provided 27 | // predicate parsed by NewFilter. Filters can keep or discard entire 28 | // Results, or just particular measurements from a Result. 29 | // 30 | // 2c. Project the benchfmt.Result using one or more Projections. 31 | // Projecting a Result extracts a subset of the information from a 32 | // Result into a Key. Projections are produced by a ProjectionParser, 33 | // typically from user-provided projection expressions. An application 34 | // should have a Projection for each "dimension" of its output. For 35 | // example, an application that emits a table may have two Projections: 36 | // one for rows and one for columns. An application that produces a 37 | // scatterplot could have Projections for X and Y as well as other 38 | // visual properties like point color and size. 39 | // 40 | // 2d. Group the benchfmt.Result according to the projected Keys. 41 | // Usually this is done by storing the measurements from the Result in a 42 | // Go map indexed by Key. Because identical Keys compare ==, they can be 43 | // used as map keys. Applications that use two or more Projections may 44 | // use a map of maps, or a map keyed by a struct of two Keys, or some 45 | // combination. 46 | // 47 | // 3. At the end of the Results stream, once all Results have been 48 | // grouped by their Keys, sort the Keys of each dimension using SortKeys 49 | // and present the data in the resulting order. 50 | package benchproc 51 | -------------------------------------------------------------------------------- /benchunit/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package benchunit manipulates benchmark units and formats numbers 6 | // in those units. 7 | package benchunit 8 | 9 | import ( 10 | "fmt" 11 | "unicode" 12 | ) 13 | 14 | // A Class specifies what class of unit prefixes are in use. 15 | type Class int 16 | 17 | const ( 18 | // Decimal indicates values of a given unit should be scaled 19 | // by powers of 1000. Decimal units use the International 20 | // System of Units SI prefixes, such as "k", and "M". 21 | Decimal Class = iota 22 | // Binary indicates values of a given unit should be scaled by 23 | // powers of 1024. Binary units use the International 24 | // Electrotechnical Commission (IEC) binary prefixes, such as 25 | // "Ki" and "Mi". 26 | Binary 27 | ) 28 | 29 | func (c Class) String() string { 30 | switch c { 31 | case Decimal: 32 | return "Decimal" 33 | case Binary: 34 | return "Binary" 35 | } 36 | return fmt.Sprintf("Class(%d)", int(c)) 37 | } 38 | 39 | // ClassOf returns the Class of unit. If unit contains some measure of 40 | // bytes in the numerator, this is Binary. Otherwise, it is Decimal. 41 | func ClassOf(unit string) Class { 42 | p := newParser(unit) 43 | for p.next() { 44 | if (p.tok == "B" || p.tok == "MB" || p.tok == "bytes") && !p.denom { 45 | return Binary 46 | } 47 | } 48 | return Decimal 49 | } 50 | 51 | type parser struct { 52 | rest string // unparsed unit 53 | rpos int // byte consumed from original unit 54 | 55 | // Current token 56 | tok string 57 | pos int // byte offset of tok in original unit 58 | denom bool // current token is in denominator 59 | } 60 | 61 | func newParser(unit string) *parser { 62 | return &parser{rest: unit} 63 | } 64 | 65 | func (p *parser) next() bool { 66 | // Consume separators. 67 | for i, r := range p.rest { 68 | if r == '*' { 69 | p.denom = false 70 | } else if r == '/' { 71 | p.denom = true 72 | } else if !(r == '-' || unicode.IsSpace(r)) { 73 | p.rpos += i 74 | p.rest = p.rest[i:] 75 | goto tok 76 | } 77 | } 78 | // End of string. 79 | p.rest = "" 80 | return false 81 | 82 | tok: 83 | // Consume until separator. 84 | end := len(p.rest) 85 | for i, r := range p.rest { 86 | if r == '*' || r == '/' || r == '-' || unicode.IsSpace(r) { 87 | end = i 88 | break 89 | } 90 | } 91 | p.tok = p.rest[:end] 92 | p.pos = p.rpos 93 | p.rpos += end 94 | p.rest = p.rest[end:] 95 | return true 96 | } 97 | -------------------------------------------------------------------------------- /internal/stats/ttest_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import "testing" 8 | 9 | func TestTTest(t *testing.T) { 10 | s1 := Sample{Xs: []float64{2, 1, 3, 4}} 11 | s2 := Sample{Xs: []float64{6, 5, 7, 9}} 12 | 13 | check := func(want, got *TTestResult) { 14 | if want.N1 != got.N1 || want.N2 != got.N2 || 15 | !aeq(want.T, got.T) || !aeq(want.DoF, got.DoF) || 16 | want.AltHypothesis != got.AltHypothesis || 17 | !aeq(want.P, got.P) { 18 | t.Errorf("want %+v, got %+v", want, got) 19 | } 20 | } 21 | check3 := func(test func(alt LocationHypothesis) (*TTestResult, error), n1, n2 int, t, dof float64, pless, pdiff, pgreater float64) { 22 | want := &TTestResult{N1: n1, N2: n2, T: t, DoF: dof} 23 | 24 | want.AltHypothesis = LocationLess 25 | want.P = pless 26 | got, _ := test(want.AltHypothesis) 27 | check(want, got) 28 | 29 | want.AltHypothesis = LocationDiffers 30 | want.P = pdiff 31 | got, _ = test(want.AltHypothesis) 32 | check(want, got) 33 | 34 | want.AltHypothesis = LocationGreater 35 | want.P = pgreater 36 | got, _ = test(want.AltHypothesis) 37 | check(want, got) 38 | } 39 | 40 | check3(func(alt LocationHypothesis) (*TTestResult, error) { 41 | return TwoSampleTTest(s1, s1, alt) 42 | }, 4, 4, 0, 6, 43 | 0.5, 1, 0.5) 44 | check3(func(alt LocationHypothesis) (*TTestResult, error) { 45 | return TwoSampleWelchTTest(s1, s1, alt) 46 | }, 4, 4, 0, 6, 47 | 0.5, 1, 0.5) 48 | 49 | check3(func(alt LocationHypothesis) (*TTestResult, error) { 50 | return TwoSampleTTest(s1, s2, alt) 51 | }, 4, 4, -3.9703446152237674, 6, 52 | 0.0036820296121056195, 0.0073640592242113214, 0.9963179703878944) 53 | check3(func(alt LocationHypothesis) (*TTestResult, error) { 54 | return TwoSampleWelchTTest(s1, s2, alt) 55 | }, 4, 4, -3.9703446152237674, 5.584615384615385, 56 | 0.004256431565689112, 0.0085128631313781695, 0.9957435684343109) 57 | 58 | check3(func(alt LocationHypothesis) (*TTestResult, error) { 59 | return PairedTTest(s1.Xs, s2.Xs, 0, alt) 60 | }, 4, 4, -17, 3, 61 | 0.0002216717691559955, 0.00044334353831207749, 0.999778328230844) 62 | 63 | check3(func(alt LocationHypothesis) (*TTestResult, error) { 64 | return OneSampleTTest(s1, 0, alt) 65 | }, 4, 0, 3.872983346207417, 3, 66 | 0.9847668541689145, 0.030466291662170977, 0.015233145831085482) 67 | check3(func(alt LocationHypothesis) (*TTestResult, error) { 68 | return OneSampleTTest(s1, 2.5, alt) 69 | }, 4, 0, 0, 3, 70 | 0.5, 1, 0.5) 71 | } 72 | -------------------------------------------------------------------------------- /internal/stats/alg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | // Miscellaneous helper algorithms 8 | 9 | import ( 10 | "fmt" 11 | ) 12 | 13 | func sumint(xs []int) int { 14 | sum := 0 15 | for _, x := range xs { 16 | sum += x 17 | } 18 | return sum 19 | } 20 | 21 | // bisect returns an x in [low, high] such that |f(x)| <= tolerance 22 | // using the bisection method. 23 | // 24 | // f(low) and f(high) must have opposite signs. 25 | // 26 | // If f does not have a root in this interval (e.g., it is 27 | // discontiguous), this returns the X of the apparent discontinuity 28 | // and false. 29 | func bisect(f func(float64) float64, low, high, tolerance float64) (float64, bool) { 30 | flow, fhigh := f(low), f(high) 31 | if -tolerance <= flow && flow <= tolerance { 32 | return low, true 33 | } 34 | if -tolerance <= fhigh && fhigh <= tolerance { 35 | return high, true 36 | } 37 | if mathSign(flow) == mathSign(fhigh) { 38 | panic(fmt.Sprintf("root of f is not bracketed by [low, high]; f(%g)=%g f(%g)=%g", low, flow, high, fhigh)) 39 | } 40 | for { 41 | mid := (high + low) / 2 42 | fmid := f(mid) 43 | if -tolerance <= fmid && fmid <= tolerance { 44 | return mid, true 45 | } 46 | if mid == high || mid == low { 47 | return mid, false 48 | } 49 | if mathSign(fmid) == mathSign(flow) { 50 | low = mid 51 | flow = fmid 52 | } else { 53 | high = mid 54 | fhigh = fmid 55 | } 56 | } 57 | } 58 | 59 | // bisectBool implements the bisection method on a boolean function. 60 | // It returns x1, x2 ∈ [low, high], x1 < x2 such that f(x1) != f(x2) 61 | // and x2 - x1 <= xtol. 62 | // 63 | // If f(low) == f(high), it panics. 64 | func bisectBool(f func(float64) bool, low, high, xtol float64) (x1, x2 float64) { 65 | flow, fhigh := f(low), f(high) 66 | if flow == fhigh { 67 | panic(fmt.Sprintf("root of f is not bracketed by [low, high]; f(%g)=%v f(%g)=%v", low, flow, high, fhigh)) 68 | } 69 | for { 70 | if high-low <= xtol { 71 | return low, high 72 | } 73 | mid := (high + low) / 2 74 | if mid == high || mid == low { 75 | return low, high 76 | } 77 | fmid := f(mid) 78 | if fmid == flow { 79 | low = mid 80 | flow = fmid 81 | } else { 82 | high = mid 83 | fhigh = fmid 84 | } 85 | } 86 | } 87 | 88 | // series returns the sum of the series f(0), f(1), ... 89 | // 90 | // This implementation is fast, but subject to round-off error. 91 | func series(f func(float64) float64) float64 { 92 | y, yp := 0.0, 1.0 93 | for n := 0.0; y != yp; n++ { 94 | yp = y 95 | y += f(n) 96 | } 97 | return y 98 | } 99 | -------------------------------------------------------------------------------- /storage/app/query.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package app 6 | 7 | import ( 8 | "encoding/json" 9 | "net/http" 10 | "strconv" 11 | 12 | "golang.org/x/perf/storage/benchfmt" 13 | ) 14 | 15 | func (a *App) search(w http.ResponseWriter, r *http.Request) { 16 | ctx := requestContext(r) 17 | 18 | if err := r.ParseForm(); err != nil { 19 | http.Error(w, err.Error(), 500) 20 | return 21 | } 22 | 23 | q := r.Form.Get("q") 24 | if q == "" { 25 | http.Error(w, "missing q parameter", 400) 26 | return 27 | } 28 | 29 | query := a.DB.Query(q) 30 | defer query.Close() 31 | 32 | infof(ctx, "query: %s", query.Debug()) 33 | 34 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") 35 | bw := benchfmt.NewPrinter(w) 36 | for query.Next() { 37 | if err := bw.Print(query.Result()); err != nil { 38 | http.Error(w, err.Error(), 500) 39 | return 40 | } 41 | } 42 | if err := query.Err(); err != nil { 43 | errorf(ctx, "query returned error: %v", err) 44 | http.Error(w, err.Error(), 500) 45 | return 46 | } 47 | } 48 | 49 | // uploads serves a list of upload IDs on /uploads. 50 | // If the query parameter q is provided, only uploads containing matching records are returned. 51 | // The format of the result is " \n" where count is the number of matching records. 52 | // The lines are sorted in order from most to least recent. 53 | // If the query parameter limit is provided, only the most recent limit upload IDs are returned. 54 | // If limit is not provided, the most recent 1000 upload IDs are returned. 55 | func (a *App) uploads(w http.ResponseWriter, r *http.Request) { 56 | ctx := requestContext(r) 57 | 58 | if err := r.ParseForm(); err != nil { 59 | http.Error(w, err.Error(), 500) 60 | return 61 | } 62 | 63 | q := r.Form.Get("q") 64 | 65 | limit := 1000 66 | limitStr := r.Form.Get("limit") 67 | if limitStr != "" { 68 | var err error 69 | limit, err = strconv.Atoi(limitStr) 70 | if err != nil { 71 | http.Error(w, "invalid limit parameter", 400) 72 | return 73 | } 74 | } 75 | 76 | res := a.DB.ListUploads(q, r.Form["extra_label"], limit) 77 | defer res.Close() 78 | 79 | infof(ctx, "query: %s", res.Debug()) 80 | 81 | w.Header().Set("Content-Type", "application/json") 82 | e := json.NewEncoder(w) 83 | for res.Next() { 84 | ui := res.Info() 85 | if err := e.Encode(&ui); err != nil { 86 | errorf(ctx, "failed to encode JSON: %v", err) 87 | http.Error(w, err.Error(), 500) 88 | return 89 | } 90 | } 91 | if err := res.Err(); err != nil { 92 | http.Error(w, err.Error(), 500) 93 | return 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module golang.org/x/perf 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | cloud.google.com/go/storage v1.29.0 7 | github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf 8 | github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0 9 | github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 10 | github.com/go-sql-driver/mysql v1.4.1 11 | github.com/google/safehtml v0.0.2 12 | github.com/mattn/go-sqlite3 v1.14.14 13 | golang.org/x/net v0.48.0 14 | golang.org/x/oauth2 v0.34.0 15 | gonum.org/v1/plot v0.10.1 16 | google.golang.org/api v0.126.0 17 | google.golang.org/appengine v1.6.7 18 | ) 19 | 20 | require ( 21 | cloud.google.com/go v0.110.2 // indirect 22 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 23 | cloud.google.com/go/iam v0.13.0 // indirect 24 | git.sr.ht/~sbinet/gg v0.3.1 // indirect 25 | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect 26 | github.com/go-fonts/liberation v0.2.0 // indirect 27 | github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 // indirect 28 | github.com/go-pdf/fpdf v0.6.0 // indirect 29 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 30 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 31 | github.com/golang/protobuf v1.5.3 // indirect 32 | github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac // indirect 33 | github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 // indirect 34 | github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 // indirect 35 | github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 // indirect 36 | github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 // indirect 37 | github.com/google/go-cmp v0.6.0 // indirect 38 | github.com/google/s2a-go v0.1.4 // indirect 39 | github.com/google/uuid v1.3.0 // indirect 40 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect 41 | github.com/googleapis/gax-go/v2 v2.11.0 // indirect 42 | go.opencensus.io v0.24.0 // indirect 43 | golang.org/x/crypto v0.46.0 // indirect 44 | golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect 45 | golang.org/x/image v0.34.0 // indirect 46 | golang.org/x/sys v0.39.0 // indirect 47 | golang.org/x/text v0.32.0 // indirect 48 | golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect 49 | gonum.org/v1/gonum v0.11.0 // indirect 50 | google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect 51 | google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect 52 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect 53 | google.golang.org/grpc v1.55.0 // indirect 54 | google.golang.org/protobuf v1.31.0 // indirect 55 | ) 56 | -------------------------------------------------------------------------------- /benchunit/tidy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchunit 6 | 7 | import ( 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | type tidyEntry struct { 13 | tidied string 14 | factor float64 15 | } 16 | 17 | var tidyCache sync.Map // unit string -> *tidyCache 18 | 19 | // Tidy normalizes a value with a (possibly pre-scaled) unit into base units. 20 | // For example, if unit is "ns" or "MB", it will re-scale the value to 21 | // "sec" or "B" units, respectively. It returns the re-scaled value and 22 | // its new unit. If the value is already in base units, it does nothing. 23 | func Tidy(value float64, unit string) (tidiedValue float64, tidiedUnit string) { 24 | newUnit, factor := tidyUnit(unit) 25 | return value * factor, newUnit 26 | } 27 | 28 | // tidyUnit normalizes common pre-scaled units like "ns" to "sec" and 29 | // "MB" to "B". It returns the tidied version of unit and the 30 | // multiplicative factor to convert a value in unit "unit" to a value in 31 | // unit to "tidied". For example, to convert value x in the untidied unit 32 | // to the tidied unit, multiply x by factor. 33 | func tidyUnit(unit string) (tidied string, factor float64) { 34 | // Fast path for units from testing package. 35 | switch unit { 36 | case "ns/op": 37 | return "sec/op", 1e-9 38 | case "MB/s": 39 | return "B/s", 1e6 40 | case "B/op", "allocs/op": 41 | return unit, 1 42 | } 43 | // Fast path for units with no normalization. 44 | if !(strings.Contains(unit, "ns") || strings.Contains(unit, "MB")) { 45 | return unit, 1 46 | } 47 | 48 | // Check the cache. 49 | if tc, ok := tidyCache.Load(unit); ok { 50 | tc := tc.(*tidyEntry) 51 | return tc.tidied, tc.factor 52 | } 53 | 54 | // Do the hard work and cache it. 55 | tidied, factor = tidyUnitUncached(unit) 56 | tidyCache.Store(unit, &tidyEntry{tidied, factor}) 57 | return 58 | } 59 | 60 | func tidyUnitUncached(unit string) (tidied string, factor float64) { 61 | type edit struct { 62 | pos, len int 63 | replace string 64 | } 65 | 66 | // The caller has handled the fast paths. Parse the unit. 67 | factor = 1 68 | p := newParser(unit) 69 | edits := make([]edit, 0, 4) 70 | for p.next() { 71 | if p.denom { 72 | // Don't edit in the denominator. 73 | continue 74 | } 75 | switch p.tok { 76 | case "ns": 77 | edits = append(edits, edit{p.pos, len("ns"), "sec"}) 78 | factor /= 1e9 79 | case "MB": 80 | edits = append(edits, edit{p.pos, len("MB"), "B"}) 81 | factor *= 1e6 82 | } 83 | } 84 | // Apply edits. 85 | for i := len(edits) - 1; i >= 0; i-- { 86 | e := edits[i] 87 | unit = unit[:e.pos] + e.replace + unit[e.pos+e.len:] 88 | } 89 | return unit, factor 90 | } 91 | -------------------------------------------------------------------------------- /storage/fs/fs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package fs provides a backend-agnostic filesystem layer for storing 6 | // performance results. 7 | package fs 8 | 9 | import ( 10 | "context" 11 | "errors" 12 | "io" 13 | "sort" 14 | "sync" 15 | ) 16 | 17 | // An FS stores uploaded benchmark data files. 18 | type FS interface { 19 | // NewWriter returns a Writer for a given file name. 20 | // When the Writer is closed, the file will be stored with the 21 | // given metadata and the data written to the writer. 22 | NewWriter(ctx context.Context, name string, metadata map[string]string) (Writer, error) 23 | } 24 | 25 | // A Writer is an io.Writer that can also be closed with an error. 26 | type Writer interface { 27 | io.WriteCloser 28 | // CloseWithError cancels the writing of the file, removing 29 | // any partially written data. 30 | CloseWithError(error) error 31 | } 32 | 33 | // MemFS is an in-memory filesystem implementing the FS interface. 34 | type MemFS struct { 35 | mu sync.Mutex 36 | content map[string]*memFile 37 | } 38 | 39 | // NewMemFS constructs a new, empty MemFS. 40 | func NewMemFS() *MemFS { 41 | return &MemFS{ 42 | content: make(map[string]*memFile), 43 | } 44 | } 45 | 46 | // NewWriter returns a Writer for a given file name. As a side effect, 47 | // it associates the given metadata with the file. 48 | func (fs *MemFS) NewWriter(_ context.Context, name string, metadata map[string]string) (Writer, error) { 49 | meta := make(map[string]string) 50 | for k, v := range metadata { 51 | meta[k] = v 52 | } 53 | return &memFile{fs: fs, name: name, metadata: meta}, nil 54 | } 55 | 56 | // Files returns the names of the files written to fs. 57 | func (fs *MemFS) Files() []string { 58 | fs.mu.Lock() 59 | defer fs.mu.Unlock() 60 | var files []string 61 | for f := range fs.content { 62 | files = append(files, f) 63 | } 64 | sort.Strings(files) 65 | return files 66 | } 67 | 68 | // memFile represents a file in a MemFS. While the file is being 69 | // written, fs points to the filesystem. Close writes the file's 70 | // content to fs and sets fs to nil. 71 | type memFile struct { 72 | fs *MemFS 73 | name string 74 | metadata map[string]string 75 | content []byte 76 | } 77 | 78 | func (f *memFile) Write(p []byte) (int, error) { 79 | f.content = append(f.content, p...) 80 | return len(p), nil 81 | } 82 | 83 | func (f *memFile) Close() error { 84 | if f.fs == nil { 85 | return errors.New("already closed") 86 | } 87 | f.fs.mu.Lock() 88 | defer f.fs.mu.Unlock() 89 | f.fs.content[f.name] = f 90 | f.fs = nil 91 | return nil 92 | } 93 | 94 | func (f *memFile) CloseWithError(error) error { 95 | f.fs = nil 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /internal/stats/beta.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import "math" 8 | 9 | func lgamma(x float64) float64 { 10 | y, _ := math.Lgamma(x) 11 | return y 12 | } 13 | 14 | // mathBeta returns the value of the complete beta function B(a, b). 15 | func mathBeta(a, b float64) float64 { 16 | // B(x,y) = Γ(x)Γ(y) / Γ(x+y) 17 | return math.Exp(lgamma(a) + lgamma(b) - lgamma(a+b)) 18 | } 19 | 20 | // mathBetaInc returns the value of the regularized incomplete beta 21 | // function Iₓ(a, b). 22 | // 23 | // This is not to be confused with the "incomplete beta function", 24 | // which can be computed as BetaInc(x, a, b)*Beta(a, b). 25 | // 26 | // If x < 0 or x > 1, returns NaN. 27 | func mathBetaInc(x, a, b float64) float64 { 28 | // Based on Numerical Recipes in C, section 6.4. This uses the 29 | // continued fraction definition of I: 30 | // 31 | // (xᵃ*(1-x)ᵇ)/(a*B(a,b)) * (1/(1+(d₁/(1+(d₂/(1+...)))))) 32 | // 33 | // where B(a,b) is the beta function and 34 | // 35 | // d_{2m+1} = -(a+m)(a+b+m)x/((a+2m)(a+2m+1)) 36 | // d_{2m} = m(b-m)x/((a+2m-1)(a+2m)) 37 | if x < 0 || x > 1 { 38 | return math.NaN() 39 | } 40 | bt := 0.0 41 | if 0 < x && x < 1 { 42 | // Compute the coefficient before the continued 43 | // fraction. 44 | bt = math.Exp(lgamma(a+b) - lgamma(a) - lgamma(b) + 45 | a*math.Log(x) + b*math.Log(1-x)) 46 | } 47 | if x < (a+1)/(a+b+2) { 48 | // Compute continued fraction directly. 49 | return bt * betacf(x, a, b) / a 50 | } else { 51 | // Compute continued fraction after symmetry transform. 52 | return 1 - bt*betacf(1-x, b, a)/b 53 | } 54 | } 55 | 56 | // betacf is the continued fraction component of the regularized 57 | // incomplete beta function Iₓ(a, b). 58 | func betacf(x, a, b float64) float64 { 59 | const maxIterations = 200 60 | const epsilon = 3e-14 61 | 62 | raiseZero := func(z float64) float64 { 63 | if math.Abs(z) < math.SmallestNonzeroFloat64 { 64 | return math.SmallestNonzeroFloat64 65 | } 66 | return z 67 | } 68 | 69 | c := 1.0 70 | d := 1 / raiseZero(1-(a+b)*x/(a+1)) 71 | h := d 72 | for m := 1; m <= maxIterations; m++ { 73 | mf := float64(m) 74 | 75 | // Even step of the recurrence. 76 | numer := mf * (b - mf) * x / ((a + 2*mf - 1) * (a + 2*mf)) 77 | d = 1 / raiseZero(1+numer*d) 78 | c = raiseZero(1 + numer/c) 79 | h *= d * c 80 | 81 | // Odd step of the recurrence. 82 | numer = -(a + mf) * (a + b + mf) * x / ((a + 2*mf) * (a + 2*mf + 1)) 83 | d = 1 / raiseZero(1+numer*d) 84 | c = raiseZero(1 + numer/c) 85 | hfac := d * c 86 | h *= hfac 87 | 88 | if math.Abs(hfac-1) < epsilon { 89 | return h 90 | } 91 | } 92 | panic("betainc: a or b too big; failed to converge") 93 | } 94 | -------------------------------------------------------------------------------- /benchproc/internal/parse/tree.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package parse 6 | 7 | import ( 8 | "fmt" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | // A Filter is a node in the boolean filter. It can either be a 14 | // FilterOp or a FilterMatch. 15 | type Filter interface { 16 | isFilter() 17 | String() string 18 | } 19 | 20 | // A FilterMatch is a leaf in a Filter tree that tests a specific key 21 | // for a match. 22 | type FilterMatch struct { 23 | Key string 24 | 25 | // Regexp is the regular expression to match against the 26 | // value. This may be nil, in which case this is a literal 27 | // match against Lit. 28 | Regexp *regexp.Regexp 29 | // Lit is the literal value to match against the value if Regexp 30 | // is nil. 31 | Lit string 32 | 33 | // Off is the byte offset of the key in the original query, 34 | // for error reporting. 35 | Off int 36 | } 37 | 38 | func (q *FilterMatch) isFilter() {} 39 | func (q *FilterMatch) String() string { 40 | if q.Regexp != nil { 41 | return quoteWord(q.Key) + ":/" + q.Regexp.String() + "/" 42 | } 43 | return quoteWord(q.Key) + ":" + quoteWord(q.Lit) 44 | } 45 | 46 | // Match returns whether q matches the given value of q.Key. 47 | func (q *FilterMatch) Match(value []byte) bool { 48 | if q.Regexp != nil { 49 | return q.Regexp.Match(value) 50 | } 51 | return q.Lit == string(value) 52 | } 53 | 54 | // MatchString returns whether q matches the given value of q.Key. 55 | func (q *FilterMatch) MatchString(value string) bool { 56 | if q.Regexp != nil { 57 | return q.Regexp.MatchString(value) 58 | } 59 | return q.Lit == value 60 | } 61 | 62 | // A FilterOp is a boolean operator in the Filter tree. OpNot must have 63 | // exactly one child node. OpAnd and OpOr may have zero or more child nodes. 64 | type FilterOp struct { 65 | Op Op 66 | Exprs []Filter 67 | } 68 | 69 | func (q *FilterOp) isFilter() {} 70 | func (q *FilterOp) String() string { 71 | var op string 72 | switch q.Op { 73 | case OpNot: 74 | return fmt.Sprintf("-%s", q.Exprs[0]) 75 | case OpAnd: 76 | if len(q.Exprs) == 0 { 77 | return "*" 78 | } 79 | op = " AND " 80 | case OpOr: 81 | if len(q.Exprs) == 0 { 82 | return "-*" 83 | } 84 | op = " OR " 85 | } 86 | var buf strings.Builder 87 | buf.WriteByte('(') 88 | for i, e := range q.Exprs { 89 | if i > 0 { 90 | buf.WriteString(op) 91 | } 92 | buf.WriteString(e.String()) 93 | } 94 | buf.WriteByte(')') 95 | return buf.String() 96 | } 97 | 98 | // Op specifies a type of boolean operator. 99 | type Op int 100 | 101 | const ( 102 | OpAnd Op = 1 + iota 103 | OpOr 104 | OpNot 105 | ) 106 | -------------------------------------------------------------------------------- /internal/stats/utest_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import "testing" 8 | 9 | func TestMannWhitneyUTest(t *testing.T) { 10 | check := func(want, got *MannWhitneyUTestResult) { 11 | if want.N1 != got.N1 || want.N2 != got.N2 || 12 | !aeq(want.U, got.U) || 13 | want.AltHypothesis != got.AltHypothesis || 14 | !aeq(want.P, got.P) { 15 | t.Errorf("want %+v, got %+v", want, got) 16 | } 17 | } 18 | check3 := func(x1, x2 []float64, U float64, pless, pdiff, pgreater float64) { 19 | want := &MannWhitneyUTestResult{N1: len(x1), N2: len(x2), U: U} 20 | 21 | want.AltHypothesis = LocationLess 22 | want.P = pless 23 | got, _ := MannWhitneyUTest(x1, x2, want.AltHypothesis) 24 | check(want, got) 25 | 26 | want.AltHypothesis = LocationDiffers 27 | want.P = pdiff 28 | got, _ = MannWhitneyUTest(x1, x2, want.AltHypothesis) 29 | check(want, got) 30 | 31 | want.AltHypothesis = LocationGreater 32 | want.P = pgreater 33 | got, _ = MannWhitneyUTest(x1, x2, want.AltHypothesis) 34 | check(want, got) 35 | } 36 | 37 | s1 := []float64{2, 1, 3, 5} 38 | s2 := []float64{12, 11, 13, 15} 39 | s3 := []float64{0, 4, 6, 7} // Interleaved with s1, but no ties 40 | s4 := []float64{2, 2, 2, 2} 41 | s5 := []float64{1, 1, 1, 1, 1} 42 | 43 | // Small sample, no ties 44 | check3(s1, s2, 0, 0.014285714285714289, 0.028571428571428577, 1) 45 | check3(s2, s1, 16, 1, 0.028571428571428577, 0.014285714285714289) 46 | check3(s1, s3, 5, 0.24285714285714288, 0.485714285714285770, 0.8285714285714285) 47 | 48 | // Small sample, ties 49 | // TODO: Check these against some other implementation. 50 | check3(s1, s1, 8, 0.6285714285714286, 1, 0.6285714285714286) 51 | check3(s1, s4, 10, 0.8571428571428571, 0.7142857142857143, 0.3571428571428571) 52 | check3(s1, s5, 17.5, 1, 0, 0.04761904761904767) 53 | 54 | r, err := MannWhitneyUTest(s4, s4, LocationDiffers) 55 | if err != ErrSamplesEqual { 56 | t.Errorf("want ErrSamplesEqual, got %+v, %+v", r, err) 57 | } 58 | 59 | // Large samples. 60 | l1 := make([]float64, 500) 61 | for i := range l1 { 62 | l1[i] = float64(i * 2) 63 | } 64 | l2 := make([]float64, 600) 65 | for i := range l2 { 66 | l2[i] = float64(i*2 - 41) 67 | } 68 | l3 := append([]float64{}, l2...) 69 | for i := 0; i < 30; i++ { 70 | l3[i] = l1[i] 71 | } 72 | // For comparing with R's wilcox.test: 73 | // l1 <- seq(0, 499)*2 74 | // l2 <- seq(0,599)*2-41 75 | // l3 <- l2; for (i in 1:30) { l3[i] = l1[i] } 76 | 77 | check3(l1, l2, 135250, 0.0024667680407086112, 0.0049335360814172224, 0.9975346930458906) 78 | check3(l1, l1, 125000, 0.5000436801680628, 1, 0.5000436801680628) 79 | check3(l1, l3, 134845, 0.0019351907119808942, 0.0038703814239617884, 0.9980659818257166) 80 | } 81 | -------------------------------------------------------------------------------- /benchproc/internal/parse/filter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package parse 6 | 7 | import "testing" 8 | 9 | func TestParseFilter(t *testing.T) { 10 | check := func(query string, want string) { 11 | t.Helper() 12 | q, err := ParseFilter(query) 13 | if err != nil { 14 | t.Errorf("%s: unexpected error %s", query, err) 15 | } else if got := q.String(); got != want { 16 | t.Errorf("%s: got %s, want %s", query, got, want) 17 | } 18 | } 19 | checkErr := func(query, error string, pos int) { 20 | t.Helper() 21 | _, err := ParseFilter(query) 22 | if se, _ := err.(*SyntaxError); se == nil || se.Msg != error || se.Off != pos { 23 | t.Errorf("%s: want error %s at %d; got %s", query, error, pos, err) 24 | } 25 | } 26 | check(`*`, `*`) 27 | check(`a:b`, `a:b`) 28 | checkErr(`a`, "expected key:value", 0) 29 | checkErr(`a :`, "expected key:value", 0) 30 | check(`a :b`, `a:b`) 31 | check(`a : b`, `a:b`) 32 | checkErr(`a:`, "expected key:value", 0) 33 | checkErr(``, "expected key:value or subexpression", 0) 34 | checkErr(`()`, "expected key:value or subexpression", 1) 35 | checkErr(`AND`, "expected key:value or subexpression", 0) 36 | check(`"a":"b c"`, `a:"b c"`) 37 | check(`"a\"":"b c"`, `"a\"":"b c"`) 38 | check(`"a\u2603":"b c"`, `a☃:"b c"`) 39 | checkErr(`"a\z":"b c"`, "bad escape sequence", 0) 40 | checkErr(`a "b`, "missing end quote", 2) 41 | check("a-b:c", `a-b:c`) // "-" inside bare word 42 | check("a*b:c", `a*b:c`) // "*" inside bare word 43 | 44 | // Parens 45 | check(`(a:b)`, `a:b`) 46 | checkErr(`(a:b`, "missing \")\"", 4) 47 | checkErr(`(a:b))`, "unexpected \")\"", 5) 48 | 49 | // Operators 50 | check(`a:b c:d e:f`, `(a:b AND c:d AND e:f)`) 51 | check(`-a:b`, `-a:b`) 52 | check(`-*`, `-*`) 53 | check(`a:b AND c:d`, `(a:b AND c:d)`) 54 | check(`-a:b AND c:d`, `(-a:b AND c:d)`) 55 | check(`-(a:b AND c:d)`, `-(a:b AND c:d)`) 56 | check(`a:b AND * AND c:d`, `(a:b AND * AND c:d)`) 57 | check(`a:b OR c:d`, `(a:b OR c:d)`) 58 | check(`a:b AND c:d OR e:f AND g:h`, `((a:b AND c:d) OR (e:f AND g:h))`) 59 | check(`a:b AND (c:d OR e:f) AND g:h`, `(a:b AND (c:d OR e:f) AND g:h)`) 60 | 61 | // Regexp match 62 | checkErr("a:/b", "missing close \"/\"", 2) 63 | checkErr("a:/[[:foo:]]/", "error parsing regexp: invalid character class range: `[:foo:]`", 2) 64 | checkErr("a:/b/c", "regexp must be followed by space or an operator (unescaped \"/\"?)", 5) 65 | check("a:/b[/](/)\\/c/", "a:/b[/](/)\\/c/") 66 | 67 | // Multi-match 68 | check(`a:(b OR c OR d)`, `(a:b OR a:c OR a:d)`) 69 | check(`a:(b OR "c " OR /d/)`, `(a:b OR a:"c " OR a:/d/)`) 70 | checkErr(`a:(b c)`, "value list must be separated by OR", 5) 71 | checkErr(`a:(b AND c)`, "value list must be separated by OR", 5) 72 | checkErr(`a:(b OR AND)`, "expected value", 8) 73 | checkErr(`a:()`, "expected value", 3) 74 | } 75 | -------------------------------------------------------------------------------- /cmd/benchstat/doc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "go/ast" 10 | "go/doc" 11 | "go/parser" 12 | "go/token" 13 | "os" 14 | "regexp" 15 | "strings" 16 | "testing" 17 | ) 18 | 19 | // Test that the examples in the command documentation do what they 20 | // say. 21 | func TestDoc(t *testing.T) { 22 | // Read the package documentation. 23 | fset := token.NewFileSet() 24 | f, err := parser.ParseFile(fset, "main.go", nil, parser.ParseComments) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | p, err := doc.NewFromFiles(fset, []*ast.File{f}, "p") 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | tests := parseDocTests(p.Doc) 33 | if len(tests) == 0 { 34 | t.Fatal("failed to parse doc tests: found 0 tests") 35 | } 36 | 37 | // Run the tests. 38 | if err := os.Chdir("testdata"); err != nil { 39 | t.Fatal(err) 40 | } 41 | defer os.Chdir("..") 42 | for _, test := range tests { 43 | var got, gotErr bytes.Buffer 44 | t.Logf("benchstat %s", strings.Join(test.args, " ")) 45 | if err := benchstat(&got, &gotErr, test.args); err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | // None of the doc tests should have error output. 50 | if gotErr.Len() != 0 { 51 | t.Errorf("unexpected stderr output:\n%s", gotErr.String()) 52 | continue 53 | } 54 | 55 | // Compare the output 56 | diff(t, []byte(test.want), got.Bytes()) 57 | } 58 | } 59 | 60 | type docTest struct { 61 | args []string 62 | want string 63 | } 64 | 65 | var docTestRe = regexp.MustCompile(`(?m)^[ \t]+\$ benchstat (.*)\n((?:\t.*\n|\n)+)`) 66 | 67 | func parseDocTests(doc string) []*docTest { 68 | var tests []*docTest 69 | for _, m := range docTestRe.FindAllStringSubmatch(doc, -1) { 70 | want := m[2] 71 | // Strip extra trailing newlines 72 | want = strings.TrimRight(want, "\n") + "\n" 73 | // Strip \t at the beginning of each line 74 | want = strings.Replace(want[1:], "\n\t", "\n", -1) 75 | tests = append(tests, &docTest{ 76 | args: parseArgs(m[1]), 77 | want: want, 78 | }) 79 | } 80 | return tests 81 | } 82 | 83 | // parseArgs is a very basic parser for shell-like word lists. 84 | func parseArgs(x string) []string { 85 | // TODO: Use strings.Cut 86 | var out []string 87 | x = strings.Trim(x, " ") 88 | for len(x) > 0 { 89 | if x[0] == '"' { 90 | x = x[1:] 91 | i := strings.Index(x, "\"") 92 | if i < 0 { 93 | panic("missing \"") 94 | } 95 | out = append(out, x[:i]) 96 | x = strings.TrimLeft(x[i+1:], " ") 97 | } else if i := strings.Index(x, " "); i < 0 { 98 | out = append(out, x) 99 | x = "" 100 | } else { 101 | out = append(out, x[:i]) 102 | x = strings.TrimLeft(x[i+1:], " ") 103 | } 104 | } 105 | return out 106 | } 107 | -------------------------------------------------------------------------------- /benchfmt/units_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchfmt 6 | 7 | import ( 8 | "testing" 9 | 10 | "golang.org/x/perf/benchmath" 11 | ) 12 | 13 | func TestReaderUnits(t *testing.T) { 14 | _, reader := parseAll(t, ` 15 | Unit ns/op a=1 b=2 16 | Unit MB/s c=3 c=3 17 | `) 18 | units := reader.Units() 19 | if len(units) != 3 { 20 | t.Fatalf("expected 3 unit metadata, got %d:\n%v", len(units), units) 21 | } 22 | check := func(unit, tidyUnit, key, value string) { 23 | t.Helper() 24 | // The map is indexed by tidy units. 25 | mkey := UnitMetadataKey{tidyUnit, key} 26 | m, ok := units[mkey] 27 | if !ok { 28 | t.Errorf("for %s:%s, want %s, got none", tidyUnit, key, value) 29 | return 30 | } 31 | want := UnitMetadata{UnitMetadataKey: mkey, OrigUnit: unit, Value: value} 32 | if m.UnitMetadataKey != want.UnitMetadataKey || 33 | m.OrigUnit != want.OrigUnit || 34 | m.Value != want.Value { 35 | t.Errorf("for %s:%s, want %+v, got %+v", unit, key, want, m) 36 | } 37 | // Check that Get works for both tidied and untidied units. 38 | if got := units.Get(unit, key); got != m { 39 | t.Errorf("for Get(%v, %v), want %+v, got %+v", unit, key, m, got) 40 | } 41 | if got := units.Get(tidyUnit, key); got != m { 42 | t.Errorf("for Get(%v, %v), want %+v, got %+v", tidyUnit, key, m, got) 43 | } 44 | } 45 | check("ns/op", "sec/op", "a", "1") 46 | check("ns/op", "sec/op", "b", "2") 47 | check("MB/s", "B/s", "c", "3") 48 | } 49 | 50 | func TestUnitsGetAssumption(t *testing.T) { 51 | _, reader := parseAll(t, ` 52 | Unit a assume=nothing 53 | Unit ns/frob assume=exact 54 | Unit c assume=blah`) 55 | units := reader.Units() 56 | 57 | check := func(unit string, want benchmath.Assumption) { 58 | got := units.GetAssumption(unit) 59 | if got != want { 60 | t.Errorf("for unit %s, want assumption %s, got %s", unit, want, got) 61 | } 62 | } 63 | check("a", benchmath.AssumeNothing) 64 | check("ns/frob", benchmath.AssumeExact) 65 | check("sec/frob", benchmath.AssumeExact) 66 | check("c", benchmath.AssumeNothing) 67 | } 68 | 69 | func TestUnitsGetBetter(t *testing.T) { 70 | _, reader := parseAll(t, ` 71 | Unit a better=higher 72 | Unit b better=lower`) 73 | units := reader.Units() 74 | 75 | check := func(unit string, want int) { 76 | got := units.GetBetter(unit) 77 | if got != want { 78 | t.Errorf("for unit %s, want better=%+d, got %+d", unit, want, got) 79 | } 80 | } 81 | check("a", 1) 82 | check("b", -1) 83 | check("c", 0) 84 | 85 | check("ns/op", -1) 86 | check("sec/op", -1) 87 | 88 | check("MB/s", 1) 89 | check("B/s", 1) 90 | 91 | check("B/op", -1) 92 | check("allocs/op", -1) 93 | 94 | // Check that we can override the built-ins 95 | _, reader = parseAll(t, "Unit ns/op better=higher") 96 | units = reader.Units() 97 | check("ns/op", 1) 98 | check("sec/op", 1) 99 | } 100 | -------------------------------------------------------------------------------- /benchstat/html.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchstat 6 | 7 | import ( 8 | "bytes" 9 | "strings" 10 | 11 | "github.com/google/safehtml" 12 | "github.com/google/safehtml/template" 13 | ) 14 | 15 | var htmlTemplate = template.Must(template.New("").Funcs(htmlFuncs).Parse(` 16 | {{- if . -}} 17 | {{with index . 0}} 18 | 19 | {{if eq (len .Configs) 1}} 20 | {{- else -}} 21 | 26 | {{if eq (len .Configs) 1}} 27 | 35 | {{- else -}} 36 | 37 | {{- end -}} 38 | 43 | {{end}} 44 |
{{range .Configs}}{{.}}{{end}} 22 | {{end}} 23 | {{end}} 24 | {{- range $i, $table := .}} 25 |
{{.Metric}} 28 | {{else -}} 29 |
{{.Metric}}{{if .OldNewDelta}}delta{{end}} 30 | {{end}}{{range $group := group $table.Rows -}} 31 | {{if and (gt (len $table.Groups) 1) (len (index . 0).Group)}}
{{(index . 0).Group}}{{end}} 32 | {{- range $row := . -}} 33 | {{if $table.OldNewDelta -}} 34 |
{{.Benchmark}}{{range .Metrics}}{{.Format $row.Scaler}}{{end}}{{if $table.OldNewDelta}}{{replace .Delta "-" "−" -1}}{{.Note}}{{end}} 39 | {{end -}} 40 | {{- end -}} 41 |
  42 |
45 | {{end -}} 46 | `)) 47 | 48 | var htmlFuncs = template.FuncMap{ 49 | "replace": strings.Replace, 50 | "group": htmlGroup, 51 | "colspan": htmlColspan, 52 | } 53 | 54 | func htmlColspan(configs int, delta bool) int { 55 | if delta { 56 | configs++ 57 | } 58 | return configs + 1 59 | } 60 | 61 | func htmlGroup(rows []*Row) (out [][]*Row) { 62 | var group string 63 | var cur []*Row 64 | for _, r := range rows { 65 | if r.Group != group { 66 | group = r.Group 67 | if len(cur) > 0 { 68 | out = append(out, cur) 69 | cur = nil 70 | } 71 | } 72 | cur = append(cur, r) 73 | } 74 | if len(cur) > 0 { 75 | out = append(out, cur) 76 | } 77 | return 78 | } 79 | 80 | // SafeFormatHTML returns the HTML formatting of the tables. 81 | func SafeFormatHTML(tables []*Table) safehtml.HTML { 82 | h, err := htmlTemplate.ExecuteToHTML(tables) 83 | if err != nil { 84 | // Only possible errors here are template not matching data structure. 85 | // Don't make caller check - it's our fault. 86 | panic(err) 87 | } 88 | return h 89 | } 90 | 91 | // FormatHTML appends an HTML formatting of the tables to buf. 92 | func FormatHTML(buf *bytes.Buffer, tables []*Table) { 93 | err := htmlTemplate.Execute(buf, tables) 94 | if err != nil { 95 | // Only possible errors here are template not matching data structure. 96 | // Don't make caller check - it's our fault. 97 | panic(err) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /internal/stats/tdist_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import "testing" 8 | 9 | func TestT(t *testing.T) { 10 | testFunc(t, "PDF(%v|v=1)", TDist{1}.PDF, map[float64]float64{ 11 | -10: 0.0031515830315226806, 12 | -9: 0.0038818278802901312, 13 | -8: 0.0048970751720583188, 14 | -7: 0.0063661977236758151, 15 | -6: 0.0086029698968592104, 16 | -5: 0.012242687930145799, 17 | -4: 0.018724110951987692, 18 | -3: 0.031830988618379075, 19 | -2: 0.063661977236758149, 20 | -1: 0.15915494309189537, 21 | 0: 0.31830988618379075, 22 | 1: 0.15915494309189537, 23 | 2: 0.063661977236758149, 24 | 3: 0.031830988618379075, 25 | 4: 0.018724110951987692, 26 | 5: 0.012242687930145799, 27 | 6: 0.0086029698968592104, 28 | 7: 0.0063661977236758151, 29 | 8: 0.0048970751720583188, 30 | 9: 0.0038818278802901312}) 31 | testFunc(t, "PDF(%v|v=5)", TDist{5}.PDF, map[float64]float64{ 32 | -10: 4.0989816415343313e-05, 33 | -9: 7.4601664362590413e-05, 34 | -8: 0.00014444303269563934, 35 | -7: 0.00030134402928803911, 36 | -6: 0.00068848154013743002, 37 | -5: 0.0017574383788078445, 38 | -4: 0.0051237270519179133, 39 | -3: 0.017292578800222964, 40 | -2: 0.065090310326216455, 41 | -1: 0.21967979735098059, 42 | 0: 0.3796066898224944, 43 | 1: 0.21967979735098059, 44 | 2: 0.065090310326216455, 45 | 3: 0.017292578800222964, 46 | 4: 0.0051237270519179133, 47 | 5: 0.0017574383788078445, 48 | 6: 0.00068848154013743002, 49 | 7: 0.00030134402928803911, 50 | 8: 0.00014444303269563934, 51 | 9: 7.4601664362590413e-05}) 52 | 53 | testFunc(t, "CDF(%v|v=1)", TDist{1}.CDF, map[float64]float64{ 54 | -10: 0.03172551743055356, 55 | -9: 0.035223287477277272, 56 | -8: 0.039583424160565539, 57 | -7: 0.045167235300866547, 58 | -6: 0.052568456711253424, 59 | -5: 0.06283295818900117, 60 | -4: 0.077979130377369324, 61 | -3: 0.10241638234956672, 62 | -2: 0.14758361765043321, 63 | -1: 0.24999999999999978, 64 | 0: 0.5, 65 | 1: 0.75000000000000022, 66 | 2: 0.85241638234956674, 67 | 3: 0.89758361765043326, 68 | 4: 0.92202086962263075, 69 | 5: 0.93716704181099886, 70 | 6: 0.94743154328874657, 71 | 7: 0.95483276469913347, 72 | 8: 0.96041657583943452, 73 | 9: 0.96477671252272279}) 74 | testFunc(t, "CDF(%v|v=5)", TDist{5}.CDF, map[float64]float64{ 75 | -10: 8.5473787871481787e-05, 76 | -9: 0.00014133998712194845, 77 | -8: 0.00024645333028622187, 78 | -7: 0.00045837375719920225, 79 | -6: 0.00092306914479700695, 80 | -5: 0.0020523579900266612, 81 | -4: 0.0051617077404157259, 82 | -3: 0.015049623948731284, 83 | -2: 0.05096973941492914, 84 | -1: 0.18160873382456127, 85 | 0: 0.5, 86 | 1: 0.81839126617543867, 87 | 2: 0.9490302605850709, 88 | 3: 0.98495037605126878, 89 | 4: 0.99483829225958431, 90 | 5: 0.99794764200997332, 91 | 6: 0.99907693085520299, 92 | 7: 0.99954162624280074, 93 | 8: 0.99975354666971372, 94 | 9: 0.9998586600128780}) 95 | } 96 | -------------------------------------------------------------------------------- /storage/appengine/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Go Performance Data Server 5 | 6 | 7 |

Go Performance Data Server

8 |

The Go Performance Data Server allows upload and querying of benchmark results in the standard benchmark data format. It provides a RESTful API to upload benchmark results and query individual results.

9 |

API Documentation

10 | 11 |

POST /upload

12 |

A POST request to this URL with multipart/form-data contents. The form should contain a single field, "file", and the other MIME components are the uploaded files in benchmark format. The request is authenticated with OAuth. Upon success, it will return a JSON body that identifies the uploaded records:

13 | 14 |
15 | {
16 | 	"uploadid": "arbitrary-string",
17 | 	"fileids": [
18 | 		"arbitrary-string-1",
19 | 		"arbitrary-string-2"
20 | 	],
21 | 	"viewurl": "https://foo/bar",
22 | }
23 |     
24 | 25 |

The upload ID may be used in a query as "upload:$uploadid" to find the uploaded data, and each file ID may be used in a query as "upload-part:$fileid". The view URL is optional and if present points to a human-readable page for analysing the uploaded benchmark data.

26 | 27 |

Errors will be returned as HTTP errors with a plain text error message.

28 | 29 |

As a convenience for testing, GET on /upload will render an HTML form that can be used for initiating an upload.

30 | 31 |

GET /search?q=$search

32 |

A GET request to this URL will return a text file with synthesized benchmark results matching the search. The search string contains space-separated "key:value" pairs which limits the results to only records containing those exact fields. Every "key:value" pair is ANDed together, and each value must be matched exactly, with no regexes or substring matches supported. The operators ">" and "<" may be used instead of ":" to perform a range query. Example searches:

33 | 34 |
    35 |
  • by:rsc pkg:compress/flate commit:1234
  • 36 |
  • upload-part:4567
  • 37 |
  • upload:123
  • 38 |
  • commit-time>2016-12-01
  • 39 |
40 | 41 |

GET /uploads?q=$search&extra_label=$label&limit=$limit

42 |

A GET request to this URL returns a list of the most recent $limit uploads that match the search string. If the q parameter is omitted, all uploads will be returned. If the limit parameter is omitted, a server-specified limit is used. If the extra_label parameter is supplied, an arbitrary value for that label will be chosen from the upload's records. (Therefore, this is most useful for labels that do not vary across the upload, such as "by" or "upload-time".)

43 |

The result of this query is streaming JSON (readable using >json.NewDecoder), with one JSON entity per upload:

44 |
45 | {
46 | 	"Count": 10,
47 | 	"UploadID": "arbitrary-string",
48 | 	"LabelValues": {
49 | 		"by": "user@email.com",
50 | 		"upload-time": "2006-01-02T15:04:05Z",
51 | 	}
52 | }
53 |     
54 | 55 | 56 | -------------------------------------------------------------------------------- /internal/stats/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "sort" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | func testDiscreteCDF(t *testing.T, name string, dist DiscreteDist) { 16 | // Build the expected CDF out of the PMF. 17 | l, h := dist.Bounds() 18 | s := dist.Step() 19 | want := map[float64]float64{l - 0.1: 0, h: 1} 20 | sum := 0.0 21 | for x := l; x < h; x += s { 22 | sum += dist.PMF(x) 23 | want[x] = sum 24 | want[x+s/2] = sum 25 | } 26 | 27 | testFunc(t, name, dist.CDF, want) 28 | } 29 | 30 | func testInvCDF(t *testing.T, dist Dist, bounded bool) { 31 | inv := InvCDF(dist) 32 | name := fmt.Sprintf("InvCDF(%+v)", dist) 33 | cdfName := fmt.Sprintf("CDF(%+v)", dist) 34 | 35 | // Test bounds. 36 | vals := map[float64]float64{-0.01: nan, 1.01: nan} 37 | if !bounded { 38 | vals[0] = -inf 39 | vals[1] = inf 40 | } 41 | testFunc(t, name, inv, vals) 42 | 43 | if bounded { 44 | lo, hi := inv(0), inv(1) 45 | vals := map[float64]float64{ 46 | lo - 0.01: 0, lo: 0, 47 | hi: 1, hi + 0.01: 1, 48 | } 49 | testFunc(t, cdfName, dist.CDF, vals) 50 | if got := dist.CDF(lo + 0.01); !(got > 0) { 51 | t.Errorf("%s(0)=%v, but %s(%v)=0", name, lo, cdfName, lo+0.01) 52 | } 53 | if got := dist.CDF(hi - 0.01); !(got < 1) { 54 | t.Errorf("%s(1)=%v, but %s(%v)=1", name, hi, cdfName, hi-0.01) 55 | } 56 | } 57 | 58 | // Test points between. 59 | vals = map[float64]float64{} 60 | for _, p := range vecLinspace(0, 1, 11) { 61 | if p == 0 || p == 1 { 62 | continue 63 | } 64 | x := inv(p) 65 | vals[x] = x 66 | } 67 | testFunc(t, fmt.Sprintf("InvCDF(CDF(%+v))", dist), 68 | func(x float64) float64 { 69 | return inv(dist.CDF(x)) 70 | }, 71 | vals) 72 | } 73 | 74 | // aeq returns true if expect and got are equal to 8 significant 75 | // figures (1 part in 100 million). 76 | func aeq(expect, got float64) bool { 77 | if expect < 0 && got < 0 { 78 | expect, got = -expect, -got 79 | } 80 | return expect*0.99999999 <= got && got*0.99999999 <= expect 81 | } 82 | 83 | func testFunc(t *testing.T, name string, f func(float64) float64, vals map[float64]float64) { 84 | xs := make([]float64, 0, len(vals)) 85 | for x := range vals { 86 | xs = append(xs, x) 87 | } 88 | sort.Float64s(xs) 89 | 90 | for _, x := range xs { 91 | want, got := vals[x], f(x) 92 | if math.IsNaN(want) && math.IsNaN(got) || aeq(want, got) { 93 | continue 94 | } 95 | var label string 96 | if strings.Contains(name, "%v") { 97 | label = fmt.Sprintf(name, x) 98 | } else { 99 | label = fmt.Sprintf("%s(%v)", name, x) 100 | } 101 | t.Errorf("want %s=%v, got %v", label, want, got) 102 | } 103 | } 104 | 105 | // vecLinspace returns num values spaced evenly between lo and hi, 106 | // inclusive. If num is 1, this returns an array consisting of lo. 107 | func vecLinspace(lo, hi float64, num int) []float64 { 108 | res := make([]float64, num) 109 | if num == 1 { 110 | res[0] = lo 111 | return res 112 | } 113 | for i := 0; i < num; i++ { 114 | res[i] = lo + float64(i)*(hi-lo)/float64(num-1) 115 | } 116 | return res 117 | } 118 | -------------------------------------------------------------------------------- /benchproc/keyheader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchproc 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func checkKeyHeader(t *testing.T, tree *KeyHeader, want string) { 14 | t.Helper() 15 | got := renderKeyHeader(tree) 16 | if got != want { 17 | t.Errorf("want %s\ngot %s", want, got) 18 | } 19 | 20 | // Check the structure of the tree. 21 | prevEnd := make([]int, len(tree.Levels)) 22 | var walk func(int, *KeyHeaderNode) 23 | walk = func(level int, n *KeyHeaderNode) { 24 | if n.Field != level { 25 | t.Errorf("want level %d, got %d", level, n.Field) 26 | } 27 | if n.Start != prevEnd[level] { 28 | t.Errorf("want start %d, got %d", prevEnd[level], n.Start) 29 | } 30 | prevEnd[level] = n.Start + n.Len 31 | for _, sub := range n.Children { 32 | walk(level+1, sub) 33 | } 34 | } 35 | for _, n := range tree.Top { 36 | walk(0, n) 37 | } 38 | // Check that we walked the full span of keys on each level. 39 | for level, width := range prevEnd { 40 | if width != len(tree.Keys) { 41 | t.Errorf("want width %d, got %d at level %d", len(tree.Keys), width, level) 42 | } 43 | } 44 | } 45 | 46 | func renderKeyHeader(t *KeyHeader) string { 47 | buf := new(strings.Builder) 48 | var walk func([]*KeyHeaderNode) 49 | walk = func(ns []*KeyHeaderNode) { 50 | for _, n := range ns { 51 | fmt.Fprintf(buf, "\n%s%s:%s", strings.Repeat("\t", n.Field), t.Levels[n.Field], n.Value) 52 | walk(n.Children) 53 | } 54 | } 55 | walk(t.Top) 56 | return buf.String() 57 | } 58 | 59 | func TestKeyHeader(t *testing.T) { 60 | // Test basic merging. 61 | t.Run("basic", func(t *testing.T) { 62 | s, _ := mustParse(t, ".config") 63 | c1 := p(t, s, "", "a", "a1", "b", "b1") 64 | c2 := p(t, s, "", "a", "a1", "b", "b2") 65 | tree := NewKeyHeader([]Key{c1, c2}) 66 | checkKeyHeader(t, tree, ` 67 | a:a1 68 | b:b1 69 | b:b2`) 70 | }) 71 | 72 | // Test that higher level differences prevent lower levels 73 | // from being merged, even if the lower levels match. 74 | t.Run("noMerge", func(t *testing.T) { 75 | s, _ := mustParse(t, ".config") 76 | c1 := p(t, s, "", "a", "a1", "b", "b1") 77 | c2 := p(t, s, "", "a", "a2", "b", "b1") 78 | tree := NewKeyHeader([]Key{c1, c2}) 79 | checkKeyHeader(t, tree, ` 80 | a:a1 81 | b:b1 82 | a:a2 83 | b:b1`) 84 | }) 85 | 86 | // Test mismatched tuple lengths. 87 | t.Run("missingValues", func(t *testing.T) { 88 | s, _ := mustParse(t, ".config") 89 | c1 := p(t, s, "", "a", "a1") 90 | c2 := p(t, s, "", "a", "a1", "b", "b1") 91 | c3 := p(t, s, "", "a", "a1", "b", "b1", "c", "c1") 92 | tree := NewKeyHeader([]Key{c1, c2, c3}) 93 | checkKeyHeader(t, tree, ` 94 | a:a1 95 | b: 96 | c: 97 | b:b1 98 | c: 99 | c:c1`) 100 | }) 101 | 102 | // Test no Keys. 103 | t.Run("none", func(t *testing.T) { 104 | tree := NewKeyHeader([]Key{}) 105 | if len(tree.Top) != 0 { 106 | t.Fatalf("wanted empty tree, got %v", tree) 107 | } 108 | }) 109 | 110 | // Test empty Keys. 111 | t.Run("empty", func(t *testing.T) { 112 | s, _ := mustParse(t, ".config") 113 | c1 := p(t, s, "") 114 | c2 := p(t, s, "") 115 | tree := NewKeyHeader([]Key{c1, c2}) 116 | checkKeyHeader(t, tree, "") 117 | }) 118 | } 119 | -------------------------------------------------------------------------------- /benchfmt/units.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchfmt 6 | 7 | import ( 8 | "golang.org/x/perf/benchmath" 9 | "golang.org/x/perf/benchunit" 10 | ) 11 | 12 | // Unit metadata is a single piece of unit metadata. 13 | // 14 | // Unit metadata gives information that's useful to interpreting 15 | // values in a given unit. The following metadata keys are predefined: 16 | // 17 | // better={higher,lower} indicates whether higher or lower values of 18 | // this unit are better (indicate an improvement). 19 | // 20 | // assume={nothing,exact} indicates what statistical assumption to 21 | // make when considering distributions of values. 22 | // `nothing` means to make no statistical assumptions (e.g., use 23 | // non-parametric methods) and `exact` means to assume measurements are 24 | // exact (repeated measurement does not increase confidence). 25 | // The default is `nothing`. 26 | type UnitMetadata struct { 27 | UnitMetadataKey 28 | 29 | // OrigUnit is the original, untidied unit as written in the input. 30 | OrigUnit string 31 | 32 | Value string 33 | 34 | fileName string 35 | line int 36 | } 37 | 38 | func (u *UnitMetadata) Pos() (fileName string, line int) { 39 | return u.fileName, u.line 40 | } 41 | 42 | // UnitMetadataKey identifies a single piece of unit metadata by unit 43 | // and metadata name. 44 | type UnitMetadataKey struct { 45 | Unit string 46 | Key string // Metadata key (e.g., "better" or "assume") 47 | } 48 | 49 | // UnitMetadataMap stores the accumulated metadata for several units. 50 | // This is indexed by tidied units, while the values store the original 51 | // units from the benchmark file. 52 | type UnitMetadataMap map[UnitMetadataKey]*UnitMetadata 53 | 54 | // Get returns the unit metadata for the specified unit and metadata key. 55 | // It tidies unit if necessary. 56 | func (m UnitMetadataMap) Get(unit, key string) *UnitMetadata { 57 | _, tidyUnit := benchunit.Tidy(1, unit) 58 | return m[UnitMetadataKey{tidyUnit, key}] 59 | } 60 | 61 | // GetAssumption returns the appropriate statistical Assumption to make 62 | // about distributions of values in the given unit. 63 | func (m UnitMetadataMap) GetAssumption(unit string) benchmath.Assumption { 64 | dist := m.Get(unit, "assume") 65 | if dist != nil && dist.Value == "exact" { 66 | return benchmath.AssumeExact 67 | } 68 | // The default is to assume nothing. 69 | return benchmath.AssumeNothing 70 | } 71 | 72 | // GetBetter returns whether higher or lower values of the given unit 73 | // indicate an improvement. It returns +1 if higher values are better, 74 | // -1 if lower values are better, or 0 if unknown. 75 | func (m UnitMetadataMap) GetBetter(unit string) int { 76 | better := m.Get(unit, "better") 77 | if better != nil { 78 | switch better.Value { 79 | case "higher": 80 | return 1 81 | case "lower": 82 | return -1 83 | } 84 | return 0 85 | } 86 | // Fall back to some built-in defaults. 87 | switch unit { 88 | case "ns/op", "sec/op": 89 | // This measures "duration", so lower is better. 90 | return -1 91 | case "MB/s", "B/s": 92 | // This measures "speed", so higher is better. 93 | return 1 94 | case "B/op", "allocs/op": 95 | // These measure amount of allocation, so lower is better. 96 | return -1 97 | } 98 | return 0 99 | } 100 | -------------------------------------------------------------------------------- /cmd/benchstat/internal/texttab/table_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package texttab 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestAlign(t *testing.T) { 13 | check := func(s string, a align, w int, want string) { 14 | t.Helper() 15 | got := a.lpad(s, w) 16 | if got != want { 17 | t.Errorf("want %q, got %q", want, got) 18 | } 19 | } 20 | 21 | check("abc", alignLeft, 10, "abc") 22 | check("abc", alignCenter, 10, " abc") 23 | check("abc", alignCenter, 11, " abc") 24 | check("abc", alignRight, 10, " abc") 25 | check("☃", alignRight, 4, " ☃") 26 | } 27 | 28 | func TestTable(t *testing.T) { 29 | var tab Table 30 | check := func(want string) { 31 | t.Helper() 32 | var gotBuf strings.Builder 33 | tab.Format(&gotBuf) 34 | got := gotBuf.String() 35 | if want != got { 36 | t.Errorf("want:\n%sgot:\n%s", want, got) 37 | } 38 | // Reset tab. 39 | tab = Table{} 40 | } 41 | 42 | // Basic test. 43 | tab.Row().Cell("a").Cell("b").Cell("c") 44 | tab.Row().Cell("d").Cell("e").Cell("f") 45 | check("a b c\nd e f\n") 46 | 47 | // Basic cell padding. Also checks that we don't print 48 | // unnecessary spaces at the ends of lines. 49 | tab.Row().Cell("a").Cell("b").Cell("c") 50 | tab.Row().Cell("long").Cell("e").Cell("long") 51 | check("a b c\nlong e long\n") 52 | 53 | // Cell alignment. 54 | tab.Row().Cell("a", Left).Cell("b", Center).Cell("c", Right) 55 | tab.Row().Cell("xxx").Cell("xxx").Cell("xxx") 56 | check("a b c\nxxx xxx xxx\n") 57 | 58 | // Margins. 59 | tab.Row().Cell("a").Cell("b", LeftMargin(" ")) 60 | tab.Row().Cell("c").Cell("d") 61 | tab.Row().Cell("e").Cell("f", LeftMargin("|")) 62 | check("a b\nc d\ne |f\n") 63 | 64 | // Missing cell in the middle. 65 | tab.Row().Cell("a").Col(2).Cell("c") 66 | tab.Row().Cell("d").Cell("e").Cell("f") 67 | check("a c\nd e f\n") 68 | 69 | // Missing cells at the end. 70 | tab.Row().Cell("a") 71 | tab.Row().Cell("d").Cell("e").Cell("f") 72 | check("a\nd e f\n") 73 | 74 | // Blank rows. 75 | tab.Row().Cell("a") 76 | tab.Row() 77 | tab.Row() 78 | tab.Row().Cell("b") 79 | check("a\n\n\nb\n") 80 | 81 | // Basic spans. 82 | tab.Row().Cell("a").Cell("b") 83 | tab.Row().Span(2, "abc") 84 | check("a b\nabc\n") 85 | 86 | // Spans expanding other cells. 87 | tab.Row().Cell("a").Cell("b") 88 | tab.Row().Span(2, "abcdefg") 89 | check("a b\nabcdefg\n") 90 | 91 | // Other cells expanding spans. 92 | tab.Row().Cell("abc").Cell("def") 93 | tab.Row().Span(2, "a", Right) 94 | check("abc def\n a\n") 95 | 96 | // Some cells are already large enough to complete the span. 97 | tab.Row().Cell("a").Cell("def") 98 | tab.Row().Span(2, "abdef", Right) 99 | check("a def\nabdef\n") 100 | 101 | // Larger cells are sufficient, but smaller cells need to be 102 | // expanded. 103 | tab.Row().Cell("a").Cell("def") 104 | tab.Row().Span(2, "abcdef", Right) 105 | check("a def\nabcdef\n") 106 | 107 | // Spans with margins. 108 | tab.Row().Cell("a").Cell("b", LeftMargin(" ")).Cell("x") 109 | tab.Row().Span(2, "a__b").Cell("x") 110 | check("a b x\na__b x\n") 111 | 112 | // Shrink columns. 113 | tab.Row().Span(2, "abcdef") 114 | tab.Row().Cell("a").Cell("bc") 115 | tab.Row().Cell("x").Cell("y") 116 | tab.SetShrink(1, true) 117 | check("abcdef\na bc\nx y\n") 118 | } 119 | -------------------------------------------------------------------------------- /benchproc/sort_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchproc 6 | 7 | import ( 8 | "math/rand" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | func TestSort(t *testing.T) { 14 | check := func(keys []Key, want ...string) { 15 | SortKeys(keys) 16 | var got []string 17 | for _, key := range keys { 18 | got = append(got, key.String()) 19 | } 20 | if !reflect.DeepEqual(got, want) { 21 | t.Errorf("got %v, want %v", got, want) 22 | } 23 | } 24 | 25 | // Observation order. 26 | s, _ := mustParse(t, "a") 27 | k := []Key{ 28 | p(t, s, "", "a", "1"), 29 | p(t, s, "", "a", "3"), 30 | p(t, s, "", "a", "2"), 31 | } 32 | check(k, "a:1", "a:3", "a:2") 33 | 34 | // Tuple observation order. 35 | s, _ = mustParse(t, "a,b") 36 | // Prepare order. 37 | p(t, s, "", "a", "1") 38 | p(t, s, "", "a", "2") 39 | p(t, s, "", "b", "1") 40 | p(t, s, "", "b", "2") 41 | k = []Key{ 42 | p(t, s, "", "a", "2", "b", "1"), 43 | p(t, s, "", "a", "1", "b", "2"), 44 | } 45 | check(k, "a:1 b:2", "a:2 b:1") 46 | 47 | // Alphabetic 48 | s, _ = mustParse(t, "a@alpha") 49 | k = []Key{ 50 | p(t, s, "", "a", "c"), 51 | p(t, s, "", "a", "b"), 52 | p(t, s, "", "a", "a"), 53 | } 54 | check(k, "a:a", "a:b", "a:c") 55 | 56 | // Numeric. 57 | s, _ = mustParse(t, "a@num") 58 | k = []Key{ 59 | p(t, s, "", "a", "100"), 60 | p(t, s, "", "a", "20"), 61 | p(t, s, "", "a", "3"), 62 | } 63 | check(k, "a:3", "a:20", "a:100") 64 | 65 | // Numeric with strings. 66 | s, _ = mustParse(t, "a@num") 67 | k = []Key{ 68 | p(t, s, "", "a", "b"), 69 | p(t, s, "", "a", "a"), 70 | p(t, s, "", "a", "100"), 71 | p(t, s, "", "a", "20"), 72 | } 73 | check(k, "a:20", "a:100", "a:a", "a:b") 74 | 75 | // Numeric with weird cases. 76 | s, _ = mustParse(t, "a@num") 77 | k = []Key{ 78 | p(t, s, "", "a", "1"), 79 | p(t, s, "", "a", "-inf"), 80 | p(t, s, "", "a", "-infinity"), 81 | p(t, s, "", "a", "inf"), 82 | p(t, s, "", "a", "infinity"), 83 | p(t, s, "", "a", "1.0"), 84 | p(t, s, "", "a", "NaN"), 85 | p(t, s, "", "a", "nan"), 86 | } 87 | // Shuffle the slice to exercise any instabilities. 88 | for try := 0; try < 10; try++ { 89 | for i := 1; i < len(k); i++ { 90 | p := rand.Intn(i) 91 | k[p], k[i] = k[i], k[p] 92 | } 93 | check(k, "a:-inf", "a:-infinity", "a:1", "a:1.0", "a:inf", "a:infinity", "a:NaN", "a:nan") 94 | } 95 | 96 | // Fixed. 97 | s, _ = mustParse(t, "a@(c b a)") 98 | k = []Key{ 99 | p(t, s, "", "a", "a"), 100 | p(t, s, "", "a", "b"), 101 | p(t, s, "", "a", "c"), 102 | } 103 | check(k, "a:c", "a:b", "a:a") 104 | } 105 | 106 | func TestParseNum(t *testing.T) { 107 | check := func(x string, want float64) { 108 | t.Helper() 109 | got, err := parseNum(x) 110 | if err != nil { 111 | t.Errorf("%s: want %v, got error %s", x, want, err) 112 | } else if want != got { 113 | t.Errorf("%s: want %v, got %v", x, want, got) 114 | } 115 | } 116 | 117 | check("1", 1) 118 | check("1B", 1) 119 | check("1b", 1) 120 | check("100.5", 100.5) 121 | check("1k", 1000) 122 | check("1K", 1000) 123 | check("1ki", 1024) 124 | check("1kiB", 1024) 125 | check("1M", 1000000) 126 | check("1Mi", 1<<20) 127 | check("1G", 1000000000) 128 | check("1T", 1000000000000) 129 | check("1P", 1000000000000000) 130 | check("1E", 1000000000000000000) 131 | check("1Z", 1000000000000000000000) 132 | check("1Y", 1000000000000000000000000) 133 | } 134 | -------------------------------------------------------------------------------- /storage/app/upload_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build cgo 6 | 7 | package app 8 | 9 | import ( 10 | "encoding/json" 11 | "fmt" 12 | "io" 13 | "mime/multipart" 14 | "net/http" 15 | "net/http/httptest" 16 | "reflect" 17 | "testing" 18 | "time" 19 | 20 | "golang.org/x/perf/storage/db" 21 | "golang.org/x/perf/storage/db/dbtest" 22 | _ "golang.org/x/perf/storage/db/sqlite3" 23 | "golang.org/x/perf/storage/fs" 24 | ) 25 | 26 | type testApp struct { 27 | db *db.DB 28 | dbCleanup func() 29 | fs *fs.MemFS 30 | app *App 31 | srv *httptest.Server 32 | } 33 | 34 | func (app *testApp) Close() { 35 | app.dbCleanup() 36 | app.srv.Close() 37 | } 38 | 39 | // createTestApp returns a testApp corresponding to a new app 40 | // serving from an in-memory database and file system on an 41 | // isolated test HTTP server. 42 | // 43 | // When finished with app, the caller must call app.Close(). 44 | func createTestApp(t *testing.T) *testApp { 45 | db, cleanup := dbtest.NewDB(t) 46 | 47 | fs := fs.NewMemFS() 48 | 49 | app := &App{ 50 | DB: db, 51 | FS: fs, 52 | Auth: func(http.ResponseWriter, *http.Request) (string, error) { return "user", nil }, 53 | ViewURLBase: "view:", 54 | } 55 | 56 | mux := http.NewServeMux() 57 | app.RegisterOnMux(mux) 58 | 59 | srv := httptest.NewServer(mux) 60 | 61 | return &testApp{db, cleanup, fs, app, srv} 62 | } 63 | 64 | // uploadFiles calls the /upload endpoint and executes f in a new 65 | // goroutine to write files to the POST request. 66 | func (app *testApp) uploadFiles(t *testing.T, f func(*multipart.Writer)) *uploadStatus { 67 | pr, pw := io.Pipe() 68 | mpw := multipart.NewWriter(pw) 69 | 70 | go func() { 71 | defer pw.Close() 72 | defer mpw.Close() 73 | f(mpw) 74 | }() 75 | 76 | resp, err := http.Post(app.srv.URL+"/upload", mpw.FormDataContentType(), pr) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | defer resp.Body.Close() 81 | if resp.StatusCode != 200 { 82 | t.Fatalf("post /upload: %v", resp.Status) 83 | } 84 | body, err := io.ReadAll(resp.Body) 85 | if err != nil { 86 | t.Fatalf("reading /upload response: %v", err) 87 | } 88 | t.Logf("/upload response:\n%s", body) 89 | 90 | status := &uploadStatus{} 91 | if err := json.Unmarshal(body, status); err != nil { 92 | t.Fatalf("unmarshaling /upload response: %v", err) 93 | } 94 | return status 95 | } 96 | 97 | func TestUpload(t *testing.T) { 98 | app := createTestApp(t) 99 | defer app.Close() 100 | 101 | wantID := time.Now().UTC().Format("20060102.") + "1" 102 | 103 | status := app.uploadFiles(t, func(mpw *multipart.Writer) { 104 | w, err := mpw.CreateFormFile("file", "1.txt") 105 | if err != nil { 106 | t.Errorf("CreateFormFile: %v", err) 107 | } 108 | fmt.Fprintf(w, "key: value\nBenchmarkOne 5 ns/op\nkey:value2\nBenchmarkTwo 10 ns/op\n") 109 | }) 110 | 111 | if status.UploadID != wantID { 112 | t.Errorf("uploadid = %q, want %q", status.UploadID, wantID) 113 | } 114 | if have, want := status.FileIDs, []string{wantID + "/0"}; !reflect.DeepEqual(have, want) { 115 | t.Errorf("fileids = %v, want %v", have, want) 116 | } 117 | if want := "view:" + wantID; status.ViewURL != want { 118 | t.Errorf("viewurl = %q, want %q", status.ViewURL, want) 119 | } 120 | 121 | if len(app.fs.Files()) != 1 { 122 | t.Errorf("/upload wrote %d files, want 1", len(app.fs.Files())) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /benchfmt/files_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchfmt 6 | 7 | import ( 8 | "errors" 9 | "io/fs" 10 | "os" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | func TestFiles(t *testing.T) { 16 | // Switch to testdata/files directory. 17 | oldDir, err := os.Getwd() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | defer os.Chdir(oldDir) 22 | if err := os.Chdir("testdata/files"); err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | check := func(f *Files, want ...string) { 27 | t.Helper() 28 | for f.Scan() { 29 | switch res := f.Result(); res := res.(type) { 30 | default: 31 | t.Fatalf("unexpected result type %T", res) 32 | case *SyntaxError: 33 | t.Fatalf("unexpected Result error %s", res) 34 | return 35 | case *Result: 36 | if len(want) == 0 { 37 | t.Errorf("got result, want end of stream") 38 | return 39 | } 40 | got := res.GetConfig(".file") + " " + string(res.Name.Full()) 41 | if got != want[0] { 42 | t.Errorf("got %q, want %q", got, want[0]) 43 | } 44 | want = want[1:] 45 | } 46 | } 47 | 48 | err := f.Err() 49 | noent := errors.Is(err, fs.ErrNotExist) 50 | wantNoent := len(want) == 1 && strings.HasPrefix(want[0], "ErrNotExist") 51 | if wantNoent { 52 | want = want[1:] 53 | } 54 | if err != nil && !noent { 55 | t.Errorf("got unexpected error %s", err) 56 | } else if noent && !wantNoent { 57 | t.Errorf("got %s, want success", err) 58 | } else if !noent && wantNoent { 59 | t.Errorf("got success, want ErrNotExist") 60 | } 61 | 62 | if len(want) != 0 { 63 | t.Errorf("got end of stream, want %v", want) 64 | } 65 | } 66 | 67 | // Basic tests. 68 | check( 69 | &Files{Paths: []string{"a", "b"}}, 70 | "a X", "a Y", "b Z", 71 | ) 72 | check( 73 | &Files{Paths: []string{"a", "b", "c", "d"}}, 74 | "a X", "a Y", "b Z", "ErrNotExist", 75 | ) 76 | 77 | // Ambiguous paths. 78 | check( 79 | &Files{Paths: []string{"a", "b", "a"}}, 80 | "a#0 X", "a#0 Y", "b Z", "a#1 X", "a#1 Y", 81 | ) 82 | 83 | // AllowStdin. 84 | check( 85 | &Files{Paths: []string{"-"}}, 86 | "ErrNotExist", 87 | ) 88 | fakeStdin("BenchmarkIn 1 1 ns/op\n", func() { 89 | check( 90 | &Files{ 91 | Paths: []string{"-"}, 92 | AllowStdin: true, 93 | }, 94 | "- In", 95 | ) 96 | }) 97 | 98 | // Labels. 99 | check( 100 | &Files{ 101 | Paths: []string{"a", "b"}, 102 | AllowLabels: true, 103 | }, 104 | "a X", "a Y", "b Z", 105 | ) 106 | check( 107 | &Files{ 108 | Paths: []string{"foo=a", "b"}, 109 | AllowLabels: true, 110 | }, 111 | "foo X", "foo Y", "b Z", 112 | ) 113 | fakeStdin("BenchmarkIn 1 1 ns/op\n", func() { 114 | check( 115 | &Files{ 116 | Paths: []string{"foo=-"}, 117 | AllowStdin: true, 118 | AllowLabels: true, 119 | }, 120 | "foo In", 121 | ) 122 | }) 123 | 124 | // Ambiguous labels don't get disambiguated. 125 | check( 126 | &Files{ 127 | Paths: []string{"foo=a", "foo=a"}, 128 | AllowLabels: true, 129 | }, 130 | "foo X", "foo Y", "foo X", "foo Y", 131 | ) 132 | } 133 | 134 | func fakeStdin(content string, cb func()) { 135 | r, w, err := os.Pipe() 136 | if err != nil { 137 | panic(err) 138 | } 139 | go func() { 140 | defer w.Close() 141 | w.WriteString(content) 142 | }() 143 | defer r.Close() 144 | defer func(orig *os.File) { os.Stdin = orig }(os.Stdin) 145 | os.Stdin = r 146 | cb() 147 | } 148 | -------------------------------------------------------------------------------- /benchstat/sort_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchstat 6 | 7 | import ( 8 | "log" 9 | "os" 10 | "runtime" 11 | "sort" 12 | "testing" 13 | ) 14 | 15 | var file1 = "../cmd/benchstat/testdata/old.txt" 16 | var file2 = "../cmd/benchstat/testdata/new.txt" 17 | 18 | func extractRowBenchmark(row *Row) string { 19 | return row.Benchmark 20 | } 21 | func extractRowDelta(row *Row) float64 { 22 | return row.PctDelta 23 | } 24 | func extractRowChange(row *Row) int { 25 | return row.Change 26 | } 27 | 28 | func benchmarkSortTest(t *testing.T, sampleTable *Table) { 29 | numRows := len(sampleTable.Rows) 30 | benchmarks := make([]string, numRows) 31 | Sort(sampleTable, ByName) 32 | for idx, row := range sampleTable.Rows { 33 | benchmarks[idx] = extractRowBenchmark(row) 34 | } 35 | t.Run("BenchSorted", func(t *testing.T) { 36 | if !sort.StringsAreSorted(benchmarks) { 37 | t.Error("Table not sorted by names") 38 | } 39 | }) 40 | Sort(sampleTable, Reverse(ByName)) 41 | for idx, row := range sampleTable.Rows { 42 | benchmarks[numRows-idx-1] = extractRowBenchmark(row) 43 | } 44 | t.Run("BenchReversed", func(t *testing.T) { 45 | if !sort.StringsAreSorted(benchmarks) { 46 | t.Error("Table not reverse sorted by benchmarks") 47 | } 48 | }) 49 | } 50 | 51 | func deltaSortTest(t *testing.T, sampleTable *Table) { 52 | numRows := len(sampleTable.Rows) 53 | deltas := make([]float64, numRows) 54 | Sort(sampleTable, ByDelta) 55 | for idx, row := range sampleTable.Rows { 56 | deltas[idx] = -extractRowDelta(row) 57 | } 58 | t.Run("DeltaSorted", func(t *testing.T) { 59 | if !sort.Float64sAreSorted(deltas) { 60 | t.Errorf("Table not sorted by deltas: %v", deltas) 61 | } 62 | }) 63 | Sort(sampleTable, Reverse(ByDelta)) 64 | for idx, row := range sampleTable.Rows { 65 | deltas[idx] = extractRowDelta(row) 66 | } 67 | t.Run("DeltaReversed", func(t *testing.T) { 68 | if !sort.Float64sAreSorted(deltas) { 69 | t.Error("Table not reverse sorted by deltas") 70 | } 71 | }) 72 | } 73 | 74 | func TestCompareCollection(t *testing.T) { 75 | if runtime.GOOS == "android" { 76 | t.Skipf("files not available on GOOS=%s", runtime.GOOS) 77 | } 78 | sampleCompareCollection := Collection{Alpha: 0.05, AddGeoMean: false, DeltaTest: UTest} 79 | file1Data, err := os.ReadFile(file1) 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | file2Data, err := os.ReadFile(file2) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | sampleCompareCollection.AddConfig(file1, file1Data) 88 | sampleCompareCollection.AddConfig(file2, file2Data) 89 | // data has both time and speed tables, test only the speed table 90 | sampleTable := sampleCompareCollection.Tables()[0] 91 | t.Run("BenchmarkSort", func(t *testing.T) { 92 | benchmarkSortTest(t, sampleTable) 93 | }) 94 | t.Run("DeltaSort", func(t *testing.T) { 95 | deltaSortTest(t, sampleTable) 96 | }) 97 | } 98 | 99 | func TestSingleCollection(t *testing.T) { 100 | if runtime.GOOS == "android" { 101 | t.Skipf("files not available on GOOS=%s", runtime.GOOS) 102 | } 103 | sampleCollection := Collection{Alpha: 0.05, AddGeoMean: false, DeltaTest: UTest} 104 | file1Data, err1 := os.ReadFile(file1) 105 | if err1 != nil { 106 | log.Fatal(err1) 107 | } 108 | sampleCollection.AddConfig(file1, file1Data) 109 | sampleTable := sampleCollection.Tables()[0] 110 | t.Run("BenchmarkSort", func(t *testing.T) { 111 | benchmarkSortTest(t, sampleTable) 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /benchproc/keyheader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchproc 6 | 7 | // A KeyHeader represents a slice of Keys, combined into a forest of 8 | // trees by common Field values. This is intended to visually present a 9 | // sequence of Keys in a compact form; primarily, as a header on a 10 | // table. 11 | // 12 | // All Keys must have the same Projection. The levels of the tree 13 | // correspond to the Fields of the Projection, and the depth of the tree 14 | // is exactly the number of Fields. A node at level i represents a 15 | // subslice of the Keys that all have the same values as each other for 16 | // fields 0 through i. 17 | // 18 | // For example, given four keys: 19 | // 20 | // K[0] = a:1 b:1 c:1 21 | // K[1] = a:1 b:1 c:2 22 | // K[2] = a:2 b:2 c:2 23 | // K[3] = a:2 b:3 c:3 24 | // 25 | // the KeyHeader is as follows: 26 | // 27 | // Level 0 a:1 a:2 28 | // | / \ 29 | // Level 1 b:1 b:2 b:3 30 | // / \ | | 31 | // Level 2 c:1 c:2 c:2 c:3 32 | // K[0] K[1] K[2] K[3] 33 | type KeyHeader struct { 34 | // Keys is the sequence of keys at the leaf level of this tree. 35 | // Each level subdivides this sequence. 36 | Keys []Key 37 | 38 | // Levels are the labels for each level of the tree. Level i of the 39 | // tree corresponds to the i'th field of all Keys. 40 | Levels []*Field 41 | 42 | // Top is the list of tree roots. These nodes are all at level 0. 43 | Top []*KeyHeaderNode 44 | } 45 | 46 | // KeyHeaderNode is a node in a KeyHeader. 47 | type KeyHeaderNode struct { 48 | // Field is the index into KeyHeader.Levels of the Field represented 49 | // by this node. This is also the level of this node in the tree, 50 | // starting with 0 at the top. 51 | Field int 52 | 53 | // Value is the value that all Keys have in common for Field. 54 | Value string 55 | 56 | // Start is the index of the first Key covered by this node. 57 | Start int 58 | // Len is the number of Keys in the sequence represented by this node. 59 | // This is also the number of leaf nodes in the subtree at this node. 60 | Len int 61 | 62 | // Children are the children of this node. These are all at level Field+1. 63 | // Child i+1 differs from child i in the value of field Field+1. 64 | Children []*KeyHeaderNode 65 | } 66 | 67 | // NewKeyHeader computes the KeyHeader of a slice of Keys by combining 68 | // common prefixes of fields. 69 | func NewKeyHeader(keys []Key) *KeyHeader { 70 | if len(keys) == 0 { 71 | return &KeyHeader{} 72 | } 73 | 74 | fields := commonProjection(keys).FlattenedFields() 75 | 76 | var walk func(parent *KeyHeaderNode) 77 | walk = func(parent *KeyHeaderNode) { 78 | level := parent.Field + 1 79 | if level == len(fields) { 80 | return 81 | } 82 | field := fields[level] 83 | var node *KeyHeaderNode 84 | for j, key := range keys[parent.Start : parent.Start+parent.Len] { 85 | val := key.Get(field) 86 | if node != nil && val == node.Value { 87 | // Add this key to the current node. 88 | node.Len++ 89 | } else { 90 | // Start a new node. 91 | node = &KeyHeaderNode{level, val, parent.Start + j, 1, nil} 92 | parent.Children = append(parent.Children, node) 93 | } 94 | } 95 | for _, child := range parent.Children { 96 | walk(child) 97 | } 98 | } 99 | root := &KeyHeaderNode{-1, "", 0, len(keys), nil} 100 | walk(root) 101 | return &KeyHeader{keys, fields, root.Children} 102 | } 103 | -------------------------------------------------------------------------------- /benchproc/key.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchproc 6 | 7 | import "strings" 8 | 9 | // A Key is an immutable tuple mapping from Fields to strings whose 10 | // structure is given by a Projection. Two Keys are == if they come 11 | // from the same Projection and have identical values. 12 | type Key struct { 13 | k *keyNode 14 | } 15 | 16 | // IsZero reports whether k is a zeroed Key with no projection and no fields. 17 | func (k Key) IsZero() bool { 18 | return k.k == nil 19 | } 20 | 21 | // Get returns the value of Field f in this Key. 22 | // 23 | // It panics if Field f does not come from the same Projection as the 24 | // Key or if f is a tuple Field. 25 | func (k Key) Get(f *Field) string { 26 | if k.IsZero() { 27 | panic("zero Key has no fields") 28 | } 29 | if k.k.proj != f.proj { 30 | panic("Key and Field have different Projections") 31 | } 32 | if f.IsTuple { 33 | panic(f.Name + " is a tuple field") 34 | } 35 | idx := f.idx 36 | if idx >= len(k.k.vals) { 37 | return "" 38 | } 39 | return k.k.vals[idx] 40 | } 41 | 42 | // Projection returns the Projection describing Key k. 43 | func (k Key) Projection() *Projection { 44 | if k.IsZero() { 45 | return nil 46 | } 47 | return k.k.proj 48 | } 49 | 50 | // String returns Key as a space-separated sequence of key:value 51 | // pairs in field order. 52 | func (k Key) String() string { 53 | return k.string(true) 54 | } 55 | 56 | // StringValues returns Key as a space-separated sequences of 57 | // values in field order. 58 | func (k Key) StringValues() string { 59 | return k.string(false) 60 | } 61 | 62 | func (k Key) string(keys bool) string { 63 | if k.IsZero() { 64 | return "" 65 | } 66 | buf := new(strings.Builder) 67 | for _, field := range k.k.proj.FlattenedFields() { 68 | if field.idx >= len(k.k.vals) { 69 | continue 70 | } 71 | val := k.k.vals[field.idx] 72 | if val == "" { 73 | continue 74 | } 75 | if buf.Len() > 0 { 76 | buf.WriteByte(' ') 77 | } 78 | if keys { 79 | buf.WriteString(field.Name) 80 | buf.WriteByte(':') 81 | } 82 | buf.WriteString(val) 83 | } 84 | return buf.String() 85 | } 86 | 87 | // commonProjection returns the Projection that all Keys have, or panics if any 88 | // Key has a different Projection. It returns nil if len(keys) == 0. 89 | func commonProjection(keys []Key) *Projection { 90 | if len(keys) == 0 { 91 | return nil 92 | } 93 | s := keys[0].Projection() 94 | for _, k := range keys[1:] { 95 | if k.Projection() != s { 96 | panic("Keys must all have the same Projection") 97 | } 98 | } 99 | return s 100 | } 101 | 102 | // keyNode is the internal heap-allocated object backing a Key. 103 | // This allows Key itself to be a value type whose equality is 104 | // determined by the pointer equality of the underlying keyNode. 105 | type keyNode struct { 106 | proj *Projection 107 | // vals are the values in this Key, indexed by fieldInternal.idx. Trailing 108 | // ""s are always trimmed. 109 | // 110 | // Notably, this is *not* in the order of the flattened schema. This is 111 | // because fields can be added in the middle of a schema on-the-fly, and we 112 | // need to not invalidate existing Keys. 113 | vals []string 114 | } 115 | 116 | func (n *keyNode) equalRow(row []string) bool { 117 | if len(n.vals) != len(row) { 118 | return false 119 | } 120 | for i, v := range n.vals { 121 | if row[i] != v { 122 | return false 123 | } 124 | } 125 | return true 126 | } 127 | -------------------------------------------------------------------------------- /benchstat/scaler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchstat 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | // A Scaler is a function that scales and formats a measurement. 13 | // All measurements within a given table row are formatted 14 | // using the same scaler, so that the units are consistent 15 | // across the row. 16 | type Scaler func(float64) string 17 | 18 | // NewScaler returns a Scaler appropriate for formatting 19 | // the measurement val, which has the given unit. 20 | func NewScaler(val float64, unit string) Scaler { 21 | if hasBaseUnit(unit, "ns/op") || hasBaseUnit(unit, "ns/GC") { 22 | return timeScaler(val) 23 | } 24 | 25 | var format string 26 | var scale float64 27 | var suffix string 28 | 29 | prescale := 1.0 30 | if hasBaseUnit(unit, "MB/s") { 31 | prescale = 1e6 32 | } 33 | 34 | switch x := val * prescale; { 35 | case x >= 99500000000000: 36 | format, scale, suffix = "%.0f", 1e12, "T" 37 | case x >= 9950000000000: 38 | format, scale, suffix = "%.1f", 1e12, "T" 39 | case x >= 995000000000: 40 | format, scale, suffix = "%.2f", 1e12, "T" 41 | case x >= 99500000000: 42 | format, scale, suffix = "%.0f", 1e9, "G" 43 | case x >= 9950000000: 44 | format, scale, suffix = "%.1f", 1e9, "G" 45 | case x >= 995000000: 46 | format, scale, suffix = "%.2f", 1e9, "G" 47 | case x >= 99500000: 48 | format, scale, suffix = "%.0f", 1e6, "M" 49 | case x >= 9950000: 50 | format, scale, suffix = "%.1f", 1e6, "M" 51 | case x >= 995000: 52 | format, scale, suffix = "%.2f", 1e6, "M" 53 | case x >= 99500: 54 | format, scale, suffix = "%.0f", 1e3, "k" 55 | case x >= 9950: 56 | format, scale, suffix = "%.1f", 1e3, "k" 57 | case x >= 995: 58 | format, scale, suffix = "%.2f", 1e3, "k" 59 | case x >= 99.5: 60 | format, scale, suffix = "%.0f", 1, "" 61 | case x >= 9.95: 62 | format, scale, suffix = "%.1f", 1, "" 63 | default: 64 | format, scale, suffix = "%.2f", 1, "" 65 | } 66 | 67 | if hasBaseUnit(unit, "B/op") || hasBaseUnit(unit, "bytes/op") || hasBaseUnit(unit, "bytes") { 68 | suffix += "B" 69 | } 70 | if hasBaseUnit(unit, "MB/s") { 71 | suffix += "B/s" 72 | } 73 | scale /= prescale 74 | 75 | return func(val float64) string { 76 | return fmt.Sprintf(format+suffix, val/scale) 77 | } 78 | } 79 | 80 | func timeScaler(ns float64) Scaler { 81 | var format string 82 | var scale float64 83 | switch x := ns / 1e9; { 84 | case x >= 99.5: 85 | format, scale = "%.0fs", 1 86 | case x >= 9.95: 87 | format, scale = "%.1fs", 1 88 | case x >= 0.995: 89 | format, scale = "%.2fs", 1 90 | case x >= 0.0995: 91 | format, scale = "%.0fms", 1000 92 | case x >= 0.00995: 93 | format, scale = "%.1fms", 1000 94 | case x >= 0.000995: 95 | format, scale = "%.2fms", 1000 96 | case x >= 0.0000995: 97 | format, scale = "%.0fµs", 1000*1000 98 | case x >= 0.00000995: 99 | format, scale = "%.1fµs", 1000*1000 100 | case x >= 0.000000995: 101 | format, scale = "%.2fµs", 1000*1000 102 | case x >= 0.0000000995: 103 | format, scale = "%.0fns", 1000*1000*1000 104 | case x >= 0.00000000995: 105 | format, scale = "%.1fns", 1000*1000*1000 106 | default: 107 | format, scale = "%.2fns", 1000*1000*1000 108 | } 109 | return func(ns float64) string { 110 | return fmt.Sprintf(format, ns/1e9*scale) 111 | } 112 | } 113 | 114 | // hasBaseUnit reports whether s has unit unit. 115 | // For now, it reports whether s == unit or s ends in -unit. 116 | func hasBaseUnit(s, unit string) bool { 117 | return s == unit || strings.HasSuffix(s, "-"+unit) 118 | } 119 | -------------------------------------------------------------------------------- /benchfmt/testdata/bent/20200101T024818.BaseNl.build: -------------------------------------------------------------------------------- 1 | goos: linux 2 | goarch: amd64 3 | BenchmarkUber_zap 1 6910000000 build-real-ns/op 24230000000 build-user-ns/op 2630000000 build-sys-ns/op 4 | BenchmarkRcrowley_metrics 1 2780000000 build-real-ns/op 11810000000 build-user-ns/op 1190000000 build-sys-ns/op 5 | BenchmarkGonum_topo 1 4140000000 build-real-ns/op 17770000000 build-user-ns/op 1380000000 build-sys-ns/op 6 | BenchmarkKanzi 1 2060000000 build-real-ns/op 9880000000 build-user-ns/op 790000000 build-sys-ns/op 7 | BenchmarkCespare_mph 1 1850000000 build-real-ns/op 8330000000 build-user-ns/op 670000000 build-sys-ns/op 8 | BenchmarkGonum_mat 1 4420000000 build-real-ns/op 17680000000 build-user-ns/op 1360000000 build-sys-ns/op 9 | BenchmarkGonum_community 1 4210000000 build-real-ns/op 17300000000 build-user-ns/op 1450000000 build-sys-ns/op 10 | BenchmarkGonum_lapack_native 1 4450000000 build-real-ns/op 16530000000 build-user-ns/op 1140000000 build-sys-ns/op 11 | BenchmarkCespare_xxhash 1 1880000000 build-real-ns/op 8340000000 build-user-ns/op 750000000 build-sys-ns/op 12 | BenchmarkSemver 1 1980000000 build-real-ns/op 9140000000 build-user-ns/op 850000000 build-sys-ns/op 13 | BenchmarkMinio 1 51870000000 build-real-ns/op 145400000000 build-user-ns/op 11270000000 build-sys-ns/op 14 | BenchmarkNelsam_gxui_interval 1 1890000000 build-real-ns/op 8550000000 build-user-ns/op 700000000 build-sys-ns/op 15 | BenchmarkGtank_blake2s 1 1920000000 build-real-ns/op 9060000000 build-user-ns/op 700000000 build-sys-ns/op 16 | BenchmarkCapnproto2 1 5270000000 build-real-ns/op 17280000000 build-user-ns/op 1530000000 build-sys-ns/op 17 | BenchmarkAjstarks_deck_generate 1 1900000000 build-real-ns/op 8900000000 build-user-ns/op 860000000 build-sys-ns/op 18 | BenchmarkEriclagergren_decimal 1 3100000000 build-real-ns/op 11530000000 build-user-ns/op 1050000000 build-sys-ns/op 19 | BenchmarkEthereum_core 1 14100000000 build-real-ns/op 41700000000 build-user-ns/op 4040000000 build-sys-ns/op 20 | BenchmarkHugo_helpers 1 13280000000 build-real-ns/op 60610000000 build-user-ns/op 4500000000 build-sys-ns/op 21 | BenchmarkBindata 1 2230000000 build-real-ns/op 11030000000 build-user-ns/op 840000000 build-sys-ns/op 22 | BenchmarkEthereum_trie 1 12100000000 build-real-ns/op 27870000000 build-user-ns/op 2640000000 build-sys-ns/op 23 | BenchmarkGonum_path 1 4140000000 build-real-ns/op 17390000000 build-user-ns/op 1380000000 build-sys-ns/op 24 | BenchmarkEthereum_corevm 1 12480000000 build-real-ns/op 29960000000 build-user-ns/op 3060000000 build-sys-ns/op 25 | BenchmarkEthereum_storage 1 14300000000 build-real-ns/op 36700000000 build-user-ns/op 3400000000 build-sys-ns/op 26 | BenchmarkK8s_api 1 18130000000 build-real-ns/op 88380000000 build-user-ns/op 6760000000 build-sys-ns/op 27 | BenchmarkBenhoyt_goawk 1 2070000000 build-real-ns/op 9220000000 build-user-ns/op 930000000 build-sys-ns/op 28 | BenchmarkSpexs2 1 2560000000 build-real-ns/op 10040000000 build-user-ns/op 920000000 build-sys-ns/op 29 | BenchmarkCommonmark_markdown 1 8970000000 build-real-ns/op 18350000000 build-user-ns/op 1190000000 build-sys-ns/op 30 | BenchmarkDustin_humanize 1 2050000000 build-real-ns/op 9470000000 build-user-ns/op 780000000 build-sys-ns/op 31 | BenchmarkGonum_traverse 1 3970000000 build-real-ns/op 16290000000 build-user-ns/op 1400000000 build-sys-ns/op 32 | BenchmarkEthereum_bitutil 1 3540000000 build-real-ns/op 10310000000 build-user-ns/op 930000000 build-sys-ns/op 33 | BenchmarkDustin_broadcast 1 1860000000 build-real-ns/op 8370000000 build-user-ns/op 700000000 build-sys-ns/op 34 | BenchmarkGonum_blas_native 1 3650000000 build-real-ns/op 12180000000 build-user-ns/op 1000000000 build-sys-ns/op 35 | BenchmarkEthereum_ethash 1 12460000000 build-real-ns/op 35230000000 build-user-ns/op 3230000000 build-sys-ns/op 36 | BenchmarkK8s_schedulercache 1 18450000000 build-real-ns/op 91710000000 build-user-ns/op 6300000000 build-sys-ns/op 37 | -------------------------------------------------------------------------------- /benchfmt/testdata/bent/20200101T024818.TipNl.build: -------------------------------------------------------------------------------- 1 | goos: linux 2 | goarch: amd64 3 | BenchmarkUber_zap 1 6730000000 build-real-ns/op 24390000000 build-user-ns/op 2640000000 build-sys-ns/op 4 | BenchmarkHugo_helpers 1 13040000000 build-real-ns/op 59300000000 build-user-ns/op 4730000000 build-sys-ns/op 5 | BenchmarkBindata 1 2280000000 build-real-ns/op 11290000000 build-user-ns/op 920000000 build-sys-ns/op 6 | BenchmarkKanzi 1 2130000000 build-real-ns/op 9990000000 build-user-ns/op 910000000 build-sys-ns/op 7 | BenchmarkEthereum_core 1 14010000000 build-real-ns/op 41400000000 build-user-ns/op 3760000000 build-sys-ns/op 8 | BenchmarkNelsam_gxui_interval 1 1980000000 build-real-ns/op 8630000000 build-user-ns/op 730000000 build-sys-ns/op 9 | BenchmarkEthereum_storage 1 14240000000 build-real-ns/op 36520000000 build-user-ns/op 3470000000 build-sys-ns/op 10 | BenchmarkGonum_community 1 4190000000 build-real-ns/op 17290000000 build-user-ns/op 1610000000 build-sys-ns/op 11 | BenchmarkEriclagergren_decimal 1 3120000000 build-real-ns/op 11560000000 build-user-ns/op 1040000000 build-sys-ns/op 12 | BenchmarkK8s_schedulercache 1 17710000000 build-real-ns/op 86060000000 build-user-ns/op 6110000000 build-sys-ns/op 13 | BenchmarkGonum_traverse 1 3970000000 build-real-ns/op 16710000000 build-user-ns/op 1190000000 build-sys-ns/op 14 | BenchmarkGonum_lapack_native 1 4450000000 build-real-ns/op 16560000000 build-user-ns/op 1220000000 build-sys-ns/op 15 | BenchmarkGtank_blake2s 1 1970000000 build-real-ns/op 9160000000 build-user-ns/op 730000000 build-sys-ns/op 16 | BenchmarkBenhoyt_goawk 1 2130000000 build-real-ns/op 9670000000 build-user-ns/op 790000000 build-sys-ns/op 17 | BenchmarkSemver 1 2029999999 build-real-ns/op 9320000000 build-user-ns/op 760000000 build-sys-ns/op 18 | BenchmarkCommonmark_markdown 1 8450000000 build-real-ns/op 17470000000 build-user-ns/op 1240000000 build-sys-ns/op 19 | BenchmarkGonum_topo 1 4160000000 build-real-ns/op 17890000000 build-user-ns/op 1420000000 build-sys-ns/op 20 | BenchmarkEthereum_trie 1 11870000000 build-real-ns/op 27860000000 build-user-ns/op 2760000000 build-sys-ns/op 21 | BenchmarkGonum_blas_native 1 3710000000 build-real-ns/op 12300000000 build-user-ns/op 920000000 build-sys-ns/op 22 | BenchmarkMinio 1 52660000000 build-real-ns/op 143750000000 build-user-ns/op 11090000000 build-sys-ns/op 23 | BenchmarkDustin_humanize 1 2040000000 build-real-ns/op 9520000000 build-user-ns/op 750000000 build-sys-ns/op 24 | BenchmarkCapnproto2 1 5770000000 build-real-ns/op 18420000000 build-user-ns/op 1540000000 build-sys-ns/op 25 | BenchmarkRcrowley_metrics 1 2800000000 build-real-ns/op 11940000000 build-user-ns/op 1210000000 build-sys-ns/op 26 | BenchmarkEthereum_corevm 1 12400000000 build-real-ns/op 30680000000 build-user-ns/op 2790000000 build-sys-ns/op 27 | BenchmarkCespare_xxhash 1 1930000000 build-real-ns/op 8540000000 build-user-ns/op 690000000 build-sys-ns/op 28 | BenchmarkEthereum_ethash 1 12860000000 build-real-ns/op 35360000000 build-user-ns/op 3180000000 build-sys-ns/op 29 | BenchmarkCespare_mph 1 1910000000 build-real-ns/op 8640000000 build-user-ns/op 640000000 build-sys-ns/op 30 | BenchmarkGonum_path 1 4180000000 build-real-ns/op 17240000000 build-user-ns/op 1340000000 build-sys-ns/op 31 | BenchmarkSpexs2 1 2630000000 build-real-ns/op 10160000000 build-user-ns/op 960000000 build-sys-ns/op 32 | BenchmarkDustin_broadcast 1 1900000000 build-real-ns/op 8490000000 build-user-ns/op 680000000 build-sys-ns/op 33 | BenchmarkK8s_api 1 17470000000 build-real-ns/op 86820000000 build-user-ns/op 6380000000 build-sys-ns/op 34 | BenchmarkAjstarks_deck_generate 1 1960000000 build-real-ns/op 9160000000 build-user-ns/op 680000000 build-sys-ns/op 35 | BenchmarkEthereum_bitutil 1 3640000000 build-real-ns/op 10450000000 build-user-ns/op 990000000 build-sys-ns/op 36 | BenchmarkGonum_mat 1 4380000000 build-real-ns/op 17770000000 build-user-ns/op 1240000000 build-sys-ns/op 37 | -------------------------------------------------------------------------------- /benchfmt/testdata/bent/20200101T030626.Tipl.build: -------------------------------------------------------------------------------- 1 | goos: linux 2 | goarch: amd64 3 | BenchmarkBenhoyt_goawk 1 2370000000 build-real-ns/op 9440000000 build-user-ns/op 1060000000 build-sys-ns/op 4 | BenchmarkK8s_schedulercache 1 17730000000 build-real-ns/op 86260000000 build-user-ns/op 6140000000 build-sys-ns/op 5 | BenchmarkDustin_broadcast 1 1910000000 build-real-ns/op 8560000000 build-user-ns/op 750000000 build-sys-ns/op 6 | BenchmarkAjstarks_deck_generate 1 1970000000 build-real-ns/op 9070000000 build-user-ns/op 750000000 build-sys-ns/op 7 | BenchmarkEthereum_storage 1 14160000000 build-real-ns/op 36540000000 build-user-ns/op 3280000000 build-sys-ns/op 8 | BenchmarkGonum_path 1 4130000000 build-real-ns/op 17540000000 build-user-ns/op 1230000000 build-sys-ns/op 9 | BenchmarkBindata 1 2230000000 build-real-ns/op 10990000000 build-user-ns/op 970000000 build-sys-ns/op 10 | BenchmarkK8s_api 1 17560000000 build-real-ns/op 86510000000 build-user-ns/op 6080000000 build-sys-ns/op 11 | BenchmarkGonum_blas_native 1 3720000000 build-real-ns/op 12440000000 build-user-ns/op 970000000 build-sys-ns/op 12 | BenchmarkGtank_blake2s 1 1960000000 build-real-ns/op 9130000000 build-user-ns/op 690000000 build-sys-ns/op 13 | BenchmarkEthereum_trie 1 12050000000 build-real-ns/op 28070000000 build-user-ns/op 2680000000 build-sys-ns/op 14 | BenchmarkEthereum_ethash 1 12870000000 build-real-ns/op 35220000000 build-user-ns/op 3250000000 build-sys-ns/op 15 | BenchmarkUber_zap 1 6460000000 build-real-ns/op 24450000000 build-user-ns/op 2320000000 build-sys-ns/op 16 | BenchmarkEthereum_bitutil 1 3530000000 build-real-ns/op 10320000000 build-user-ns/op 930000000 build-sys-ns/op 17 | BenchmarkSemver 1 2020000000 build-real-ns/op 9360000000 build-user-ns/op 640000000 build-sys-ns/op 18 | BenchmarkRcrowley_metrics 1 2800000000 build-real-ns/op 12090000000 build-user-ns/op 1070000000 build-sys-ns/op 19 | BenchmarkEriclagergren_decimal 1 3110000000 build-real-ns/op 11720000000 build-user-ns/op 970000000 build-sys-ns/op 20 | BenchmarkGonum_traverse 1 3900000000 build-real-ns/op 16480000000 build-user-ns/op 1280000000 build-sys-ns/op 21 | BenchmarkCommonmark_markdown 1 8420000000 build-real-ns/op 17540000000 build-user-ns/op 1180000000 build-sys-ns/op 22 | BenchmarkNelsam_gxui_interval 1 1960000000 build-real-ns/op 8700000000 build-user-ns/op 730000000 build-sys-ns/op 23 | BenchmarkGonum_mat 1 4330000000 build-real-ns/op 17930000000 build-user-ns/op 1180000000 build-sys-ns/op 24 | BenchmarkSpexs2 1 2640000000 build-real-ns/op 10070000000 build-user-ns/op 890000000 build-sys-ns/op 25 | BenchmarkDustin_humanize 1 2070000000 build-real-ns/op 9530000000 build-user-ns/op 710000000 build-sys-ns/op 26 | BenchmarkCapnproto2 1 5190000000 build-real-ns/op 17290000000 build-user-ns/op 1560000000 build-sys-ns/op 27 | BenchmarkMinio 1 50670000000 build-real-ns/op 143930000000 build-user-ns/op 10920000000 build-sys-ns/op 28 | BenchmarkCespare_xxhash 1 1890000000 build-real-ns/op 8400000000 build-user-ns/op 730000000 build-sys-ns/op 29 | BenchmarkKanzi 1 2090000000 build-real-ns/op 10000000000 build-user-ns/op 840000000 build-sys-ns/op 30 | BenchmarkGonum_lapack_native 1 4520000000 build-real-ns/op 16880000000 build-user-ns/op 1170000000 build-sys-ns/op 31 | BenchmarkEthereum_corevm 1 12100000000 build-real-ns/op 30240000000 build-user-ns/op 2800000000 build-sys-ns/op 32 | BenchmarkGonum_community 1 4170000000 build-real-ns/op 17380000000 build-user-ns/op 1400000000 build-sys-ns/op 33 | BenchmarkGonum_topo 1 4120000000 build-real-ns/op 17910000000 build-user-ns/op 1340000000 build-sys-ns/op 34 | BenchmarkEthereum_core 1 13900000000 build-real-ns/op 41610000000 build-user-ns/op 3730000000 build-sys-ns/op 35 | BenchmarkHugo_helpers 1 12910000000 build-real-ns/op 59680000000 build-user-ns/op 4360000000 build-sys-ns/op 36 | BenchmarkCespare_mph 1 1910000000 build-real-ns/op 8370000000 build-user-ns/op 650000000 build-sys-ns/op 37 | -------------------------------------------------------------------------------- /benchproc/filter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchproc 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | 11 | "golang.org/x/perf/benchfmt" 12 | ) 13 | 14 | func TestFilter(t *testing.T) { 15 | res := r(t, "Name/n1=v3", "f1", "v1", "f2", "v2") 16 | res.Values = []benchfmt.Value{ 17 | {Value: 100, Unit: "ns/op", OrigValue: 100e-9, OrigUnit: "sec/op"}, 18 | {Value: 100, Unit: "B/op", OrigValue: 0, OrigUnit: ""}, 19 | } 20 | const ALL = 0b11 21 | const NONE = 0 22 | 23 | check := func(t *testing.T, query string, want uint) { 24 | t.Helper() 25 | f, err := NewFilter(query) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | m, _ := f.Match(res) 30 | var got uint 31 | for i := 0; i < 2; i++ { 32 | if m.Test(i) { 33 | got |= 1 << i 34 | } 35 | } 36 | if got != want { 37 | t.Errorf("%s: got %02b, want %02b", query, got, want) 38 | } else if want == ALL && !m.All() { 39 | t.Errorf("%s: want All", query) 40 | } else if want == 0 && m.Any() { 41 | t.Errorf("%s: want !Any", query) 42 | } 43 | } 44 | 45 | t.Run("basic", func(t *testing.T) { 46 | // File keys 47 | check(t, "f1:v1", ALL) 48 | check(t, "f1:v2", NONE) 49 | // Name keys 50 | check(t, "/n1:v3", ALL) 51 | // Special keys 52 | check(t, ".name:Name", ALL) 53 | check(t, ".fullname:Name/n1=v3", ALL) 54 | }) 55 | 56 | t.Run("units", func(t *testing.T) { 57 | check(t, ".unit:ns/op", 0b01) // Base unit 58 | check(t, ".unit:sec/op", 0b01) // Tidied unit 59 | check(t, ".unit:B/op", 0b10) 60 | check(t, ".unit:foo", 0b00) 61 | }) 62 | 63 | t.Run("boolean", func(t *testing.T) { 64 | check(t, "*", ALL) 65 | check(t, "f1:v1 OR f1:v2", ALL) 66 | check(t, "f1:v1 AND f1:v2", NONE) 67 | check(t, "f1:v1 f1:v2", NONE) 68 | check(t, "f1:v1 f2:v2", ALL) 69 | check(t, "-f1:v1", NONE) 70 | check(t, "--f1:v1", ALL) 71 | check(t, ".unit:(ns/op OR B/op)", 0b11) 72 | }) 73 | 74 | t.Run("manyUnits", func(t *testing.T) { 75 | res := res.Clone() 76 | res.Values = make([]benchfmt.Value, 100) 77 | for i := range res.Values { 78 | res.Values[i].Unit = fmt.Sprintf("u%d", i) 79 | } 80 | // Test large unit matches through all operators. 81 | f, err := NewFilter("f1:v1 AND --(f1:v2 OR .unit:(u0 OR u99))") 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | m, _ := f.Match(res) 86 | for i := 0; i < 100; i++ { 87 | got := m.Test(i) 88 | want := i == 0 || i == 99 89 | if got != want { 90 | t.Errorf("for unit u%d, got %v, want %v", i, got, want) 91 | } 92 | } 93 | }) 94 | } 95 | 96 | func TestMatch(t *testing.T) { 97 | check := func(m Match, all, any bool) { 98 | t.Helper() 99 | if m.All() != all { 100 | t.Errorf("match %+v: All should be %v, got %v", m, all, !all) 101 | } 102 | if m.Any() != any { 103 | t.Errorf("match %+v: Any should be %v, got %v", m, any, !any) 104 | } 105 | if m.Test(-1) { 106 | t.Errorf("match %+v: Test(-1) should be false, got true", m) 107 | } 108 | if m.Test(m.n + 1) { 109 | t.Errorf("match %+v: Test(%v) should be false, got true", m, m.n+1) 110 | } 111 | } 112 | 113 | // Check nil mask. 114 | m := Match{n: 4, x: false} 115 | check(m, false, false) 116 | m = Match{n: 4, x: true} 117 | check(m, true, true) 118 | 119 | // Check mask with some bits set. 120 | m = Match{n: 4, m: []uint32{0x1}} 121 | check(m, false, true) 122 | m = Match{n: 1, m: []uint32{0x1}} 123 | check(m, true, true) 124 | 125 | // Check that we ignore zeroed bits above n. 126 | m = Match{n: 4, m: []uint32{0xf}} 127 | check(m, true, true) 128 | 129 | // Check that we ignore set bits above n. 130 | m = Match{n: 4, m: []uint32{0xfffffff0}} 131 | check(m, false, false) 132 | } 133 | -------------------------------------------------------------------------------- /benchfmt/testdata/bent/20200101T030626.Basel.build: -------------------------------------------------------------------------------- 1 | goos: linux 2 | goarch: amd64 3 | BenchmarkUber_zap 1 6720000000 build-real-ns/op 24300000000 build-user-ns/op 2590000000 build-sys-ns/op 4 | BenchmarkEthereum_core 1 14030000000 build-real-ns/op 41430000000 build-user-ns/op 3790000000 build-sys-ns/op 5 | BenchmarkEthereum_ethash 1 13010000000 build-real-ns/op 34960000000 build-user-ns/op 3560000000 build-sys-ns/op 6 | BenchmarkK8s_api 1 17900000000 build-real-ns/op 89630000000 build-user-ns/op 6030000000 build-sys-ns/op 7 | BenchmarkSemver 1 2009999999 build-real-ns/op 9130000000 build-user-ns/op 740000000 build-sys-ns/op 8 | BenchmarkHugo_helpers 1 13340000000 build-real-ns/op 60110000000 build-user-ns/op 4510000000 build-sys-ns/op 9 | BenchmarkGonum_path 1 4120000000 build-real-ns/op 17180000000 build-user-ns/op 1460000000 build-sys-ns/op 10 | BenchmarkK8s_schedulercache 1 17880000000 build-real-ns/op 91670000000 build-user-ns/op 6240000000 build-sys-ns/op 11 | BenchmarkGonum_topo 1 4110000000 build-real-ns/op 17520000000 build-user-ns/op 1550000000 build-sys-ns/op 12 | BenchmarkSpexs2 1 2580000000 build-real-ns/op 9880000000 build-user-ns/op 930000000 build-sys-ns/op 13 | BenchmarkCapnproto2 1 5230000000 build-real-ns/op 16920000000 build-user-ns/op 1630000000 build-sys-ns/op 14 | BenchmarkGonum_traverse 1 3960000000 build-real-ns/op 16490000000 build-user-ns/op 1210000000 build-sys-ns/op 15 | BenchmarkGonum_blas_native 1 3670000000 build-real-ns/op 12380000000 build-user-ns/op 940000000 build-sys-ns/op 16 | BenchmarkAjstarks_deck_generate 1 1920000000 build-real-ns/op 9150000000 build-user-ns/op 770000000 build-sys-ns/op 17 | BenchmarkEthereum_trie 1 11460000000 build-real-ns/op 27700000000 build-user-ns/op 2870000000 build-sys-ns/op 18 | BenchmarkDustin_humanize 1 2040000000 build-real-ns/op 9390000000 build-user-ns/op 710000000 build-sys-ns/op 19 | BenchmarkGonum_mat 1 4340000000 build-real-ns/op 17410000000 build-user-ns/op 1350000000 build-sys-ns/op 20 | BenchmarkRcrowley_metrics 1 2790000000 build-real-ns/op 11890000000 build-user-ns/op 1240000000 build-sys-ns/op 21 | BenchmarkEriclagergren_decimal 1 3090000000 build-real-ns/op 11340000000 build-user-ns/op 1120000000 build-sys-ns/op 22 | BenchmarkGonum_community 1 4300000000 build-real-ns/op 17230000000 build-user-ns/op 1530000000 build-sys-ns/op 23 | BenchmarkEthereum_storage 1 14120000000 build-real-ns/op 36630000000 build-user-ns/op 3430000000 build-sys-ns/op 24 | BenchmarkKanzi 1 2040000000 build-real-ns/op 9810000000 build-user-ns/op 870000000 build-sys-ns/op 25 | BenchmarkBenhoyt_goawk 1 2050000000 build-real-ns/op 9220000000 build-user-ns/op 980000000 build-sys-ns/op 26 | BenchmarkMinio 1 52370000000 build-real-ns/op 145070000000 build-user-ns/op 11230000000 build-sys-ns/op 27 | BenchmarkCespare_xxhash 1 1870000000 build-real-ns/op 8320000000 build-user-ns/op 790000000 build-sys-ns/op 28 | BenchmarkCespare_mph 1 1850000000 build-real-ns/op 8210000000 build-user-ns/op 690000000 build-sys-ns/op 29 | BenchmarkCommonmark_markdown 1 8570000000 build-real-ns/op 17310000000 build-user-ns/op 1340000000 build-sys-ns/op 30 | BenchmarkAws_jsonutil 1 5110000000 build-real-ns/op 23480000000 build-user-ns/op 2220000000 build-sys-ns/op 31 | BenchmarkGonum_lapack_native 1 4440000000 build-real-ns/op 16420000000 build-user-ns/op 1320000000 build-sys-ns/op 32 | BenchmarkGtank_blake2s 1 1910000000 build-real-ns/op 8930000000 build-user-ns/op 800000000 build-sys-ns/op 33 | BenchmarkNelsam_gxui_interval 1 1900000000 build-real-ns/op 8500000000 build-user-ns/op 780000000 build-sys-ns/op 34 | BenchmarkEthereum_corevm 1 12380000000 build-real-ns/op 30240000000 build-user-ns/op 2910000000 build-sys-ns/op 35 | BenchmarkDustin_broadcast 1 1870000000 build-real-ns/op 8340000000 build-user-ns/op 710000000 build-sys-ns/op 36 | BenchmarkEthereum_bitutil 1 3410000000 build-real-ns/op 10160000000 build-user-ns/op 950000000 build-sys-ns/op 37 | BenchmarkBindata 1 2210000000 build-real-ns/op 11040000000 build-user-ns/op 940000000 build-sys-ns/op 38 | -------------------------------------------------------------------------------- /benchproc/internal/parse/projection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package parse 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | // A Field is one element in a projection expression. It represents 13 | // extracting a single dimension of a benchmark result and applying an 14 | // order to it. 15 | type Field struct { 16 | Key string 17 | 18 | // Order is the sort order for this field. This can be 19 | // "first", meaning to sort by order of first appearance; 20 | // "fixed", meaning to use the explicit value order in Fixed; 21 | // or a named sort order. 22 | Order string 23 | 24 | // Fixed gives the explicit value order for "fixed" ordering. 25 | // If a record's value is not in this list, the record should 26 | // be filtered out. Otherwise, values should be sorted 27 | // according to their order in this list. 28 | Fixed []string 29 | 30 | // KeyOff and OrderOff give the byte offsets of the key and 31 | // order, for error reporting. 32 | KeyOff, OrderOff int 33 | } 34 | 35 | // String returns Projection as a valid projection expression. 36 | func (p Field) String() string { 37 | switch p.Order { 38 | case "first": 39 | return quoteWord(p.Key) 40 | case "fixed": 41 | words := make([]string, 0, len(p.Fixed)) 42 | for _, word := range p.Fixed { 43 | words = append(words, quoteWord(word)) 44 | } 45 | return fmt.Sprintf("%s@(%s)", quoteWord(p.Key), strings.Join(words, " ")) 46 | } 47 | return fmt.Sprintf("%s@%s", quoteWord(p.Key), quoteWord(p.Order)) 48 | } 49 | 50 | // ParseProjection parses a projection expression into a tuple of 51 | // Fields. 52 | func ParseProjection(q string) ([]Field, error) { 53 | // Parse each projection field. 54 | var fields []Field 55 | toks := newTokenizer(q) 56 | for { 57 | // Peek at the next token. 58 | tok, toks2 := toks.keyOrOp() 59 | if tok.Kind == 0 { 60 | // No more fields. 61 | break 62 | } else if tok.Kind == ',' && len(fields) > 0 { 63 | // Consume optional separating comma. 64 | toks = toks2 65 | } 66 | 67 | var f Field 68 | f, toks = parseField(toks) 69 | fields = append(fields, f) 70 | } 71 | toks.end() 72 | if toks.errt.err != nil { 73 | return nil, toks.errt.err 74 | } 75 | return fields, nil 76 | } 77 | 78 | func parseField(toks tokenizer) (Field, tokenizer) { 79 | var f Field 80 | 81 | // Consume key. 82 | key, toks2 := toks.keyOrOp() 83 | if !(key.Kind == 'w' || key.Kind == 'q') { 84 | _, toks = toks.error("expected key") 85 | return f, toks 86 | } 87 | toks = toks2 88 | f.Key = key.Tok 89 | f.KeyOff = key.Off 90 | 91 | // Consume optional sort order. 92 | f.Order = "first" 93 | f.OrderOff = key.Off + len(key.Tok) 94 | sep, toks2 := toks.keyOrOp() 95 | if sep.Kind != '@' { 96 | // No sort order. 97 | return f, toks 98 | } 99 | toks = toks2 100 | 101 | // Is it a named sort order? 102 | order, toks2 := toks.keyOrOp() 103 | f.OrderOff = order.Off 104 | if order.Kind == 'w' || order.Kind == 'q' { 105 | f.Order = order.Tok 106 | return f, toks2 107 | } 108 | // Or a fixed sort order? 109 | if order.Kind == '(' { 110 | f.Order = "fixed" 111 | toks = toks2 112 | for { 113 | t, toks2 := toks.keyOrOp() 114 | if t.Kind == 'w' || t.Kind == 'q' { 115 | toks = toks2 116 | f.Fixed = append(f.Fixed, t.Tok) 117 | } else if t.Kind == ')' { 118 | if len(f.Fixed) == 0 { 119 | _, toks = toks.error("nothing to match") 120 | } else { 121 | toks = toks2 122 | } 123 | break 124 | } else { 125 | _, toks = toks.error("missing )") 126 | break 127 | } 128 | } 129 | return f, toks 130 | } 131 | // Bad sort order syntax. 132 | _, toks = toks.error("expected named sort order or parenthesized list") 133 | return f, toks 134 | } 135 | -------------------------------------------------------------------------------- /benchproc/extract_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchproc 6 | 7 | import ( 8 | "testing" 9 | 10 | "golang.org/x/perf/benchfmt" 11 | ) 12 | 13 | func checkNameExtractor(t *testing.T, x extractor, fullName string, want string) { 14 | t.Helper() 15 | res := &benchfmt.Result{Name: benchfmt.Name(fullName)} 16 | got := string(x(res)) 17 | if got != want { 18 | t.Errorf("got %s, want %s", got, want) 19 | } 20 | } 21 | 22 | func TestExtractName(t *testing.T) { 23 | check := checkNameExtractor 24 | 25 | x, err := newExtractor(".name") 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | check(t, x, "Test", "Test") 30 | check(t, x, "Test/a", "Test") 31 | check(t, x, "Test-4", "Test") 32 | check(t, x, "Test/a-4", "Test") 33 | } 34 | 35 | func TestExtractFullName(t *testing.T) { 36 | check := checkNameExtractor 37 | 38 | t.Run("basic", func(t *testing.T) { 39 | x, err := newExtractor(".fullname") 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | check(t, x, "Test", "Test") 44 | check(t, x, "Test/a=123", "Test/a=123") 45 | check(t, x, "Test-2", "Test-2") 46 | }) 47 | 48 | t.Run("excludeA", func(t *testing.T) { 49 | x := newExtractorFullName([]string{"/a"}) 50 | check(t, x, "Test", "Test") 51 | check(t, x, "Test/a=123", "Test") 52 | check(t, x, "Test/b=123/a=123", "Test/b=123") 53 | check(t, x, "Test/a=123/b=123", "Test/b=123") 54 | check(t, x, "Test/a=123/a=123", "Test") 55 | check(t, x, "Test/a=123-2", "Test-2") 56 | }) 57 | 58 | t.Run("excludeName", func(t *testing.T) { 59 | x := newExtractorFullName([]string{".name"}) 60 | check(t, x, "Test", "*") 61 | check(t, x, "Test/a=123", "*/a=123") 62 | x = newExtractorFullName([]string{".name", "/a"}) 63 | check(t, x, "Test", "*") 64 | check(t, x, "Test/a=123", "*") 65 | check(t, x, "Test/a=123/b=123", "*/b=123") 66 | }) 67 | 68 | t.Run("excludeGomaxprocs", func(t *testing.T) { 69 | x := newExtractorFullName([]string{"/gomaxprocs"}) 70 | check(t, x, "Test", "Test") 71 | check(t, x, "Test/a=123", "Test/a=123") 72 | check(t, x, "Test/a=123-2", "Test/a=123") 73 | check(t, x, "Test/gomaxprocs=123", "Test") 74 | }) 75 | } 76 | 77 | func TestExtractNameKey(t *testing.T) { 78 | check := checkNameExtractor 79 | 80 | t.Run("basic", func(t *testing.T) { 81 | x, err := newExtractor("/a") 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | check(t, x, "Test", "") 86 | check(t, x, "Test/a=1", "1") 87 | check(t, x, "Test/aa=1", "") 88 | check(t, x, "Test/a=1/b=2", "1") 89 | check(t, x, "Test/b=1/a=2", "2") 90 | check(t, x, "Test/b=1/a=2-4", "2") 91 | }) 92 | 93 | t.Run("gomaxprocs", func(t *testing.T) { 94 | x, err := newExtractor("/gomaxprocs") 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | check(t, x, "Test", "") 99 | check(t, x, "Test/gomaxprocs=4", "4") 100 | check(t, x, "Test-4", "4") 101 | check(t, x, "Test/a-4", "4") 102 | }) 103 | } 104 | 105 | func TestExtractFileKey(t *testing.T) { 106 | x, err := newExtractor("file-key") 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | 111 | res := r(t, "Name", "file-key", "123", "other-key", "456") 112 | got := string(x(res)) 113 | want := "123" 114 | if got != want { 115 | t.Errorf("got %s, want %s", got, want) 116 | } 117 | 118 | res = r(t, "Name", "other-key", "456") 119 | got = string(x(res)) 120 | want = "" 121 | if got != want { 122 | t.Errorf("got %s, want %s", got, want) 123 | } 124 | } 125 | 126 | func TestExtractBadKey(t *testing.T) { 127 | check := func(t *testing.T, got error, want string) { 128 | t.Helper() 129 | if got == nil || got.Error() != want { 130 | t.Errorf("got error %s, want error %s", got, want) 131 | } 132 | } 133 | _, err := newExtractor("") 134 | check(t, err, "key must not be empty") 135 | } 136 | -------------------------------------------------------------------------------- /benchproc/internal/parse/filter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package parse 6 | 7 | import "strconv" 8 | 9 | // ParseFilter parses a filter expression into a Filter tree. 10 | func ParseFilter(q string) (Filter, error) { 11 | toks := newTokenizer(q) 12 | p := parser{} 13 | query, toks := p.expr(toks) 14 | toks.end() 15 | if toks.errt.err != nil { 16 | return nil, toks.errt.err 17 | } 18 | return query, nil 19 | } 20 | 21 | type parser struct{} 22 | 23 | func (p *parser) error(toks tokenizer, msg string) tokenizer { 24 | _, toks = toks.error(msg) 25 | return toks 26 | } 27 | 28 | func (p *parser) expr(toks tokenizer) (Filter, tokenizer) { 29 | var terms []Filter 30 | for { 31 | var q Filter 32 | q, toks = p.andExpr(toks) 33 | terms = append(terms, q) 34 | op, toks2 := toks.keyOrOp() 35 | if op.Kind != 'O' { 36 | break 37 | } 38 | toks = toks2 39 | } 40 | if len(terms) == 1 { 41 | return terms[0], toks 42 | } 43 | return &FilterOp{OpOr, terms}, toks 44 | } 45 | 46 | func (p *parser) andExpr(toks tokenizer) (Filter, tokenizer) { 47 | var q Filter 48 | q, toks = p.match(toks) 49 | terms := []Filter{q} 50 | loop: 51 | for { 52 | op, toks2 := toks.keyOrOp() 53 | switch op.Kind { 54 | case 'A': 55 | // "AND" between matches is the same as no 56 | // operator. Skip. 57 | toks = toks2 58 | continue 59 | case '(', '-', '*', 'w', 'q': 60 | q, toks = p.match(toks) 61 | terms = append(terms, q) 62 | case ')', 'O', 0: 63 | break loop 64 | default: 65 | return nil, p.error(toks, "unexpected "+strconv.Quote(op.Tok)) 66 | } 67 | } 68 | if len(terms) == 1 { 69 | return terms[0], toks 70 | } 71 | return &FilterOp{OpAnd, terms}, toks 72 | } 73 | 74 | func (p *parser) match(start tokenizer) (Filter, tokenizer) { 75 | tok, rest := start.keyOrOp() 76 | switch tok.Kind { 77 | case '(': 78 | q, rest := p.expr(rest) 79 | op, toks2 := rest.keyOrOp() 80 | if op.Kind != ')' { 81 | return nil, p.error(rest, "missing \")\"") 82 | } 83 | return q, toks2 84 | case '-': 85 | q, rest := p.match(rest) 86 | q = &FilterOp{OpNot, []Filter{q}} 87 | return q, rest 88 | case '*': 89 | q := &FilterOp{OpAnd, nil} 90 | return q, rest 91 | case 'w', 'q': 92 | off := tok.Off 93 | key := tok.Tok 94 | op, toks2 := rest.keyOrOp() 95 | if op.Kind != ':' { 96 | // TODO: Support other operators 97 | return nil, p.error(start, "expected key:value") 98 | } 99 | rest = toks2 100 | val, rest := rest.valueOrOp() 101 | switch val.Kind { 102 | default: 103 | return nil, p.error(start, "expected key:value") 104 | case 'w', 'q', 'r': 105 | return p.mkMatch(off, key, val), rest 106 | case '(': 107 | var terms []Filter 108 | for { 109 | val, toks2 := rest.valueOrOp() 110 | switch val.Kind { 111 | default: 112 | return nil, p.error(rest, "expected value") 113 | case 'w', 'q', 'r': 114 | terms = append(terms, p.mkMatch(off, key, val)) 115 | } 116 | rest = toks2 117 | 118 | // Consume "OR" or ")" 119 | val, toks2 = rest.valueOrOp() 120 | switch val.Kind { 121 | default: 122 | return nil, p.error(rest, "value list must be separated by OR") 123 | case ')': 124 | return &FilterOp{OpOr, terms}, toks2 125 | case 'O': 126 | // Do nothing 127 | } 128 | rest = toks2 129 | } 130 | } 131 | } 132 | return nil, p.error(start, "expected key:value or subexpression") 133 | } 134 | 135 | func (p *parser) mkMatch(off int, key string, val tok) Filter { 136 | switch val.Kind { 137 | case 'w', 'q': 138 | // Literal match. 139 | return &FilterMatch{key, nil, val.Tok, off} 140 | case 'r': 141 | // Regexp match. 142 | return &FilterMatch{key, val.Regexp, "", off} 143 | default: 144 | panic("non-word token") 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /benchproc/sort.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package benchproc 6 | 7 | import ( 8 | "math" 9 | "regexp" 10 | "sort" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | // Less reports whether k comes before o in the sort order implied by 16 | // their projection. It panics if k and o have different Projections. 17 | func (k Key) Less(o Key) bool { 18 | if k.k.proj != o.k.proj { 19 | panic("cannot compare Keys from different Projections") 20 | } 21 | return less(k.k.proj.FlattenedFields(), k.k.vals, o.k.vals) 22 | } 23 | 24 | func less(flat []*Field, a, b []string) bool { 25 | // Walk the tuples in flattened order. 26 | for _, node := range flat { 27 | var aa, bb string 28 | if node.idx < len(a) { 29 | aa = a[node.idx] 30 | } 31 | if node.idx < len(b) { 32 | bb = b[node.idx] 33 | } 34 | if aa != bb { 35 | cmp := node.cmp(aa, bb) 36 | if cmp != 0 { 37 | return cmp < 0 38 | } 39 | // The values are equal/unordered according to 40 | // the comparison function, but the strings 41 | // differ. Because Keys are only == if 42 | // their string representations are ==, this 43 | // means we have to fall back to a secondary 44 | // comparison that is only == if the strings 45 | // are ==. 46 | return aa < bb 47 | } 48 | } 49 | 50 | // Tuples are equal. 51 | return false 52 | } 53 | 54 | // SortKeys sorts a slice of Keys using Key.Less. 55 | // All Keys must have the same Projection. 56 | // 57 | // This is equivalent to using Key.Less with the sort package but 58 | // more efficient. 59 | func SortKeys(keys []Key) { 60 | // Check all the Projections so we don't have to do this on every 61 | // comparison. 62 | if len(keys) == 0 { 63 | return 64 | } 65 | s := commonProjection(keys) 66 | flat := s.FlattenedFields() 67 | 68 | sort.Slice(keys, func(i, j int) bool { 69 | return less(flat, keys[i].k.vals, keys[j].k.vals) 70 | }) 71 | } 72 | 73 | // builtinOrders is the built-in comparison functions. 74 | var builtinOrders = map[string]func(a, b string) int{ 75 | "alpha": func(a, b string) int { 76 | return strings.Compare(a, b) 77 | }, 78 | "num": func(a, b string) int { 79 | aa, erra := parseNum(a) 80 | bb, errb := parseNum(b) 81 | if erra == nil && errb == nil { 82 | // Sort numerically, and put NaNs after other 83 | // values. 84 | if aa < bb || (!math.IsNaN(aa) && math.IsNaN(bb)) { 85 | return -1 86 | } 87 | if aa > bb || (math.IsNaN(aa) && !math.IsNaN(bb)) { 88 | return 1 89 | } 90 | // The values are unordered. 91 | return 0 92 | } 93 | if erra != nil && errb != nil { 94 | // The values are unordered. 95 | return 0 96 | } 97 | // Put floats before non-floats. 98 | if erra == nil { 99 | return -1 100 | } 101 | return 1 102 | }, 103 | } 104 | 105 | const numPrefixes = `KMGTPEZY` 106 | 107 | var numRe = regexp.MustCompile(`([0-9.]+)([k` + numPrefixes + `]i?)?[bB]?`) 108 | 109 | // parseNum is a fuzzy number parser. It supports common patterns, 110 | // such as SI prefixes. 111 | func parseNum(x string) (float64, error) { 112 | // Try parsing as a regular float. 113 | v, err := strconv.ParseFloat(x, 64) 114 | if err == nil { 115 | return v, nil 116 | } 117 | 118 | // Try a suffixed number. 119 | subs := numRe.FindStringSubmatch(x) 120 | if subs != nil { 121 | v, err := strconv.ParseFloat(subs[1], 64) 122 | if err == nil { 123 | exp := 0 124 | if len(subs[2]) > 0 { 125 | pre := subs[2][0] 126 | if pre == 'k' { 127 | pre = 'K' 128 | } 129 | exp = 1 + strings.IndexByte(numPrefixes, pre) 130 | } 131 | iec := strings.HasSuffix(subs[2], "i") 132 | if iec { 133 | return v * math.Pow(1024, float64(exp)), nil 134 | } 135 | return v * math.Pow(1000, float64(exp)), nil 136 | } 137 | } 138 | 139 | return 0, strconv.ErrSyntax 140 | } 141 | -------------------------------------------------------------------------------- /internal/stats/normaldist.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stats 6 | 7 | import ( 8 | "math" 9 | "math/rand" 10 | ) 11 | 12 | // NormalDist is a normal (Gaussian) distribution with mean Mu and 13 | // standard deviation Sigma. 14 | type NormalDist struct { 15 | Mu, Sigma float64 16 | } 17 | 18 | // StdNormal is the standard normal distribution (Mu = 0, Sigma = 1) 19 | var StdNormal = NormalDist{0, 1} 20 | 21 | // 1/sqrt(2 * pi) 22 | const invSqrt2Pi = 0.39894228040143267793994605993438186847585863116493465766592583 23 | 24 | func (n NormalDist) PDF(x float64) float64 { 25 | z := x - n.Mu 26 | return math.Exp(-z*z/(2*n.Sigma*n.Sigma)) * invSqrt2Pi / n.Sigma 27 | } 28 | 29 | func (n NormalDist) pdfEach(xs []float64) []float64 { 30 | res := make([]float64, len(xs)) 31 | if n.Mu == 0 && n.Sigma == 1 { 32 | // Standard normal fast path 33 | for i, x := range xs { 34 | res[i] = math.Exp(-x*x/2) * invSqrt2Pi 35 | } 36 | } else { 37 | a := -1 / (2 * n.Sigma * n.Sigma) 38 | b := invSqrt2Pi / n.Sigma 39 | for i, x := range xs { 40 | z := x - n.Mu 41 | res[i] = math.Exp(z*z*a) * b 42 | } 43 | } 44 | return res 45 | } 46 | 47 | func (n NormalDist) CDF(x float64) float64 { 48 | return math.Erfc(-(x-n.Mu)/(n.Sigma*math.Sqrt2)) / 2 49 | } 50 | 51 | func (n NormalDist) cdfEach(xs []float64) []float64 { 52 | res := make([]float64, len(xs)) 53 | a := 1 / (n.Sigma * math.Sqrt2) 54 | for i, x := range xs { 55 | res[i] = math.Erfc(-(x-n.Mu)*a) / 2 56 | } 57 | return res 58 | } 59 | 60 | func (n NormalDist) InvCDF(p float64) (x float64) { 61 | // This is based on Peter John Acklam's inverse normal CDF 62 | // algorithm: http://home.online.no/~pjacklam/notes/invnorm/ 63 | const ( 64 | a1 = -3.969683028665376e+01 65 | a2 = 2.209460984245205e+02 66 | a3 = -2.759285104469687e+02 67 | a4 = 1.383577518672690e+02 68 | a5 = -3.066479806614716e+01 69 | a6 = 2.506628277459239e+00 70 | 71 | b1 = -5.447609879822406e+01 72 | b2 = 1.615858368580409e+02 73 | b3 = -1.556989798598866e+02 74 | b4 = 6.680131188771972e+01 75 | b5 = -1.328068155288572e+01 76 | 77 | c1 = -7.784894002430293e-03 78 | c2 = -3.223964580411365e-01 79 | c3 = -2.400758277161838e+00 80 | c4 = -2.549732539343734e+00 81 | c5 = 4.374664141464968e+00 82 | c6 = 2.938163982698783e+00 83 | 84 | d1 = 7.784695709041462e-03 85 | d2 = 3.224671290700398e-01 86 | d3 = 2.445134137142996e+00 87 | d4 = 3.754408661907416e+00 88 | 89 | plow = 0.02425 90 | phigh = 1 - plow 91 | ) 92 | 93 | if p < 0 || p > 1 { 94 | return nan 95 | } else if p == 0 { 96 | return -inf 97 | } else if p == 1 { 98 | return inf 99 | } 100 | 101 | if p < plow { 102 | // Rational approximation for lower region. 103 | q := math.Sqrt(-2 * math.Log(p)) 104 | x = (((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q + c6) / 105 | ((((d1*q+d2)*q+d3)*q+d4)*q + 1) 106 | } else if phigh < p { 107 | // Rational approximation for upper region. 108 | q := math.Sqrt(-2 * math.Log(1-p)) 109 | x = -(((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q + c6) / 110 | ((((d1*q+d2)*q+d3)*q+d4)*q + 1) 111 | } else { 112 | // Rational approximation for central region. 113 | q := p - 0.5 114 | r := q * q 115 | x = (((((a1*r+a2)*r+a3)*r+a4)*r+a5)*r + a6) * q / 116 | (((((b1*r+b2)*r+b3)*r+b4)*r+b5)*r + 1) 117 | } 118 | 119 | // Refine approximation. 120 | e := 0.5*math.Erfc(-x/math.Sqrt2) - p 121 | u := e * math.Sqrt(2*math.Pi) * math.Exp(x*x/2) 122 | x = x - u/(1+x*u/2) 123 | 124 | // Adjust from standard normal. 125 | return x*n.Sigma + n.Mu 126 | } 127 | 128 | func (n NormalDist) Rand(r *rand.Rand) float64 { 129 | var x float64 130 | if r == nil { 131 | x = rand.NormFloat64() 132 | } else { 133 | x = r.NormFloat64() 134 | } 135 | return x*n.Sigma + n.Mu 136 | } 137 | 138 | func (n NormalDist) Bounds() (float64, float64) { 139 | const stddevs = 3 140 | return n.Mu - stddevs*n.Sigma, n.Mu + stddevs*n.Sigma 141 | } 142 | --------------------------------------------------------------------------------