| // Copyright 2019 Google Inc. All rights reserved. |
| // |
| // 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 status |
| |
| import ( |
| "time" |
| |
| "android/soong/ui/logger" |
| ) |
| |
| func NewCriticalPath(log logger.Logger) StatusOutput { |
| return &criticalPath{ |
| log: log, |
| running: make(map[*Action]time.Time), |
| nodes: make(map[string]*node), |
| clock: osClock{}, |
| } |
| } |
| |
| type criticalPath struct { |
| log logger.Logger |
| |
| nodes map[string]*node |
| running map[*Action]time.Time |
| |
| start, end time.Time |
| |
| clock clock |
| } |
| |
| type clock interface { |
| Now() time.Time |
| } |
| |
| type osClock struct{} |
| |
| func (osClock) Now() time.Time { return time.Now() } |
| |
| // A critical path node stores the critical path (the minimum time to build the node and all of its dependencies given |
| // perfect parallelism) for an node. |
| type node struct { |
| action *Action |
| cumulativeDuration time.Duration |
| duration time.Duration |
| input *node |
| } |
| |
| func (cp *criticalPath) StartAction(action *Action, counts Counts) { |
| start := cp.clock.Now() |
| if cp.start.IsZero() { |
| cp.start = start |
| } |
| cp.running[action] = start |
| } |
| |
| func (cp *criticalPath) FinishAction(result ActionResult, counts Counts) { |
| if start, ok := cp.running[result.Action]; ok { |
| delete(cp.running, result.Action) |
| |
| // Determine the input to this edge with the longest cumulative duration |
| var criticalPathInput *node |
| for _, input := range result.Action.Inputs { |
| if x := cp.nodes[input]; x != nil { |
| if criticalPathInput == nil || x.cumulativeDuration > criticalPathInput.cumulativeDuration { |
| criticalPathInput = x |
| } |
| } |
| } |
| |
| end := cp.clock.Now() |
| duration := end.Sub(start) |
| |
| cumulativeDuration := duration |
| if criticalPathInput != nil { |
| cumulativeDuration += criticalPathInput.cumulativeDuration |
| } |
| |
| node := &node{ |
| action: result.Action, |
| cumulativeDuration: cumulativeDuration, |
| duration: duration, |
| input: criticalPathInput, |
| } |
| |
| for _, output := range result.Action.Outputs { |
| cp.nodes[output] = node |
| } |
| |
| cp.end = end |
| } |
| } |
| |
| func (cp *criticalPath) Flush() { |
| criticalPath := cp.criticalPath() |
| |
| if len(criticalPath) > 0 { |
| // Log the critical path to the verbose log |
| criticalTime := criticalPath[0].cumulativeDuration.Round(time.Second) |
| cp.log.Verbosef("critical path took %s", criticalTime.String()) |
| if !cp.start.IsZero() { |
| elapsedTime := cp.end.Sub(cp.start).Round(time.Second) |
| cp.log.Verbosef("elapsed time %s", elapsedTime.String()) |
| if elapsedTime > 0 { |
| cp.log.Verbosef("perfect parallelism ratio %d%%", |
| int(float64(criticalTime)/float64(elapsedTime)*100)) |
| } |
| } |
| cp.log.Verbose("critical path:") |
| for i := len(criticalPath) - 1; i >= 0; i-- { |
| duration := criticalPath[i].duration |
| duration = duration.Round(time.Second) |
| seconds := int(duration.Seconds()) |
| cp.log.Verbosef(" %2d:%02d %s", |
| seconds/60, seconds%60, criticalPath[i].action.Description) |
| } |
| } |
| } |
| |
| func (cp *criticalPath) Message(level MsgLevel, msg string) {} |
| |
| func (cp *criticalPath) Write(p []byte) (n int, err error) { return len(p), nil } |
| |
| func (cp *criticalPath) criticalPath() []*node { |
| var max *node |
| |
| // Find the node with the longest critical path |
| for _, node := range cp.nodes { |
| if max == nil || node.cumulativeDuration > max.cumulativeDuration { |
| max = node |
| } |
| } |
| |
| // Follow the critical path back to the leaf node |
| var criticalPath []*node |
| node := max |
| for node != nil { |
| criticalPath = append(criticalPath, node) |
| node = node.input |
| } |
| |
| return criticalPath |
| } |