| /* |
| * Copyright 2018 Google Inc. |
| * |
| * 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 |
| * |
| * https://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. |
| */ |
| |
| /* |
| * Notes |
| * |
| * TODO (chriswailes): Add an argument parser |
| * TODO (chriswailes): Move slice name parsing into slice class (if approved by jreck) |
| */ |
| |
| /* |
| * Imports |
| */ |
| |
| import trebuchet.model.Model |
| import trebuchet.model.ProcessModel |
| import trebuchet.model.SchedulingState |
| import trebuchet.model.base.Slice |
| import trebuchet.queries.slices.* |
| import trebuchet.util.slices.* |
| import trebuchet.util.time.* |
| |
| /* |
| * Constants |
| */ |
| |
| // Duration (in milliseconds) used for startup period when none of the |
| // appropriate events could be found. |
| const val DEFAULT_START_DURATION = 5000 |
| |
| const val PROC_NAME_SYSTEM_SERVER = "system_server" |
| |
| /* |
| * Class Definition |
| */ |
| |
| data class StartupEvent(val proc : ProcessModel, |
| val name : String, |
| val startTime : Double, |
| val endTime : Double, |
| val serverSideForkTime : Double, |
| val reportFullyDrawnTime : Double?, |
| val firstSliceTime : Double, |
| val undifferentiatedTime : Double, |
| val schedTimings : Map<SchedulingState, Double>, |
| val allSlicesInfo : AggregateSliceInfoMap, |
| val topLevelSliceInfo : AggregateSliceInfoMap, |
| val undifferentiatedSliceInfo : AggregateSliceInfoMap, |
| val nonNestedSliceInfo : AggregateSliceInfoMap) |
| |
| class AggregateSliceInfo { |
| var count : Int = 0 |
| var totalTime : Double = 0.0 |
| |
| val values : MutableMap<String, Pair<Int, Double>> = mutableMapOf() |
| } |
| |
| typealias MutableAggregateSliceInfoMap = MutableMap<String, AggregateSliceInfo> |
| typealias AggregateSliceInfoMap = Map<String, AggregateSliceInfo> |
| |
| class MissingProcessInfoException(pid : Int) : Exception("Missing process info for PID $pid") |
| |
| class MissingProcessException : Exception { |
| constructor(name : String) { |
| Exception("Unable to find process: $name") |
| } |
| |
| constructor(name : String, lowerBound : Double, upperBound : Double) { |
| Exception("Unable to find process: $name" + |
| " (Bounds: [${lowerBound.secondValueToMillisecondString()}," + |
| " ${upperBound.secondValueToMillisecondString()}]") |
| } |
| } |
| |
| /* |
| * Class Extensions |
| */ |
| |
| fun ProcessModel.fuzzyNameMatch(queryName : String) : Boolean { |
| if (queryName.endsWith(this.name)) { |
| return true |
| } else { |
| for (thread in this.threads) { |
| if (queryName.endsWith(thread.name)) { |
| return true |
| } |
| } |
| } |
| |
| return false |
| } |
| |
| fun Model.findProcess(queryName: String, |
| lowerBound : Double = this.beginTimestamp, |
| upperBound : Double = this.endTimestamp): ProcessModel { |
| |
| for (process in this.processes.values) { |
| if (process.fuzzyNameMatch(queryName)) { |
| val firstSliceStart = |
| process. |
| threads. |
| map { it.slices }. |
| filter { it.isNotEmpty() }. |
| map { it.first().startTime }. |
| min() ?: throw MissingProcessInfoException(process.id) |
| |
| if (firstSliceStart in lowerBound..upperBound) { |
| return process |
| } |
| } |
| } |
| |
| if (lowerBound == this.beginTimestamp && upperBound == this.endTimestamp) { |
| throw MissingProcessException(queryName) |
| } else { |
| throw MissingProcessException(queryName, lowerBound, upperBound) |
| } |
| } |
| |
| fun Model.getStartupEvents() : List<StartupEvent> { |
| val systemServerProc = this.findProcess(PROC_NAME_SYSTEM_SERVER) |
| |
| val startupEvents = mutableListOf<StartupEvent>() |
| |
| systemServerProc.asyncSlices.forEach { systemServerSlice -> |
| if (systemServerSlice.name.startsWith(SLICE_NAME_APP_LAUNCH)) { |
| |
| val newProcName = systemServerSlice.name.split(':', limit = 2)[1].trim() |
| val newProc = this.findProcess(newProcName, systemServerSlice.startTime, systemServerSlice.endTime) |
| val startProcSlice = systemServerProc.findFirstSlice(SLICE_NAME_PROC_START, newProcName, systemServerSlice.startTime, systemServerSlice.endTime) |
| val rfdSlice = systemServerProc.findFirstSliceOrNull(SLICE_NAME_REPORT_FULLY_DRAWN, newProcName, systemServerSlice.startTime) |
| val firstSliceTime = newProc.threads.map { it.slices.firstOrNull()?.startTime ?: Double.POSITIVE_INFINITY }.min()!! |
| |
| val schedSliceInfo : MutableMap<SchedulingState, Double> = mutableMapOf() |
| newProc.threads.first().schedSlices.forEach schedLoop@ { schedSlice -> |
| val origVal = schedSliceInfo.getOrDefault(schedSlice.state, 0.0) |
| |
| when { |
| schedSlice.startTime >= systemServerSlice.endTime -> return@schedLoop |
| schedSlice.endTime <= systemServerSlice.endTime -> schedSliceInfo[schedSlice.state] = origVal + schedSlice.duration |
| else -> { |
| schedSliceInfo[schedSlice.state] = origVal + (systemServerSlice.endTime - schedSlice.startTime) |
| return@schedLoop |
| } |
| } |
| } |
| |
| var undifferentiatedTime = 0.0 |
| |
| val allSlicesInfo : MutableAggregateSliceInfoMap = mutableMapOf() |
| val topLevelSliceInfo : MutableAggregateSliceInfoMap = mutableMapOf() |
| val undifferentiatedSliceInfo : MutableAggregateSliceInfoMap = mutableMapOf() |
| val nonNestedSliceInfo : MutableAggregateSliceInfoMap = mutableMapOf() |
| |
| newProc.threads.first().traverseSlices(object : SliceTraverser { |
| // Our depth down an individual tree in the slice forest. |
| var treeDepth = -1 |
| val sliceDepths: MutableMap<String, Int> = mutableMapOf() |
| |
| var lastTopLevelSlice : Slice? = null |
| |
| override fun beginSlice(slice : Slice) : TraverseAction { |
| val sliceContents = parseSliceName(slice.name) |
| |
| ++this.treeDepth |
| |
| val sliceDepth = this.sliceDepths.getOrDefault(sliceContents.name, -1) |
| this.sliceDepths[sliceContents.name] = sliceDepth + 1 |
| |
| if (slice.startTime < systemServerSlice.endTime) { |
| // This slice starts during the startup period. If it |
| // ends within the startup period we will record info |
| // from this slice. Either way we will visit its |
| // children. |
| |
| if (this.treeDepth == 0 && this.lastTopLevelSlice != null) { |
| undifferentiatedTime += (slice.startTime - this.lastTopLevelSlice!!.endTime) |
| } |
| |
| if (slice.endTime <= systemServerSlice.endTime) { |
| // This slice belongs in our collection. |
| |
| // All Slice Timings |
| aggregateSliceInfo(allSlicesInfo, sliceContents, slice.duration) |
| |
| // Undifferentiated Timings |
| aggregateSliceInfo(undifferentiatedSliceInfo, sliceContents, slice.durationSelf) |
| |
| // Top-level timings |
| if (this.treeDepth == 0) { |
| aggregateSliceInfo(topLevelSliceInfo, sliceContents, slice.duration) |
| } |
| |
| // Non-nested timings |
| if (sliceDepths[sliceContents.name] == 0) { |
| aggregateSliceInfo(nonNestedSliceInfo, sliceContents, slice.duration) |
| } |
| } |
| |
| return TraverseAction.VISIT_CHILDREN |
| |
| } else { |
| // All contents of this slice occur after the startup |
| // period has ended. We don't need to record anything |
| // or traverse any children. |
| return TraverseAction.DONE |
| } |
| } |
| |
| override fun endSlice(slice : Slice) { |
| if (this.treeDepth == 0) { |
| lastTopLevelSlice = slice |
| } |
| |
| val sliceInfo = parseSliceName(slice.name) |
| this.sliceDepths[sliceInfo.name] = this.sliceDepths[sliceInfo.name]!! - 1 |
| |
| --this.treeDepth |
| } |
| }) |
| |
| startupEvents.add( |
| StartupEvent(newProc, |
| newProcName, |
| systemServerSlice.startTime, |
| systemServerSlice.endTime, |
| startProcSlice.duration, |
| rfdSlice?.startTime, |
| firstSliceTime, |
| undifferentiatedTime, |
| schedSliceInfo, |
| allSlicesInfo, |
| topLevelSliceInfo, |
| undifferentiatedSliceInfo, |
| nonNestedSliceInfo)) |
| } |
| } |
| |
| return startupEvents |
| } |
| |
| fun aggregateSliceInfo(infoMap : MutableAggregateSliceInfoMap, sliceContents : SliceContents, duration : Double) { |
| val aggInfo = infoMap.getOrPut(sliceContents.name, ::AggregateSliceInfo) |
| ++aggInfo.count |
| aggInfo.totalTime += duration |
| if (sliceContents.value != null) { |
| val (uniqueValueCount, uniqueValueDuration) = aggInfo.values.getOrDefault(sliceContents.value as String, Pair(0, 0.0)) |
| aggInfo.values[sliceContents.value as String] = Pair(uniqueValueCount + 1, uniqueValueDuration + duration) |
| } |
| } |