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 }