Modified existing SliceQuery functions and added some new ones. am: 7cc06964ed
am: 4c6780ccc7
Change-Id: Ie62ebb8bb84f925b3b47c7b08fe8ed1436a2e8c5
diff --git a/core/common/src/main/kotlin/trebuchet/queries/SliceQueries.kt b/core/common/src/main/kotlin/trebuchet/queries/SliceQueries.kt
index 95efe28..79a9183 100644
--- a/core/common/src/main/kotlin/trebuchet/queries/SliceQueries.kt
+++ b/core/common/src/main/kotlin/trebuchet/queries/SliceQueries.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package trebuchet.queries
+package trebuchet.queries.slices
import kotlin.sequences.Sequence
import kotlin.sequences.SequenceScope
@@ -24,6 +24,8 @@
import trebuchet.model.ThreadModel
import trebuchet.model.base.Slice
import trebuchet.model.base.SliceGroup
+import trebuchet.util.slices.parseSliceName
+import trebuchet.util.time.*
enum class TraverseAction {
/**
@@ -41,138 +43,358 @@
fun endSlice(slice: Slice) {}
}
+class MissingSliceException : Exception {
+ constructor(pid : Int, type : String, value : String?) {
+ Exception("Unable to find slice. PID = $pid, Type = $type" +
+ (if (value == null) "" else ", Value = $value"))
+ }
+
+ constructor(pid : Int, type : String, value : String?, lowerBound : Double, upperBound : Double) {
+ Exception("Unable to find slice. PID = $pid, Type = $type" +
+ (if (value == null) "" else ", Value = $value") +
+ " (Bounds: [${lowerBound.secondValueToMillisecondString()}," +
+ " ${upperBound.secondValueToMillisecondString()}])")
+ }
+
+ constructor (pid : Int, pattern : Regex) {
+ Exception("Unable to find slice. PID = $pid, Pattern = $pattern")
+ }
+
+ constructor (pid : Int, pattern : Regex, lowerBound : Double, upperBound : Double) {
+ Exception("Unable to find slice. PID = $pid, Pattern = $pattern" +
+ " (Bounds: [${lowerBound.secondValueToMillisecondString()}," +
+ " ${upperBound.secondValueToMillisecondString()}])")
+ }
+}
+
/**
- * SliceQueries provides several methods of operating over all the slices in a model in bulk.
+ * Find all of the slices that match the given predicate.
*
- * [selectAll] finds all slices that match the given predicate.
- *
- * [iterSlices] applies a function to all slices in a model.
- *
- * [traverseSlices] is a more powerful version of [iterSlices]. It takes a [SliceTraverser], which
- * allows code to be run at the beginning and end of processing a slice. This allows the
- * [SliceTraverser] to, for example, keep track of how deep it is within the tree.
- * The [SliceTraverser] can also indicate whether [traverseSlices] should continue processing
- * child slices in the case of a [SliceGroup].
+ * @param predicate The predicate used to test slices
*/
-object SliceQueries {
- fun selectAll(model: Model, cb: (Slice) -> Boolean): List<Slice> {
- val ret = mutableListOf<Slice>()
- iterSlices(model) {
- if (cb(it)) {
- ret.add(it)
- }
+fun Model.selectAll(predicate: (Slice) -> Boolean): List<Slice> {
+ val ret = mutableListOf<Slice>()
+ this.iterSlices {
+ if (predicate(it)) {
+ ret.add(it)
}
- return ret
}
+ return ret
+}
- fun selectAll(process : ProcessModel, cb : (Slice) -> Boolean) : List<Slice> {
- val ret = mutableListOf<Slice>()
- iterSlices(process) {
- if (cb(it)) {
- ret.add(it)
- }
+/**
+ * Find all of the slices that match the given predicate.
+ *
+ * @param predicate The predicate used to test slices
+ */
+fun ProcessModel.selectAll(predicate : (Slice) -> Boolean) : List<Slice> {
+ val ret = mutableListOf<Slice>()
+ this.iterSlices {
+ if (predicate(it)) {
+ ret.add(it)
}
- return ret
}
+ return ret
+}
- fun selectAll(thread: ThreadModel, cb: (Slice) -> Boolean): List<Slice> {
- val ret = mutableListOf<Slice>()
- iterSlices(thread) {
- if (cb(it)) {
- ret.add(it)
- }
+fun ThreadModel.selectAll(predicate: (Slice) -> Boolean): List<Slice> {
+ val ret = mutableListOf<Slice>()
+ this.iterSlices {
+ if (predicate(it)) {
+ ret.add(it)
}
- return ret
}
+ return ret
+}
- fun selectFirst(model: Model, cb: (Slice) -> Boolean) : Slice? {
- return model.processes.values.mapNotNull { selectFirst(it, cb) }.minBy { it.startTime }
- }
+/**
+ * Find the first slice that matches the given predicate.
+ *
+ * @param predicate The predicate used to test slices
+ */
+fun Model.selectFirst(predicate: (Slice) -> Boolean) : Slice? {
+ return this.processes.values.mapNotNull { it.selectFirst(predicate) }.minBy { it.startTime }
+}
- fun selectFirst(process: ProcessModel, cb: (Slice) -> Boolean) : Slice? {
- var ret : Slice? = null
- process.asyncSlices.forEach {
- if (cb(it)) {
- ret = it
- return@forEach
- }
- }
+/**
+ * Find the first slice that matches the given predicate.
+ *
+ * @param predicate The predicate used to test slices
+ */
+fun ProcessModel.selectFirst(predicate: (Slice) -> Boolean) : Slice? {
+ var ret : Slice? = null
- process.threads.forEach { thread ->
- val threadSlice = selectFirst(thread, cb)
-
- if (threadSlice != null && (ret == null || threadSlice.startTime < ret!!.startTime)) {
- ret = threadSlice
- }
- }
-
- return ret
- }
-
- fun selectFirst(thread : ThreadModel, cb : (Slice) -> Boolean) : Slice? {
- var ret : Slice? = null
- iterSlices(thread) {
- if (cb(it)) {
- ret = it
- return@iterSlices
- }
- }
- return ret
- }
-
- fun traverseSlices(model: Model, cb: SliceTraverser) {
- model.processes.values.forEach { traverseSlices(it, cb) }
- }
-
- fun traverseSlices(process: ProcessModel, cb: SliceTraverser) {
- process.asyncSlices.forEach {
- cb.beginSlice(it)
- cb.endSlice(it)
- }
-
- process.threads.forEach { traverseSlices(it, cb) }
- }
-
- fun traverseSlices(thread: ThreadModel, cb: SliceTraverser) {
- traverseSlices(thread.slices, cb)
- }
-
- fun traverseSlices(slices: List<SliceGroup>, cb: SliceTraverser) {
- slices.forEach {
- val action = cb.beginSlice(it)
- if (action == TraverseAction.VISIT_CHILDREN)
- traverseSlices(it.children, cb)
- cb.endSlice(it)
+ this.asyncSlices.forEach {
+ if (predicate(it)) {
+ ret = it
+ return@forEach
}
}
- fun iterSlices(model: Model, cb: (Slice) -> Unit) {
- model.processes.values.forEach { iterSlices(it, cb) }
- }
+ this.threads.forEach { thread ->
+ val threadSlice = thread.selectFirst(predicate)
- fun iterSlices(process: ProcessModel, cb: (Slice) -> Unit) {
- process.asyncSlices.forEach { cb(it) }
- process.threads.forEach { iterSlices(it, cb) }
- }
-
- fun iterSlices(thread: ThreadModel, cb: (Slice) -> Unit) {
- iterSlices(thread.slices, cb)
- }
-
- fun iterSlices(slices: List<SliceGroup>, cb: (Slice) -> Unit) {
- slices.forEach {
- cb(it)
- iterSlices(it.children, cb)
+ if (threadSlice != null && (ret == null || threadSlice.startTime < ret!!.startTime)) {
+ ret = threadSlice
}
}
- fun any(slices: List<SliceGroup>, cb: (Slice) -> Boolean): Boolean {
- slices.forEach {
- if (cb(it)) return true
- if (any(it.children, cb)) return true
+ return ret
+}
+
+
+/**
+ * Find the first slice that matches the given predicate.
+ *
+ * @param predicate The predicate used to test slices
+ */
+fun ThreadModel.selectFirst(predicate : (Slice) -> Boolean) : Slice? {
+ var ret : Slice? = null
+ this.iterSlices {
+ if (predicate(it)) {
+ ret = it
+ return@iterSlices
}
- return false
+ }
+ return ret
+}
+
+/**
+ * This function is a more powerful version of [iterSlices]. It takes a [SliceTraverser], which
+ * allows code to be run at the beginning and end of processing a slice. This allows the
+ * [SliceTraverser] to, for example, keep track of how deep it is within the tree. The
+ * [SliceTraverser] can also indicate whether [traverseSlices] should continue processing child
+ * slices in the case of a [SliceGroup].
+ */
+fun Model.traverseSlices(visitor: SliceTraverser) {
+ this.processes.values.forEach { it.traverseSlices(visitor) }
+}
+
+/**
+ * This function is a more powerful version of [iterSlices]. It takes a [SliceTraverser], which
+ * allows code to be run at the beginning and end of processing a slice. This allows the
+ * [SliceTraverser] to, for example, keep track of how deep it is within the tree. The
+ * [SliceTraverser] can also indicate whether [traverseSlices] should continue processing child
+ * slices in the case of a [SliceGroup].
+ */
+fun ProcessModel.traverseSlices(visitor: SliceTraverser) {
+ this.asyncSlices.forEach {
+ visitor.beginSlice(it)
+ visitor.endSlice(it)
+ }
+
+ this.threads.forEach { it.traverseSlices(visitor) }
+}
+
+/**
+ * This function is a more powerful version of [iterSlices]. It takes a [SliceTraverser], which
+ * allows code to be run at the beginning and end of processing a slice. This allows the
+ * [SliceTraverser] to, for example, keep track of how deep it is within the tree. The
+ * [SliceTraverser] can also indicate whether [traverseSlices] should continue processing child
+ * slices in the case of a [SliceGroup].
+ */
+fun ThreadModel.traverseSlices(visitor: SliceTraverser) {
+ this.slices.traverseSlices(visitor)
+}
+
+/**
+ * This function is a more powerful version of [iterSlices]. It takes a [SliceTraverser], which
+ * allows code to be run at the beginning and end of processing a slice. This allows the
+ * [SliceTraverser] to, for example, keep track of how deep it is within the tree. The
+ * [SliceTraverser] can also indicate whether [traverseSlices] should continue processing child
+ * slices in the case of a [SliceGroup].
+ */
+fun List<SliceGroup>.traverseSlices(visitor: SliceTraverser) {
+ this.forEach {
+ val action = visitor.beginSlice(it)
+ if (action == TraverseAction.VISIT_CHILDREN) {
+ it.children.traverseSlices(visitor)
+ }
+ visitor.endSlice(it)
+ }
+}
+
+/**
+ * Call the provided lambda on every slice in the model.
+ */
+fun Model.iterSlices(consumer: (Slice) -> Unit) {
+ this.processes.values.forEach { it.iterSlices(consumer) }
+}
+
+/**
+ * Call the provided lambda on every slice in the model.
+ */
+fun ProcessModel.iterSlices(consumer: (Slice) -> Unit) {
+ this.asyncSlices.forEach { consumer(it) }
+ this.threads.forEach { it.iterSlices(consumer) }
+}
+
+/**
+ * Call the provided lambda on every slice in the model.
+ */
+fun ThreadModel.iterSlices(consumer: (Slice) -> Unit) {
+ this.slices.iterSlices(consumer)
+}
+
+/**
+ * Call the provided lambda on every slice in the model.
+ */
+fun List<SliceGroup>.iterSlices(consumer: (Slice) -> Unit) {
+ this.forEach {
+ consumer(it)
+ it.children.iterSlices(consumer)
+ }
+}
+
+/**
+ * Call the provided lambda on every slice in the list.
+ */
+fun List<SliceGroup>.any(predicate: (Slice) -> Boolean): Boolean {
+ this.forEach {
+ if (predicate(it)) return true
+ if (it.children.any(predicate)) return true
+ }
+ return false
+}
+
+/**
+ * Find the first slice that meets the provided criteria.
+ *
+ * @param queryType The "type" of the slice being searched for
+ * @param queryValue The "value" of the slice being searched for
+ * @param lowerBound Slice must occur after this timestamp
+ * @param upperBound Slice must occur before this timestamp
+ *
+ * @throws MissingSliceException If no matching slice is found.
+ */
+fun ProcessModel.findFirstSlice(queryType : String,
+ queryValue : String?,
+ lowerBound : Double = this.model.beginTimestamp,
+ upperBound : Double = this.model.endTimestamp) : Slice {
+
+ val foundSlice = this.findFirstSliceOrNull(queryType, queryValue, lowerBound, upperBound)
+
+ if (foundSlice != null) {
+ return foundSlice
+ } else if (lowerBound == this.model.beginTimestamp && upperBound == this.model.endTimestamp) {
+ throw MissingSliceException(this.id, queryType, queryValue)
+ } else {
+ throw MissingSliceException(this.id, queryType, queryValue, lowerBound, upperBound)
+ }
+}
+
+/**
+ * Find the first slice that meets the provided criteria.
+ *
+ * @param pattern A pattern the slice text must match
+ * @param lowerBound Slice must occur after this timestamp
+ * @param upperBound Slice must occur before this timestamp
+ *
+ * @throws MissingSliceException If no matching slice is found.
+ */
+fun ProcessModel.findFirstSlice(pattern : Regex,
+ lowerBound : Double = this.model.beginTimestamp,
+ upperBound : Double = this.model.endTimestamp) : Slice {
+
+ val foundSlice = this.findFirstSliceOrNull(pattern, lowerBound, upperBound)
+
+ if (foundSlice != null) {
+ return foundSlice
+ } else if (lowerBound == this.model.beginTimestamp && upperBound == this.model.endTimestamp) {
+ throw MissingSliceException(this.id, pattern)
+ } else {
+ throw MissingSliceException(this.id, pattern, lowerBound, upperBound)
+ }
+}
+
+/**
+ * Find the first slice that meets the provided criteria.
+ *
+ * @param queryType The "type" of the slice being searched for
+ * @param queryValue The "value" of the slice being searched for
+ * @param lowerBound Slice must occur after this timestamp
+ * @param upperBound Slice must occur before this timestamp
+ *
+ * @return Slice or null if no match is found.
+ */
+fun ProcessModel.findFirstSliceOrNull(queryType : String,
+ queryValue : String?,
+ lowerBound : Double = this.model.beginTimestamp,
+ upperBound : Double = this.model.endTimestamp) : Slice? {
+
+ return this.selectFirst { slice ->
+ val sliceInfo = parseSliceName(slice.name)
+
+ sliceInfo.name == queryType &&
+ (queryValue == null || sliceInfo.value == queryValue) &&
+ lowerBound <= slice.startTime &&
+ slice.startTime <= upperBound
+ }
+}
+
+/**
+ * Find the first slice that meets the provided criteria.
+ *
+ * @param pattern A pattern the slice text must match
+ * @param lowerBound Slice must occur after this timestamp
+ * @param upperBound Slice must occur before this timestamp
+ *
+ * @return Slice or null if no match is found.
+ */
+fun ProcessModel.findFirstSliceOrNull(pattern : Regex,
+ lowerBound : Double = this.model.beginTimestamp,
+ upperBound : Double = this.model.endTimestamp) : Slice? {
+
+ return this.selectFirst { slice ->
+ pattern.find(slice.name) != null &&
+ lowerBound <= slice.startTime &&
+ slice.startTime <= upperBound
+ }
+}
+
+/**
+ * Find all slices that meet the provided criteria.
+ *
+ * @param queryType The "type" of the slice being searched for
+ * @param queryValue The "value" of the slice being searched for
+ * @param lowerBound Slice must occur after this timestamp
+ * @param upperBound Slice must occur before this timestamp
+ *
+ * @throws MissingSliceException If no matching slice is found.
+ */
+fun ProcessModel.findAllSlices(queryType : String,
+ queryValue : String?,
+ lowerBound : Double = this.model.beginTimestamp,
+ upperBound : Double = this.model.endTimestamp) : List<Slice> {
+
+ return this.selectAll{ slice ->
+ val sliceInfo = parseSliceName(slice.name)
+
+ sliceInfo.name == queryType &&
+ (queryValue == null || sliceInfo.value == queryValue) &&
+ lowerBound <= slice.startTime &&
+ slice.startTime <= upperBound
+ }
+}
+
+/**
+ * Find all slices that meet the provided criteria.
+ *
+ * @param pattern A pattern the slice text must match
+ * @param lowerBound Slice must occur after this timestamp
+ * @param upperBound Slice must occur before this timestamp
+ *
+ * @throws MissingSliceException If no matching slice is found.
+ */
+fun ProcessModel.findAllSlices(pattern : Regex,
+ lowerBound : Double = this.model.beginTimestamp,
+ upperBound : Double = this.model.endTimestamp) : List<Slice> {
+
+ return this.selectAll { slice ->
+ pattern.find(slice.name) != null &&
+ lowerBound <= slice.startTime &&
+ slice.startTime <= upperBound
}
}
diff --git a/core/common/src/main/kotlin/trebuchet/util/Slices.kt b/core/common/src/main/kotlin/trebuchet/util/Slices.kt
new file mode 100644
index 0000000..d183cf4
--- /dev/null
+++ b/core/common/src/main/kotlin/trebuchet/util/Slices.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019 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.
+ */
+
+package trebuchet.util.slices
+
+data class SliceContents(val name: String, val value: String?)
+
+const val SLICE_NAME_ACTIVITY_DESTROY = "activityDestroy"
+const val SLICE_NAME_ACTIVITY_PAUSE = "activityPause"
+const val SLICE_NAME_ACTIVITY_RESUME = "activityResume"
+const val SLICE_NAME_ACTIVITY_START = "activityStart"
+const val SLICE_NAME_ACTIVITY_THREAD_MAIN = "ActivityThreadMain"
+const val SLICE_NAME_APP_IMAGE_INTERN_STRING = "AppImage:InternString"
+const val SLICE_NAME_APP_IMAGE_LOADING = "AppImage:Loading"
+const val SLICE_NAME_APP_LAUNCH = "launching"
+const val SLICE_NAME_ALTERNATE_DEX_OPEN_START = "Dex file open"
+const val SLICE_NAME_BIND_APPLICATION = "bindApplication"
+const val SLICE_NAME_DRAWING = "drawing"
+const val SLICE_NAME_INFLATE = "inflate"
+const val SLICE_NAME_OPEN_DEX_FILE_FUNCTION = "std::unique_ptr<const DexFile> art::OatDexFile::OpenDexFile(std::string *) const"
+const val SLICE_NAME_OBFUSCATED_TRACE_START = "X."
+const val SLICE_NAME_POST_FORK = "PostFork"
+const val SLICE_NAME_PROC_START = "Start proc"
+const val SLICE_NAME_REPORT_FULLY_DRAWN = "ActivityManager:ReportingFullyDrawn"
+const val SLICE_NAME_ZYGOTE_INIT = "ZygoteInit"
+
+val SLICE_MAPPERS = arrayOf(
+ Regex("^(Collision check)"),
+ Regex("^(launching):\\s+([\\w\\.])+"),
+ Regex("^(Lock contention).*"),
+ Regex("^(monitor contention).*"),
+ Regex("^(NetworkSecurityConfigProvider.install)"),
+ Regex("^(notifyContentCapture\\((?:true|false)\\))\\sfor\\s(.*)"),
+ Regex("^(Open dex file)(?: from RAM)? ([\\w\\./]*)"),
+ Regex("^(Open oat file)\\s+(.*)"),
+ Regex("^(RegisterDexFile)\\s+(.*)"),
+ Regex("^($SLICE_NAME_REPORT_FULLY_DRAWN)\\s+(.*)"),
+ Regex("^(serviceCreate):.*className=([\\w\\.]+)"),
+ Regex("^(serviceStart):.*cmp=([\\w\\./]+)"),
+ Regex("^(Setup proxies)"),
+ Regex("^($SLICE_NAME_PROC_START):\\s+(.*)"),
+ Regex("^(SuspendThreadByThreadId) suspended (.+)$"),
+ Regex("^(VerifyClass)(.*)"),
+
+ // Default pattern for slices with a single-word name.
+ Regex("^([\\w:]+)$")
+)
+
+fun parseSliceName(sliceString: String): SliceContents {
+ when {
+ // Handle special cases.
+ sliceString == SLICE_NAME_OPEN_DEX_FILE_FUNCTION -> return SliceContents("Open dex file function invocation", null)
+ sliceString.startsWith(SLICE_NAME_ALTERNATE_DEX_OPEN_START) -> return SliceContents("Open dex file", sliceString.split(" ").last().trim())
+ sliceString.startsWith(SLICE_NAME_OBFUSCATED_TRACE_START) -> return SliceContents("Obfuscated trace point", null)
+ sliceString[0] == '/' -> return SliceContents("Load Dex files from classpath", null)
+
+ else -> {
+ // Search the slice mapping patterns.
+ for (pattern in SLICE_MAPPERS) {
+ val matchResult = pattern.find(sliceString)
+
+ if (matchResult != null) {
+ val sliceType = matchResult.groups[1]!!.value.trim()
+
+ val sliceDetails =
+ if (matchResult.groups.size > 2 && !matchResult.groups[2]!!.value.isEmpty()) {
+ matchResult.groups[2]!!.value.trim()
+ } else {
+ null
+ }
+
+ return SliceContents(sliceType, sliceDetails)
+ }
+ }
+
+ return SliceContents("Unknown Slice", sliceString)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/common/src/main/kotlin/trebuchet/util/Time.kt b/core/common/src/main/kotlin/trebuchet/util/Time.kt
new file mode 100644
index 0000000..a2d557a
--- /dev/null
+++ b/core/common/src/main/kotlin/trebuchet/util/Time.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2019 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.
+ */
+
+package trebuchet.util.time
+
+fun Double.secondValueToMillisecondString() = "%.3f ms".format(this * 1000.0)
\ No newline at end of file
diff --git a/core/common/src/test/kotlin/trebuchet/task/ImportTaskTest.kt b/core/common/src/test/kotlin/trebuchet/task/ImportTaskTest.kt
index dc75571..f51f3ca 100644
--- a/core/common/src/test/kotlin/trebuchet/task/ImportTaskTest.kt
+++ b/core/common/src/test/kotlin/trebuchet/task/ImportTaskTest.kt
@@ -25,7 +25,7 @@
import trebuchet.io.asSlice
import trebuchet.model.Model
import trebuchet.model.fragments.AsyncSlice
-import trebuchet.queries.SliceQueries
+import trebuchet.queries.slices.*
import trebuchet.testutils.NeedsSampleData
import java.io.File
@@ -33,7 +33,7 @@
@Test @NeedsSampleData
fun testImportCameraTrace() {
val model = import("hdr-0608-4-trace.html")
- val slices = SliceQueries.selectAll(model) { it.name.startsWith("MergeShot")}
+ val slices = model.selectAll { it.name.startsWith("MergeShot")}
assertEquals(2, slices.size)
assertEquals(0.868, slices[0].duration, .001)
assertEquals(0.866, slices[1].duration, .001)
@@ -45,7 +45,7 @@
val counterName = "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#1"
val process = model.processes.values.find { it.name == "surfaceflinger" }!!
val thread = process.threads.find { it.name == "surfaceflinger" }!!
- val slices = SliceQueries.selectAll(thread) { it.name == "handleMessageRefresh" }
+ val slices = thread.selectAll { it.name == "handleMessageRefresh" }
assertEquals(103, slices.size)
assertFalse(slices.any { it.duration <= 0.0 })
val totalDuration = slices.map { it.duration }.reduce { a,b -> a+b }
diff --git a/trebuchet/analyzer/src/Analyzer.kt b/trebuchet/analyzer/src/Analyzer.kt
index 7c70f47..7f6edb0 100644
--- a/trebuchet/analyzer/src/Analyzer.kt
+++ b/trebuchet/analyzer/src/Analyzer.kt
@@ -16,9 +16,8 @@
import trebuchet.extras.openSample
import trebuchet.model.Model
-import trebuchet.queries.SliceQueries
+import trebuchet.queries.slices.*
import trebuchet.queries.ThreadQueries
-import trebuchet.queries.slices
import trebuchet.util.par_map
fun timeMergeShot(model: Model) {
@@ -49,7 +48,7 @@
}
fun measureRotator(model: Model) {
- val latchBuffers = SliceQueries.selectAll(model) {
+ val latchBuffers = model.selectAll {
it.name == "latchBuffer"
}
var largestDuration = 0.0
@@ -57,7 +56,7 @@
var retireStart = 0.0
latchBuffers.forEachIndexed { index, slice ->
val cutoff = if (index < latchBuffers.size - 1) latchBuffers[index + 1].startTime else Double.MAX_VALUE
- val retire = SliceQueries.selectAll(model) {
+ val retire = model.selectAll {
it.name == "sde_rotator_retire_handler"
&& it.startTime > slice.endTime
&& it.endTime < cutoff
diff --git a/trebuchet/startup-analyzer/src/StartupAnalyzer.kt b/trebuchet/startup-analyzer/src/StartupAnalyzer.kt
index cdc93b0..90c0281 100644
--- a/trebuchet/startup-analyzer/src/StartupAnalyzer.kt
+++ b/trebuchet/startup-analyzer/src/StartupAnalyzer.kt
@@ -27,6 +27,7 @@
import java.io.File
import trebuchet.model.Model
import trebuchet.extras.parseTrace
+import trebuchet.util.time.*
/*
* Constants
diff --git a/trebuchet/startup-common/src/StartupCommon.kt b/trebuchet/startup-common/src/StartupCommon.kt
index 13345c5..75ed400 100644
--- a/trebuchet/startup-common/src/StartupCommon.kt
+++ b/trebuchet/startup-common/src/StartupCommon.kt
@@ -29,9 +29,9 @@
import trebuchet.model.ProcessModel
import trebuchet.model.SchedulingState
import trebuchet.model.base.Slice
-import trebuchet.queries.SliceQueries
-import trebuchet.queries.SliceTraverser
-import trebuchet.queries.TraverseAction
+import trebuchet.queries.slices.*
+import trebuchet.util.slices.*
+import trebuchet.util.time.*
/*
* Constants
@@ -43,52 +43,10 @@
const val PROC_NAME_SYSTEM_SERVER = "system_server"
-const val SLICE_NAME_ACTIVITY_DESTROY = "activityDestroy"
-const val SLICE_NAME_ACTIVITY_PAUSE = "activityPause"
-const val SLICE_NAME_ACTIVITY_RESUME = "activityResume"
-const val SLICE_NAME_ACTIVITY_START = "activityStart"
-const val SLICE_NAME_ACTIVITY_THREAD_MAIN = "ActivityThreadMain"
-const val SLICE_NAME_APP_IMAGE_INTERN_STRING = "AppImage:InternString"
-const val SLICE_NAME_APP_IMAGE_LOADING = "AppImage:Loading"
-const val SLICE_NAME_APP_LAUNCH = "launching"
-const val SLICE_NAME_ALTERNATE_DEX_OPEN_START = "Dex file open"
-const val SLICE_NAME_BIND_APPLICATION = "bindApplication"
-const val SLICE_NAME_DRAWING = "drawing"
-const val SLICE_NAME_INFLATE = "inflate"
-const val SLICE_NAME_OPEN_DEX_FILE_FUNCTION = "std::unique_ptr<const DexFile> art::OatDexFile::OpenDexFile(std::string *) const"
-const val SLICE_NAME_OBFUSCATED_TRACE_START = "X."
-const val SLICE_NAME_POST_FORK = "PostFork"
-const val SLICE_NAME_PROC_START = "Start proc"
-const val SLICE_NAME_REPORT_FULLY_DRAWN = "ActivityManager:ReportingFullyDrawn"
-const val SLICE_NAME_ZYGOTE_INIT = "ZygoteInit"
-
-val SLICE_MAPPERS = arrayOf(
- Regex("^(Collision check)"),
- Regex("^(launching):\\s+([\\w\\.])+"),
- Regex("^(Lock contention).*"),
- Regex("^(monitor contention).*"),
- Regex("^(NetworkSecurityConfigProvider.install)"),
- Regex("^(notifyContentCapture\\((?:true|false)\\))\\sfor\\s(.*)"),
- Regex("^(Open dex file)(?: from RAM)? ([\\w\\./]*)"),
- Regex("^(Open oat file)\\s+(.*)"),
- Regex("^(RegisterDexFile)\\s+(.*)"),
- Regex("^($SLICE_NAME_REPORT_FULLY_DRAWN)\\s+(.*)"),
- Regex("^(serviceCreate):.*className=([\\w\\.]+)"),
- Regex("^(serviceStart):.*cmp=([\\w\\./]+)"),
- Regex("^(Setup proxies)"),
- Regex("^($SLICE_NAME_PROC_START):\\s+(.*)"),
- Regex("^(SuspendThreadByThreadId) suspended (.+)$"),
- Regex("^(VerifyClass)(.*)"),
-
- // Default pattern for slices with a single-word name.
- Regex("^([\\w:]+)$")
-)
-
/*
* Class Definition
*/
-data class SliceContents(val name: String, val value: String?)
data class StartupEvent(val proc : ProcessModel,
val name : String,
val startTime : Double,
@@ -127,36 +85,10 @@
}
}
-class MissingSliceException : Exception {
- constructor(pid : Int, type : String, value : String?) {
- Exception("Unable to find slice. PID = $pid, Type = $type" +
- (if (value == null) "" else ", Value = $value"))
- }
-
- constructor(pid : Int, type : String, value : String?, lowerBound : Double, upperBound : Double) {
- Exception("Unable to find slice. PID = $pid, Type = $type" +
- (if (value == null) "" else ", Value = $value") +
- " (Bounds: [${lowerBound.secondValueToMillisecondString()}," +
- " ${upperBound.secondValueToMillisecondString()}])")
- }
-
- constructor (pid : Int, pattern : Regex) {
- Exception("Unable to find slice. PID = $pid, Pattern = $pattern")
- }
-
- constructor (pid : Int, pattern : Regex, lowerBound : Double, upperBound : Double) {
- Exception("Unable to find slice. PID = $pid, Pattern = $pattern" +
- " (Bounds: [${lowerBound.secondValueToMillisecondString()}," +
- " ${upperBound.secondValueToMillisecondString()}])")
- }
-}
-
/*
* Class Extensions
*/
-fun Double.secondValueToMillisecondString() = "%.3f ms".format(this * 1000.0)
-
fun ProcessModel.fuzzyNameMatch(queryName : String) : Boolean {
if (queryName.endsWith(this.name)) {
return true
@@ -233,7 +165,7 @@
val undifferentiatedSliceInfo : MutableAggregateSliceInfoMap = mutableMapOf()
val nonNestedSliceInfo : MutableAggregateSliceInfoMap = mutableMapOf()
- SliceQueries.traverseSlices(newProc.threads.first(), object : SliceTraverser {
+ 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()
@@ -300,19 +232,20 @@
}
})
- startupEvents.add(StartupEvent(newProc,
- newProcName,
- systemServerSlice.startTime,
- systemServerSlice.endTime,
- startProcSlice.duration,
- rfdSlice?.startTime,
- firstSliceTime,
- undifferentiatedTime,
- schedSliceInfo,
- allSlicesInfo,
- topLevelSliceInfo,
- undifferentiatedSliceInfo,
- nonNestedSliceInfo))
+ startupEvents.add(
+ StartupEvent(newProc,
+ newProcName,
+ systemServerSlice.startTime,
+ systemServerSlice.endTime,
+ startProcSlice.duration,
+ rfdSlice?.startTime,
+ firstSliceTime,
+ undifferentiatedTime,
+ schedSliceInfo,
+ allSlicesInfo,
+ topLevelSliceInfo,
+ undifferentiatedSliceInfo,
+ nonNestedSliceInfo))
}
}
@@ -324,133 +257,7 @@
++aggInfo.count
aggInfo.totalTime += duration
if (sliceContents.value != null) {
- val (uniqueValueCount, uniqueValueDuration) = aggInfo.values.getOrDefault(sliceContents.value, Pair(0, 0.0))
- aggInfo.values[sliceContents.value] = Pair(uniqueValueCount + 1, uniqueValueDuration + duration)
+ 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)
}
-}
-
-// TODO: Move into SliceQueries
-fun ProcessModel.findFirstSlice(queryType : String,
- queryValue : String?,
- lowerBound : Double = this.model.beginTimestamp,
- upperBound : Double = this.model.endTimestamp) : Slice {
-
- val foundSlice = this.findFirstSliceOrNull(queryType, queryValue, lowerBound, upperBound)
-
- if (foundSlice != null) {
- return foundSlice
- } else if (lowerBound == this.model.beginTimestamp && upperBound == this.model.endTimestamp) {
- throw MissingSliceException(this.id, queryType, queryValue)
- } else {
- throw MissingSliceException(this.id, queryType, queryValue, lowerBound, upperBound)
- }
-}
-
-// TODO: Move into SliceQueries
-fun ProcessModel.findFirstSlice(pattern : Regex,
- lowerBound : Double = this.model.beginTimestamp,
- upperBound : Double = this.model.endTimestamp) : Slice {
-
- val foundSlice = this.findFirstSliceOrNull(pattern, lowerBound, upperBound)
-
- if (foundSlice != null) {
- return foundSlice
- } else if (lowerBound == this.model.beginTimestamp && upperBound == this.model.endTimestamp) {
- throw MissingSliceException(this.id, pattern)
- } else {
- throw MissingSliceException(this.id, pattern, lowerBound, upperBound)
- }
-}
-
-// TODO: Move into SliceQueries
-fun ProcessModel.findFirstSliceOrNull(queryType : String,
- queryValue : String?,
- lowerBound : Double = this.model.beginTimestamp,
- upperBound : Double = this.model.endTimestamp) : Slice? {
-
- return SliceQueries.selectFirst(this) { slice ->
- val sliceInfo = parseSliceName(slice.name)
-
- sliceInfo.name == queryType &&
- (queryValue == null || sliceInfo.value == queryValue) &&
- lowerBound <= slice.startTime &&
- slice.startTime <= upperBound
- }
-}
-
-// TODO: Move into SliceQueries
-fun ProcessModel.findFirstSliceOrNull(pattern : Regex,
- lowerBound : Double = this.model.beginTimestamp,
- upperBound : Double = this.model.endTimestamp) : Slice? {
-
- return SliceQueries.selectFirst(this) { slice ->
- pattern.find(slice.name) != null &&
- lowerBound <= slice.startTime &&
- slice.startTime <= upperBound
- }
-}
-
-// TODO: Move into SliceQueries
-fun ProcessModel.findAllSlices(queryType : String,
- queryValue : String?,
- lowerBound : Double = this.model.beginTimestamp,
- upperBound : Double = this.model.endTimestamp) : List<Slice> {
-
- return SliceQueries.selectAll(this) { slice ->
- val sliceInfo = parseSliceName(slice.name)
-
- sliceInfo.name == queryType &&
- (queryValue == null || sliceInfo.value == queryValue) &&
- lowerBound <= slice.startTime &&
- slice.startTime <= upperBound
- }
-}
-
-// TODO: Move into SliceQueries
-fun ProcessModel.findAllSlices(pattern : Regex,
- lowerBound : Double = this.model.beginTimestamp,
- upperBound : Double = this.model.endTimestamp) : List<Slice> {
-
- return SliceQueries.selectAll(this) { slice ->
- pattern.find(slice.name) != null &&
- lowerBound <= slice.startTime &&
- slice.startTime <= upperBound
- }
-}
-
-/*
- * Functions
- */
-
-fun parseSliceName(sliceString: String): SliceContents {
- when {
- // Handle special cases.
- sliceString == SLICE_NAME_OPEN_DEX_FILE_FUNCTION -> return SliceContents("Open dex file function invocation", null)
- sliceString.startsWith(SLICE_NAME_ALTERNATE_DEX_OPEN_START) -> return SliceContents("Open dex file", sliceString.split(" ").last().trim())
- sliceString.startsWith(SLICE_NAME_OBFUSCATED_TRACE_START) -> return SliceContents("Obfuscated trace point", null)
- sliceString[0] == '/' -> return SliceContents("Load Dex files from classpath", null)
-
- else -> {
- // Search the slice mapping patterns.
- for (pattern in SLICE_MAPPERS) {
- val matchResult = pattern.find(sliceString)
-
- if (matchResult != null) {
- val sliceType = matchResult.groups[1]!!.value.trim()
-
- val sliceDetails =
- if (matchResult.groups.size > 2 && !matchResult.groups[2]!!.value.isEmpty()) {
- matchResult.groups[2]!!.value.trim()
- } else {
- null
- }
-
- return SliceContents(sliceType, sliceDetails)
- }
- }
-
- return SliceContents("Unknown Slice", sliceString)
- }
- }
-
-}
+}
\ No newline at end of file
diff --git a/trebuchet/startup-summarizer/src/StartupSummarizer.kt b/trebuchet/startup-summarizer/src/StartupSummarizer.kt
index 616a87f..2f9e3e1 100644
--- a/trebuchet/startup-summarizer/src/StartupSummarizer.kt
+++ b/trebuchet/startup-summarizer/src/StartupSummarizer.kt
@@ -26,12 +26,16 @@
*/
import java.io.File
-import trebuchet.extras.parseTrace
-import trebuchet.model.SchedulingState
+
import kotlin.math.pow
import kotlin.math.sqrt
import kotlin.system.exitProcess
+import trebuchet.extras.parseTrace
+import trebuchet.model.SchedulingState
+import trebuchet.util.slices.*
+import trebuchet.util.time.*
+
/*
* Constants
*/
@@ -147,38 +151,46 @@
}
}
-fun printAppRecord(record : ApplicationRecord) {
+fun printPlainText(records : MutableMap<String, ApplicationRecord>) {
+ records.forEach { appName, record ->
+ if (record.numSamples() > SAMPLE_THRESHOLD_APPLICATION) {
+ println("$appName:")
+ printAppRecordPlainText(record)
+ println()
+ }
+ }
+}
+fun printAppRecordPlainText(record : ApplicationRecord) {
if (record.quicken.numSamples() > SAMPLE_THRESHOLD_COMPILER) {
println("\tQuicken:")
- printCompilerRecord(record.quicken)
+ printCompilerRecordPlainText(record.quicken)
}
if (record.speed.numSamples() > SAMPLE_THRESHOLD_COMPILER) {
println("\tSpeed:")
- printCompilerRecord(record.speed)
+ printCompilerRecordPlainText(record.speed)
}
if (record.speedProfile.numSamples() > SAMPLE_THRESHOLD_COMPILER) {
println("\tSpeed Profile:")
- printCompilerRecord(record.speedProfile)
+ printCompilerRecordPlainText(record.speedProfile)
}
}
-fun printCompilerRecord(record : CompilerRecord) {
-
+fun printCompilerRecordPlainText(record : CompilerRecord) {
if (record.cold.size > SAMPLE_THRESHOLD_TEMPERATURE) {
println("\t\tCold:")
- printSampleSet(record.cold)
+ printSampleSetPlainText(record.cold)
}
if (record.warm.size > SAMPLE_THRESHOLD_TEMPERATURE) {
println("\t\tWarm:")
- printSampleSet(record.warm)
+ printSampleSetPlainText(record.warm)
}
}
-fun printSampleSet(records : List<StartupEvent>) {
+fun printSampleSetPlainText(records : List<StartupEvent>) {
val (startupTimeAverage, startupTimeStandardDeviation) = averageAndStandardDeviation(records.map {it.endTime - it.startTime})
val (timeToFirstSliceAverage, timeToFirstSliceStandardDeviation) = averageAndStandardDeviation(records.map {it.firstSliceTime - it.startTime})
@@ -253,16 +265,58 @@
println()
}
-fun printPlainText(records : MutableMap<String, ApplicationRecord>) {
+fun printCSV(records : MutableMap<String, ApplicationRecord>) {
+ println("Application Name, Compiler Filter, Temperature, Startup Time Avg (ms), Startup Time SD (ms), Time-to-first-slice Avg (ms), Time-to-first-slice SD (ms), Report Fully Drawn Avg (ms), Report Fully Drawn SD (ms)")
+
records.forEach { appName, record ->
if (record.numSamples() > SAMPLE_THRESHOLD_APPLICATION) {
- println("$appName:")
- printAppRecord(record)
- println()
+ printAppRecordCSV(appName, record)
}
}
}
+fun printAppRecordCSV(appName : String, record : ApplicationRecord) {
+ if (record.quicken.numSamples() > SAMPLE_THRESHOLD_COMPILER) {
+ printCompilerRecordCSV(appName, "quicken", record.quicken)
+ }
+
+ if (record.speed.numSamples() > SAMPLE_THRESHOLD_COMPILER) {
+ printCompilerRecordCSV(appName, "speed", record.speed)
+ }
+
+ if (record.speedProfile.numSamples() > SAMPLE_THRESHOLD_COMPILER) {
+ printCompilerRecordCSV(appName, "speed-profile", record.speedProfile)
+ }
+}
+
+fun printCompilerRecordCSV(appName : String, compilerFilter : String, record : CompilerRecord) {
+ if (record.cold.size > SAMPLE_THRESHOLD_TEMPERATURE) {
+ printSampleSetCSV(appName, compilerFilter, "cold", record.cold)
+ }
+
+ if (record.warm.size > SAMPLE_THRESHOLD_TEMPERATURE) {
+ printSampleSetCSV(appName, compilerFilter, "warm", record.warm)
+ }
+}
+
+fun printSampleSetCSV(appName : String, compilerFilter : String, temperature : String, records : List<StartupEvent>) {
+ print("$appName, $compilerFilter, $temperature, ")
+
+ val (startupTimeAverage, startupTimeStandardDeviation) = averageAndStandardDeviation(records.map {it.endTime - it.startTime})
+ print("%.3f, %.3f, ".format(startupTimeAverage * 1000, startupTimeStandardDeviation * 1000))
+
+ val (timeToFirstSliceAverage, timeToFirstSliceStandardDeviation) = averageAndStandardDeviation(records.map {it.firstSliceTime - it.startTime})
+ print("%.3f, %.3f,".format(timeToFirstSliceAverage * 1000, timeToFirstSliceStandardDeviation * 1000))
+
+ if (records.first().reportFullyDrawnTime != null) {
+ val (rfdTimeAverage, rfdTimeStandardDeviation) = averageAndStandardDeviation(records.map { it.reportFullyDrawnTime!! - it.startTime})
+ print(" %.3f, %.3f\n".format(rfdTimeAverage * 1000, rfdTimeStandardDeviation * 1000))
+
+ } else {
+ print(",\n")
+ }
+}
+
/*
* Main Function
*/
@@ -304,7 +358,8 @@
println()
- printPlainText(records)
+// printPlainText(records)
+ printCSV(records)
if (exceptions.count() > 0) {
println("Exceptions:")