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 }