blob: 79a9183f4ff4b43f3fc45c0294603dd97490a110 [file] [log] [blame]
/*
* Copyright 2017 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.queries.slices
import kotlin.sequences.Sequence
import kotlin.sequences.SequenceScope
import trebuchet.model.Model
import trebuchet.model.ProcessModel
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 {
/**
* Continue iterating over any child slices
*/
VISIT_CHILDREN,
/**
* There is no need to process any child slices
*/
DONE,
}
interface SliceTraverser {
fun beginSlice(slice: Slice): TraverseAction = TraverseAction.VISIT_CHILDREN
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()}])")
}
}
/**
* Find all of the slices that match the given predicate.
*
* @param predicate The predicate used to test slices
*/
fun Model.selectAll(predicate: (Slice) -> Boolean): List<Slice> {
val ret = mutableListOf<Slice>()
this.iterSlices {
if (predicate(it)) {
ret.add(it)
}
}
return ret
}
/**
* 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
}
fun ThreadModel.selectAll(predicate: (Slice) -> Boolean): List<Slice> {
val ret = mutableListOf<Slice>()
this.iterSlices {
if (predicate(it)) {
ret.add(it)
}
}
return ret
}
/**
* 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 }
}
/**
* 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
this.asyncSlices.forEach {
if (predicate(it)) {
ret = it
return@forEach
}
}
this.threads.forEach { thread ->
val threadSlice = thread.selectFirst(predicate)
if (threadSlice != null && (ret == null || threadSlice.startTime < ret!!.startTime)) {
ret = threadSlice
}
}
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 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
}
}
private suspend fun SequenceScope<Slice>.yieldSlices(slices: List<SliceGroup>) {
slices.forEach {
yield(it)
yieldSlices(it.children)
}
}
fun Model.slices(includeAsync: Boolean = true): Sequence<Slice> {
val model = this
return sequence {
model.processes.values.forEach { process ->
if (includeAsync) {
yieldAll(process.asyncSlices)
}
process.threads.forEach { thread ->
yieldSlices(thread.slices)
}
}
}
}