|  | // 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/auth" | 
|  | "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, auth.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 | 
|  | } |