/* * 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, 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> = mutableMapOf() } typealias MutableAggregateSliceInfoMap = MutableMap typealias AggregateSliceInfoMap = Map 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 { val systemServerProc = this.findProcess(PROC_NAME_SYSTEM_SERVER) val startupEvents = mutableListOf() 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 = 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 = 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) } }