1 /*
<lambda>null2 * Copyright 2017 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package trebuchet.queries.slices
18
19 import kotlin.sequences.Sequence
20 import kotlin.sequences.SequenceScope
21
22 import trebuchet.model.Model
23 import trebuchet.model.ProcessModel
24 import trebuchet.model.ThreadModel
25 import trebuchet.model.base.Slice
26 import trebuchet.model.base.SliceGroup
27 import trebuchet.util.slices.parseSliceName
28 import trebuchet.util.time.*
29
30 enum class TraverseAction {
31 /**
32 * Continue iterating over any child slices
33 */
34 VISIT_CHILDREN,
35 /**
36 * There is no need to process any child slices
37 */
38 DONE,
39 }
40
41 interface SliceTraverser {
beginSlicenull42 fun beginSlice(slice: Slice): TraverseAction = TraverseAction.VISIT_CHILDREN
43 fun endSlice(slice: Slice) {}
44 }
45
46 class MissingSliceException : Exception {
47 constructor(pid : Int, type : String, value : String?) {
48 Exception("Unable to find slice. PID = $pid, Type = $type" +
49 (if (value == null) "" else ", Value = $value"))
50 }
51
52 constructor(pid : Int, type : String, value : String?, lowerBound : Double, upperBound : Double) {
53 Exception("Unable to find slice. PID = $pid, Type = $type" +
54 (if (value == null) "" else ", Value = $value") +
55 " (Bounds: [${lowerBound.secondValueToMillisecondString()}," +
56 " ${upperBound.secondValueToMillisecondString()}])")
57 }
58
59 constructor (pid : Int, pattern : Regex) {
60 Exception("Unable to find slice. PID = $pid, Pattern = $pattern")
61 }
62
63 constructor (pid : Int, pattern : Regex, lowerBound : Double, upperBound : Double) {
64 Exception("Unable to find slice. PID = $pid, Pattern = $pattern" +
65 " (Bounds: [${lowerBound.secondValueToMillisecondString()}," +
66 " ${upperBound.secondValueToMillisecondString()}])")
67 }
68 }
69
70 /**
71 * Find all of the slices that match the given predicate.
72 *
73 * @param predicate The predicate used to test slices
74 */
selectAllnull75 fun Model.selectAll(predicate: (Slice) -> Boolean): List<Slice> {
76 val ret = mutableListOf<Slice>()
77 this.iterSlices {
78 if (predicate(it)) {
79 ret.add(it)
80 }
81 }
82 return ret
83 }
84
85 /**
86 * Find all of the slices that match the given predicate.
87 *
88 * @param predicate The predicate used to test slices
89 */
ProcessModelnull90 fun ProcessModel.selectAll(predicate : (Slice) -> Boolean) : List<Slice> {
91 val ret = mutableListOf<Slice>()
92 this.iterSlices {
93 if (predicate(it)) {
94 ret.add(it)
95 }
96 }
97 return ret
98 }
99
selectAllnull100 fun ThreadModel.selectAll(predicate: (Slice) -> Boolean): List<Slice> {
101 val ret = mutableListOf<Slice>()
102 this.iterSlices {
103 if (predicate(it)) {
104 ret.add(it)
105 }
106 }
107 return ret
108 }
109
110 /**
111 * Find the first slice that matches the given predicate.
112 *
113 * @param predicate The predicate used to test slices
114 */
selectFirstnull115 fun Model.selectFirst(predicate: (Slice) -> Boolean) : Slice? {
116 return this.processes.values.mapNotNull { it.selectFirst(predicate) }.minBy { it.startTime }
117 }
118
119
120 /**
121 * Find the first slice that matches the given predicate.
122 *
123 * @param predicate The predicate used to test slices
124 */
ProcessModelnull125 fun ProcessModel.selectFirst(predicate: (Slice) -> Boolean) : Slice? {
126 var ret : Slice? = null
127
128 this.asyncSlices.forEach {
129 if (predicate(it)) {
130 ret = it
131 return@forEach
132 }
133 }
134
135 this.threads.forEach { thread ->
136 val threadSlice = thread.selectFirst(predicate)
137
138 if (threadSlice != null && (ret == null || threadSlice.startTime < ret!!.startTime)) {
139 ret = threadSlice
140 }
141 }
142
143 return ret
144 }
145
146
147 /**
148 * Find the first slice that matches the given predicate.
149 *
150 * @param predicate The predicate used to test slices
151 */
selectFirstnull152 fun ThreadModel.selectFirst(predicate : (Slice) -> Boolean) : Slice? {
153 var ret : Slice? = null
154 this.iterSlices {
155 if (predicate(it)) {
156 ret = it
157 return@iterSlices
158 }
159 }
160 return ret
161 }
162
163 /**
164 * This function is a more powerful version of [iterSlices]. It takes a [SliceTraverser], which
165 * allows code to be run at the beginning and end of processing a slice. This allows the
166 * [SliceTraverser] to, for example, keep track of how deep it is within the tree. The
167 * [SliceTraverser] can also indicate whether [traverseSlices] should continue processing child
168 * slices in the case of a [SliceGroup].
169 */
traverseSlicesnull170 fun Model.traverseSlices(visitor: SliceTraverser) {
171 this.processes.values.forEach { it.traverseSlices(visitor) }
172 }
173
174 /**
175 * This function is a more powerful version of [iterSlices]. It takes a [SliceTraverser], which
176 * allows code to be run at the beginning and end of processing a slice. This allows the
177 * [SliceTraverser] to, for example, keep track of how deep it is within the tree. The
178 * [SliceTraverser] can also indicate whether [traverseSlices] should continue processing child
179 * slices in the case of a [SliceGroup].
180 */
ProcessModelnull181 fun ProcessModel.traverseSlices(visitor: SliceTraverser) {
182 this.asyncSlices.forEach {
183 visitor.beginSlice(it)
184 visitor.endSlice(it)
185 }
186
187 this.threads.forEach { it.traverseSlices(visitor) }
188 }
189
190 /**
191 * This function is a more powerful version of [iterSlices]. It takes a [SliceTraverser], which
192 * allows code to be run at the beginning and end of processing a slice. This allows the
193 * [SliceTraverser] to, for example, keep track of how deep it is within the tree. The
194 * [SliceTraverser] can also indicate whether [traverseSlices] should continue processing child
195 * slices in the case of a [SliceGroup].
196 */
traverseSlicesnull197 fun ThreadModel.traverseSlices(visitor: SliceTraverser) {
198 this.slices.traverseSlices(visitor)
199 }
200
201 /**
202 * This function is a more powerful version of [iterSlices]. It takes a [SliceTraverser], which
203 * allows code to be run at the beginning and end of processing a slice. This allows the
204 * [SliceTraverser] to, for example, keep track of how deep it is within the tree. The
205 * [SliceTraverser] can also indicate whether [traverseSlices] should continue processing child
206 * slices in the case of a [SliceGroup].
207 */
traverseSlicesnull208 fun List<SliceGroup>.traverseSlices(visitor: SliceTraverser) {
209 this.forEach {
210 val action = visitor.beginSlice(it)
211 if (action == TraverseAction.VISIT_CHILDREN) {
212 it.children.traverseSlices(visitor)
213 }
214 visitor.endSlice(it)
215 }
216 }
217
218 /**
219 * Call the provided lambda on every slice in the model.
220 */
Modelnull221 fun Model.iterSlices(consumer: (Slice) -> Unit) {
222 this.processes.values.forEach { it.iterSlices(consumer) }
223 }
224
225 /**
226 * Call the provided lambda on every slice in the model.
227 */
ProcessModelnull228 fun ProcessModel.iterSlices(consumer: (Slice) -> Unit) {
229 this.asyncSlices.forEach { consumer(it) }
230 this.threads.forEach { it.iterSlices(consumer) }
231 }
232
233 /**
234 * Call the provided lambda on every slice in the model.
235 */
ThreadModelnull236 fun ThreadModel.iterSlices(consumer: (Slice) -> Unit) {
237 this.slices.iterSlices(consumer)
238 }
239
240 /**
241 * Call the provided lambda on every slice in the model.
242 */
Listnull243 fun List<SliceGroup>.iterSlices(consumer: (Slice) -> Unit) {
244 this.forEach {
245 consumer(it)
246 it.children.iterSlices(consumer)
247 }
248 }
249
250 /**
251 * Call the provided lambda on every slice in the list.
252 */
Listnull253 fun List<SliceGroup>.any(predicate: (Slice) -> Boolean): Boolean {
254 this.forEach {
255 if (predicate(it)) return true
256 if (it.children.any(predicate)) return true
257 }
258 return false
259 }
260
261 /**
262 * Find the first slice that meets the provided criteria.
263 *
264 * @param queryType The "type" of the slice being searched for
265 * @param queryValue The "value" of the slice being searched for
266 * @param lowerBound Slice must occur after this timestamp
267 * @param upperBound Slice must occur before this timestamp
268 *
269 * @throws MissingSliceException If no matching slice is found.
270 */
ProcessModelnull271 fun ProcessModel.findFirstSlice(queryType : String,
272 queryValue : String?,
273 lowerBound : Double = this.model.beginTimestamp,
274 upperBound : Double = this.model.endTimestamp) : Slice {
275
276 val foundSlice = this.findFirstSliceOrNull(queryType, queryValue, lowerBound, upperBound)
277
278 if (foundSlice != null) {
279 return foundSlice
280 } else if (lowerBound == this.model.beginTimestamp && upperBound == this.model.endTimestamp) {
281 throw MissingSliceException(this.id, queryType, queryValue)
282 } else {
283 throw MissingSliceException(this.id, queryType, queryValue, lowerBound, upperBound)
284 }
285 }
286
287 /**
288 * Find the first slice that meets the provided criteria.
289 *
290 * @param pattern A pattern the slice text must match
291 * @param lowerBound Slice must occur after this timestamp
292 * @param upperBound Slice must occur before this timestamp
293 *
294 * @throws MissingSliceException If no matching slice is found.
295 */
ProcessModelnull296 fun ProcessModel.findFirstSlice(pattern : Regex,
297 lowerBound : Double = this.model.beginTimestamp,
298 upperBound : Double = this.model.endTimestamp) : Slice {
299
300 val foundSlice = this.findFirstSliceOrNull(pattern, lowerBound, upperBound)
301
302 if (foundSlice != null) {
303 return foundSlice
304 } else if (lowerBound == this.model.beginTimestamp && upperBound == this.model.endTimestamp) {
305 throw MissingSliceException(this.id, pattern)
306 } else {
307 throw MissingSliceException(this.id, pattern, lowerBound, upperBound)
308 }
309 }
310
311 /**
312 * Find the first slice that meets the provided criteria.
313 *
314 * @param queryType The "type" of the slice being searched for
315 * @param queryValue The "value" of the slice being searched for
316 * @param lowerBound Slice must occur after this timestamp
317 * @param upperBound Slice must occur before this timestamp
318 *
319 * @return Slice or null if no match is found.
320 */
ProcessModelnull321 fun ProcessModel.findFirstSliceOrNull(queryType : String,
322 queryValue : String?,
323 lowerBound : Double = this.model.beginTimestamp,
324 upperBound : Double = this.model.endTimestamp) : Slice? {
325
326 return this.selectFirst { slice ->
327 val sliceInfo = parseSliceName(slice.name)
328
329 sliceInfo.name == queryType &&
330 (queryValue == null || sliceInfo.value == queryValue) &&
331 lowerBound <= slice.startTime &&
332 slice.startTime <= upperBound
333 }
334 }
335
336 /**
337 * Find the first slice that meets the provided criteria.
338 *
339 * @param pattern A pattern the slice text must match
340 * @param lowerBound Slice must occur after this timestamp
341 * @param upperBound Slice must occur before this timestamp
342 *
343 * @return Slice or null if no match is found.
344 */
ProcessModelnull345 fun ProcessModel.findFirstSliceOrNull(pattern : Regex,
346 lowerBound : Double = this.model.beginTimestamp,
347 upperBound : Double = this.model.endTimestamp) : Slice? {
348
349 return this.selectFirst { slice ->
350 pattern.find(slice.name) != null &&
351 lowerBound <= slice.startTime &&
352 slice.startTime <= upperBound
353 }
354 }
355
356 /**
357 * Find all slices that meet the provided criteria.
358 *
359 * @param queryType The "type" of the slice being searched for
360 * @param queryValue The "value" of the slice being searched for
361 * @param lowerBound Slice must occur after this timestamp
362 * @param upperBound Slice must occur before this timestamp
363 *
364 * @throws MissingSliceException If no matching slice is found.
365 */
ProcessModelnull366 fun ProcessModel.findAllSlices(queryType : String,
367 queryValue : String?,
368 lowerBound : Double = this.model.beginTimestamp,
369 upperBound : Double = this.model.endTimestamp) : List<Slice> {
370
371 return this.selectAll{ slice ->
372 val sliceInfo = parseSliceName(slice.name)
373
374 sliceInfo.name == queryType &&
375 (queryValue == null || sliceInfo.value == queryValue) &&
376 lowerBound <= slice.startTime &&
377 slice.startTime <= upperBound
378 }
379 }
380
381 /**
382 * Find all slices that meet the provided criteria.
383 *
384 * @param pattern A pattern the slice text must match
385 * @param lowerBound Slice must occur after this timestamp
386 * @param upperBound Slice must occur before this timestamp
387 *
388 * @throws MissingSliceException If no matching slice is found.
389 */
ProcessModelnull390 fun ProcessModel.findAllSlices(pattern : Regex,
391 lowerBound : Double = this.model.beginTimestamp,
392 upperBound : Double = this.model.endTimestamp) : List<Slice> {
393
394 return this.selectAll { slice ->
395 pattern.find(slice.name) != null &&
396 lowerBound <= slice.startTime &&
397 slice.startTime <= upperBound
398 }
399 }
400
yieldSlicesnull401 private suspend fun SequenceScope<Slice>.yieldSlices(slices: List<SliceGroup>) {
402 slices.forEach {
403 yield(it)
404 yieldSlices(it.children)
405 }
406 }
407
slicesnull408 fun Model.slices(includeAsync: Boolean = true): Sequence<Slice> {
409 val model = this
410 return sequence {
411 model.processes.values.forEach { process ->
412 if (includeAsync) {
413 yieldAll(process.asyncSlices)
414 }
415 process.threads.forEach { thread ->
416 yieldSlices(thread.slices)
417 }
418 }
419 }
420 }