// 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 }