1 /*
<lambda>null2  * Copyright 2018 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18  * Notes
19  *
20  * TODO (chriswailes): Add an argument parser
21  * TODO (chriswailes): Move slice name parsing into slice class (if approved by jreck)
22  */
23 
24 /*
25  * Imports
26  */
27 
28 import trebuchet.model.Model
29 import trebuchet.model.ProcessModel
30 import trebuchet.model.SchedulingState
31 import trebuchet.model.base.Slice
32 import trebuchet.queries.slices.*
33 import trebuchet.util.slices.*
34 import trebuchet.util.time.*
35 
36 /*
37  * Constants
38  */
39 
40 // Duration (in milliseconds) used for startup period when none of the
41 // appropriate events could be found.
42 const val DEFAULT_START_DURATION = 5000
43 
44 const val PROC_NAME_SYSTEM_SERVER = "system_server"
45 
46 /*
47  * Class Definition
48  */
49 
50 data class StartupEvent(val proc : ProcessModel,
51                         val name : String,
52                         val startTime : Double,
53                         val endTime : Double,
54                         val serverSideForkTime : Double,
55                         val reportFullyDrawnTime : Double?,
56                         val firstSliceTime : Double,
57                         val undifferentiatedTime : Double,
58                         val schedTimings : Map<SchedulingState, Double>,
59                         val allSlicesInfo : AggregateSliceInfoMap,
60                         val topLevelSliceInfo : AggregateSliceInfoMap,
61                         val undifferentiatedSliceInfo : AggregateSliceInfoMap,
62                         val nonNestedSliceInfo : AggregateSliceInfoMap)
63 
64 class AggregateSliceInfo {
65     var count : Int = 0
66     var totalTime : Double = 0.0
67 
68     val values : MutableMap<String, Pair<Int, Double>> = mutableMapOf()
69 }
70 
71 typealias MutableAggregateSliceInfoMap = MutableMap<String, AggregateSliceInfo>
72 typealias AggregateSliceInfoMap = Map<String, AggregateSliceInfo>
73 
74 class MissingProcessInfoException(pid : Int) : Exception("Missing process info for PID $pid")
75 
76 class MissingProcessException : Exception {
77     constructor(name : String) {
78         Exception("Unable to find process: $name")
79     }
80 
81     constructor(name : String, lowerBound : Double, upperBound : Double) {
82         Exception("Unable to find process: $name" +
83                   " (Bounds: [${lowerBound.secondValueToMillisecondString()}," +
84                   " ${upperBound.secondValueToMillisecondString()}]")
85     }
86 }
87 
88 /*
89  * Class Extensions
90  */
91 
ProcessModelnull92 fun ProcessModel.fuzzyNameMatch(queryName : String) : Boolean {
93     if (queryName.endsWith(this.name)) {
94         return true
95     } else {
96         for (thread in this.threads) {
97             if (queryName.endsWith(thread.name)) {
98                 return true
99             }
100         }
101     }
102 
103     return false
104 }
105 
findProcessnull106 fun Model.findProcess(queryName: String,
107                       lowerBound : Double = this.beginTimestamp,
108                       upperBound : Double = this.endTimestamp): ProcessModel {
109 
110     for (process in this.processes.values) {
111         if (process.fuzzyNameMatch(queryName)) {
112             val firstSliceStart =
113                 process.
114                     threads.
115                     map { it.slices }.
116                     filter { it.isNotEmpty() }.
117                     map { it.first().startTime }.
118                     min() ?: throw MissingProcessInfoException(process.id)
119 
120             if (firstSliceStart in lowerBound..upperBound) {
121                 return process
122             }
123         }
124     }
125 
126     if (lowerBound == this.beginTimestamp && upperBound == this.endTimestamp) {
127         throw MissingProcessException(queryName)
128     } else {
129         throw MissingProcessException(queryName, lowerBound, upperBound)
130     }
131 }
132 
Modelnull133 fun Model.getStartupEvents() : List<StartupEvent> {
134     val systemServerProc = this.findProcess(PROC_NAME_SYSTEM_SERVER)
135 
136     val startupEvents = mutableListOf<StartupEvent>()
137 
138     systemServerProc.asyncSlices.forEach { systemServerSlice ->
139         if (systemServerSlice.name.startsWith(SLICE_NAME_APP_LAUNCH)) {
140 
141             val newProcName    = systemServerSlice.name.split(':', limit = 2)[1].trim()
142             val newProc        = this.findProcess(newProcName, systemServerSlice.startTime, systemServerSlice.endTime)
143             val startProcSlice = systemServerProc.findFirstSlice(SLICE_NAME_PROC_START, newProcName, systemServerSlice.startTime, systemServerSlice.endTime)
144             val rfdSlice       = systemServerProc.findFirstSliceOrNull(SLICE_NAME_REPORT_FULLY_DRAWN, newProcName, systemServerSlice.startTime)
145             val firstSliceTime = newProc.threads.map { it.slices.firstOrNull()?.startTime ?: Double.POSITIVE_INFINITY }.min()!!
146 
147             val schedSliceInfo : MutableMap<SchedulingState, Double> = mutableMapOf()
148             newProc.threads.first().schedSlices.forEach schedLoop@ { schedSlice ->
149                 val origVal = schedSliceInfo.getOrDefault(schedSlice.state, 0.0)
150 
151                 when {
152                     schedSlice.startTime >= systemServerSlice.endTime -> return@schedLoop
153                     schedSlice.endTime <= systemServerSlice.endTime   -> schedSliceInfo[schedSlice.state] = origVal + schedSlice.duration
154                     else                                              -> {
155                         schedSliceInfo[schedSlice.state] = origVal + (systemServerSlice.endTime - schedSlice.startTime)
156                         return@schedLoop
157                     }
158                 }
159             }
160 
161             var undifferentiatedTime = 0.0
162 
163             val allSlicesInfo : MutableAggregateSliceInfoMap = mutableMapOf()
164             val topLevelSliceInfo : MutableAggregateSliceInfoMap = mutableMapOf()
165             val undifferentiatedSliceInfo : MutableAggregateSliceInfoMap = mutableMapOf()
166             val nonNestedSliceInfo : MutableAggregateSliceInfoMap = mutableMapOf()
167 
168             newProc.threads.first().traverseSlices(object : SliceTraverser {
169                 // Our depth down an individual tree in the slice forest.
170                 var treeDepth = -1
171                 val sliceDepths: MutableMap<String, Int> = mutableMapOf()
172 
173                 var lastTopLevelSlice : Slice? = null
174 
175                 override fun beginSlice(slice : Slice) : TraverseAction {
176                     val sliceContents = parseSliceName(slice.name)
177 
178                     ++this.treeDepth
179 
180                     val sliceDepth = this.sliceDepths.getOrDefault(sliceContents.name, -1)
181                     this.sliceDepths[sliceContents.name] = sliceDepth + 1
182 
183                     if (slice.startTime < systemServerSlice.endTime) {
184                         // This slice starts during the startup period.  If it
185                         // ends within the startup period we will record info
186                         // from this slice.  Either way we will visit its
187                         // children.
188 
189                         if (this.treeDepth == 0 && this.lastTopLevelSlice != null) {
190                             undifferentiatedTime += (slice.startTime - this.lastTopLevelSlice!!.endTime)
191                         }
192 
193                         if (slice.endTime <= systemServerSlice.endTime) {
194                             // This slice belongs in our collection.
195 
196                             // All Slice Timings
197                             aggregateSliceInfo(allSlicesInfo, sliceContents, slice.duration)
198 
199                             // Undifferentiated Timings
200                             aggregateSliceInfo(undifferentiatedSliceInfo, sliceContents, slice.durationSelf)
201 
202                             // Top-level timings
203                             if (this.treeDepth == 0) {
204                                 aggregateSliceInfo(topLevelSliceInfo, sliceContents, slice.duration)
205                             }
206 
207                             // Non-nested timings
208                             if (sliceDepths[sliceContents.name] == 0) {
209                                 aggregateSliceInfo(nonNestedSliceInfo, sliceContents, slice.duration)
210                             }
211                         }
212 
213                         return TraverseAction.VISIT_CHILDREN
214 
215                     } else {
216                         // All contents of this slice occur after the startup
217                         // period has ended. We don't need to record anything
218                         // or traverse any children.
219                         return TraverseAction.DONE
220                     }
221                 }
222 
223                 override fun endSlice(slice : Slice) {
224                     if (this.treeDepth == 0) {
225                         lastTopLevelSlice = slice
226                     }
227 
228                     val sliceInfo = parseSliceName(slice.name)
229                     this.sliceDepths[sliceInfo.name] = this.sliceDepths[sliceInfo.name]!! - 1
230 
231                     --this.treeDepth
232                 }
233             })
234 
235             startupEvents.add(
236                 StartupEvent(newProc,
237                              newProcName,
238                              systemServerSlice.startTime,
239                              systemServerSlice.endTime,
240                              startProcSlice.duration,
241                              rfdSlice?.startTime,
242                              firstSliceTime,
243                              undifferentiatedTime,
244                              schedSliceInfo,
245                              allSlicesInfo,
246                              topLevelSliceInfo,
247                              undifferentiatedSliceInfo,
248                              nonNestedSliceInfo))
249         }
250     }
251 
252     return startupEvents
253 }
254 
aggregateSliceInfonull255 fun aggregateSliceInfo(infoMap : MutableAggregateSliceInfoMap, sliceContents : SliceContents, duration : Double) {
256     val aggInfo = infoMap.getOrPut(sliceContents.name, ::AggregateSliceInfo)
257     ++aggInfo.count
258     aggInfo.totalTime += duration
259     if (sliceContents.value != null) {
260         val (uniqueValueCount, uniqueValueDuration) = aggInfo.values.getOrDefault(sliceContents.value as String, Pair(0, 0.0))
261         aggInfo.values[sliceContents.value as String] = Pair(uniqueValueCount + 1, uniqueValueDuration + duration)
262     }
263 }