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:")