blob: f96da47a624aa4984a792a79001392cf0157337d [file] [log] [blame]
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package time
import (
"context"
"flag"
"fmt"
"math"
"sort"
"time"
"dawn.googlesource.com/dawn/tools/src/cmd/cts/common"
"dawn.googlesource.com/dawn/tools/src/cts/query"
"dawn.googlesource.com/dawn/tools/src/cts/result"
"dawn.googlesource.com/dawn/tools/src/subcmd"
"go.chromium.org/luci/auth/client/authcli"
)
func init() {
common.Register(&cmd{})
}
type cmd struct {
flags struct {
source common.ResultSource
auth authcli.Flags
tags string
query string
aggregate bool
topN int
histogram bool
}
}
func (cmd) Name() string {
return "time"
}
func (cmd) Desc() string {
return "displays timing information for tests"
}
func (c *cmd) RegisterFlags(ctx context.Context, cfg common.Config) ([]string, error) {
c.flags.source.RegisterFlags(cfg)
c.flags.auth.Register(flag.CommandLine, common.DefaultAuthOptions())
flag.IntVar(&c.flags.topN, "top", 0, "print the top N slowest tests")
flag.BoolVar(&c.flags.histogram, "histogram", false, "print a histogram of test timings")
flag.StringVar(&c.flags.query, "query", "", "test query to filter results")
flag.StringVar(&c.flags.tags, "tags", "", "comma-separated list of tags to filter results")
flag.BoolVar(&c.flags.aggregate, "aggregate", false, "aggregate times by test")
return nil, nil
}
func (c *cmd) Run(ctx context.Context, cfg common.Config) error {
// Validate command line arguments
auth, err := c.flags.auth.Options()
if err != nil {
return fmt.Errorf("failed to obtain authentication options: %w", err)
}
// Obtain the results
results, err := c.flags.source.GetResults(ctx, cfg, auth)
if err != nil {
return err
}
if len(results) == 0 {
return fmt.Errorf("no results found")
}
// If tags were provided, filter the results to those that contain these tags
if c.flags.tags != "" {
results = results.FilterByTags(result.StringToTags(c.flags.tags))
if len(results) == 0 {
return fmt.Errorf("no results after filtering by tags")
}
}
if c.flags.query != "" {
results = results.FilterByQuery(query.Parse(c.flags.query))
if len(results) == 0 {
return fmt.Errorf("no results after filtering by test query")
}
}
if c.flags.aggregate {
type Key struct {
Query query.Query
Status result.Status
Tags string
}
merged := map[Key]result.Result{}
for _, r := range results {
k := Key{
Query: query.Query{
Suite: r.Query.Suite,
Files: r.Query.Files,
Tests: r.Query.Tests,
Cases: "*",
},
Status: r.Status,
Tags: result.TagsToString(r.Tags),
}
entry, exists := merged[k]
if exists {
entry.Duration += r.Duration
} else {
entry = result.Result{
Query: k.Query,
Duration: r.Duration,
Status: r.Status,
Tags: r.Tags,
}
}
merged[k] = entry
}
results = result.List{}
for _, r := range merged {
results = append(results, r)
}
}
// Sort the results with longest duration first
sort.Slice(results, func(i, j int) bool {
return results[i].Duration > results[j].Duration
})
didSomething := false
// Did the user request --top N ?
if c.flags.topN > 0 {
didSomething = true
topN := results
if c.flags.topN < len(results) {
topN = topN[:c.flags.topN]
}
for i, r := range topN {
fmt.Printf("%3.1d: %v\n", i, r)
}
}
// Did the user request --histogram ?
if c.flags.histogram {
maxTime := results[0].Duration
const (
numBins = 25
pow = 2.0
)
binToDuration := func(i int) time.Duration {
frac := math.Pow(float64(i)/float64(numBins), pow)
return time.Duration(float64(maxTime) * frac)
}
durationToBin := func(d time.Duration) int {
frac := math.Pow(float64(d)/float64(maxTime), 1.0/pow)
idx := int(frac * numBins)
if idx >= numBins-1 {
return numBins - 1
}
return idx
}
didSomething = true
bins := make([]int, numBins)
for _, r := range results {
idx := durationToBin(r.Duration)
bins[idx] = bins[idx] + 1
}
for i, bin := range bins {
fmt.Printf("[%.8v, %.8v]: %v\n", binToDuration(i), binToDuration(i+1), bin)
}
}
// If the user didn't request anything, show a helpful message
if !didSomething {
fmt.Fprintln(flag.CommandLine.Output(), "no action flags specified for", c.Name())
fmt.Fprintln(flag.CommandLine.Output())
flag.Usage()
return subcmd.ErrInvalidCLA
}
return nil
}