Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 1 | // Copyright 2019 Google Inc. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package status |
| 16 | |
| 17 | import ( |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 18 | "android/soong/ui/metrics" |
| 19 | |
| 20 | soong_metrics_proto "android/soong/ui/metrics/metrics_proto" |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 21 | "time" |
| 22 | |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 23 | "google.golang.org/protobuf/proto" |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 24 | ) |
| 25 | |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 26 | func NewCriticalPath() *CriticalPath { |
| 27 | return &CriticalPath{ |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 28 | running: make(map[*Action]time.Time), |
| 29 | nodes: make(map[string]*node), |
| 30 | clock: osClock{}, |
| 31 | } |
| 32 | } |
| 33 | |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 34 | type CriticalPath struct { |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 35 | nodes map[string]*node |
| 36 | running map[*Action]time.Time |
| 37 | |
| 38 | start, end time.Time |
| 39 | |
| 40 | clock clock |
| 41 | } |
| 42 | |
| 43 | type clock interface { |
| 44 | Now() time.Time |
| 45 | } |
| 46 | |
| 47 | type osClock struct{} |
| 48 | |
| 49 | func (osClock) Now() time.Time { return time.Now() } |
| 50 | |
| 51 | // A critical path node stores the critical path (the minimum time to build the node and all of its dependencies given |
| 52 | // perfect parallelism) for an node. |
| 53 | type node struct { |
| 54 | action *Action |
| 55 | cumulativeDuration time.Duration |
| 56 | duration time.Duration |
| 57 | input *node |
| 58 | } |
| 59 | |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 60 | func (cp *CriticalPath) StartAction(action *Action) { |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 61 | start := cp.clock.Now() |
| 62 | if cp.start.IsZero() { |
| 63 | cp.start = start |
| 64 | } |
| 65 | cp.running[action] = start |
| 66 | } |
| 67 | |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 68 | func (cp *CriticalPath) FinishAction(action *Action) { |
| 69 | if start, ok := cp.running[action]; ok { |
| 70 | delete(cp.running, action) |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 71 | |
| 72 | // Determine the input to this edge with the longest cumulative duration |
| 73 | var criticalPathInput *node |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 74 | for _, input := range action.Inputs { |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 75 | if x := cp.nodes[input]; x != nil { |
| 76 | if criticalPathInput == nil || x.cumulativeDuration > criticalPathInput.cumulativeDuration { |
| 77 | criticalPathInput = x |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | end := cp.clock.Now() |
| 83 | duration := end.Sub(start) |
| 84 | |
| 85 | cumulativeDuration := duration |
| 86 | if criticalPathInput != nil { |
| 87 | cumulativeDuration += criticalPathInput.cumulativeDuration |
| 88 | } |
| 89 | |
| 90 | node := &node{ |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 91 | action: action, |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 92 | cumulativeDuration: cumulativeDuration, |
| 93 | duration: duration, |
| 94 | input: criticalPathInput, |
| 95 | } |
| 96 | |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 97 | for _, output := range action.Outputs { |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 98 | cp.nodes[output] = node |
| 99 | } |
| 100 | |
| 101 | cp.end = end |
| 102 | } |
| 103 | } |
| 104 | |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 105 | func (cp *CriticalPath) criticalPath() (path []*node, elapsedTime time.Duration, criticalTime time.Duration) { |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 106 | var max *node |
| 107 | |
| 108 | // Find the node with the longest critical path |
| 109 | for _, node := range cp.nodes { |
| 110 | if max == nil || node.cumulativeDuration > max.cumulativeDuration { |
| 111 | max = node |
| 112 | } |
| 113 | } |
| 114 | |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 115 | node := max |
| 116 | for node != nil { |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 117 | path = append(path, node) |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 118 | node = node.input |
| 119 | } |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 120 | if len(path) > 0 { |
| 121 | // Log the critical path to the verbose log |
| 122 | criticalTime = path[0].cumulativeDuration |
| 123 | if !cp.start.IsZero() { |
| 124 | elapsedTime = cp.end.Sub(cp.start) |
| 125 | } |
| 126 | } |
| 127 | return |
| 128 | } |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 129 | |
Jeongik Cha | 28c1fe5 | 2023-03-07 15:19:44 +0900 | [diff] [blame^] | 130 | func (cp *CriticalPath) WriteToMetrics(met *metrics.Metrics) { |
| 131 | criticalPathInfo := soong_metrics_proto.CriticalPathInfo{} |
| 132 | path, elapsedTime, criticalTime := cp.criticalPath() |
| 133 | criticalPathInfo.ElapsedTime = proto.Uint64(uint64(elapsedTime.Microseconds())) |
| 134 | criticalPathInfo.CriticalPathTime = proto.Uint64(uint64(criticalTime.Microseconds())) |
| 135 | for _, job := range path { |
| 136 | jobInfo := soong_metrics_proto.JobInfo{} |
| 137 | jobInfo.ElapsedTime = proto.Uint64(uint64(job.duration.Microseconds())) |
| 138 | jobInfo.JobDescription = &job.action.Description |
| 139 | criticalPathInfo.CriticalPath = append(criticalPathInfo.CriticalPath, &jobInfo) |
| 140 | } |
| 141 | met.SetCriticalPathInfo(criticalPathInfo) |
Colin Cross | 7b62453 | 2019-06-21 15:08:30 -0700 | [diff] [blame] | 142 | } |