1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
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  *      http://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 androidx.benchmark
18 
19 import androidx.annotation.RestrictTo
20 import androidx.benchmark.traceprocessor.Insight
21 import androidx.benchmark.traceprocessor.PerfettoTrace
22 
23 /**
24  * Create a benchmark markdown trace deeplink
25  *
26  * LinkFormat isn't a TraceProcessor concept, which is why this functionality is here
27  */
28 internal fun PerfettoTrace.Link.benchmarkMarkdownLink(linkFormat: LinkFormat): String =
29     when (linkFormat) {
30         LinkFormat.V2 -> Markdown.createFileLink(label = title, path = path)
31         LinkFormat.V3 -> markdownUriLink
32     }
33 
34 /**
35  * Create a insight category title markdown link
36  *
37  * LinkFormat isn't a TraceProcessor concept, which is why this functionality is here
38  */
Insightnull39 private fun Insight.Category.header(linkFormat: LinkFormat): String =
40     if (linkFormat == LinkFormat.V3 && titleUrl != null) {
41         Markdown.createLink(title, titleUrl!!)
42     } else {
43         title
44     } + postTitleLabel
45 
toObservednull46 private fun List<Insight>.toObserved(linkFormat: LinkFormat): String {
47     return this.joinToString(separator = " ", prefix = "seen in iterations: ") { insight ->
48         insight.traceLink.benchmarkMarkdownLink(linkFormat) + "(${insight.observedLabel})"
49     }
50 }
51 
52 /**
53  * Create a list of [InsightSummary]s, each summarizing multiple [Insight]s with the same
54  * [Insight.Category]
55  */
56 @RestrictTo(RestrictTo.Scope.LIBRARY)
Listnull57 fun List<Insight>.createInsightSummaries(): List<InsightSummary> {
58     return this.groupBy { it.category }
59         .map { (category, insights) ->
60             InsightSummary(
61                 category,
62                 insights.sortedBy {
63                     // sort by iteration (embedded in title)
64                     it.traceLink.title.toInt()
65                 }
66             )
67         }
68 }
69 
70 /**
71  * Represents an insight into performance issues detected during a benchmark.
72  *
73  * Provides details about the specific criterion that was violated, along with information about
74  * where and how the violation was observed.
75  *
76  * @param category A description of the performance issue, including the expected behavior and any
77  *   relevant thresholds.
78  * @param observedV2 Specific details about when and how the violation occurred, such as the
79  *   iterations where it was observed and any associated values. Uses [LinkFormat.V2].
80  * @param observedV3 Specific details about when and how the violation occurred, such as the
81  *   iterations where it was observed and any associated values. Uses [LinkFormat.V3] to link more
82  *   precisely into traces.
83  *
84  * TODO(364598145): generalize
85  */
86 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
87 data class InsightSummary(
88     val category: String,
89     val observedV2: String,
90     val observedV3: String,
91 ) {
92     constructor(
93         category: Insight.Category,
94         insights: List<Insight>
95     ) : this(
96         category = category.header(LinkFormat.V3), // TODO: avoid this format hard coding!
97         observedV2 = insights.toObserved(LinkFormat.V2),
98         observedV3 = insights.toObserved(LinkFormat.V3)
99     ) {
100         require(insights.isNotEmpty())
<lambda>null101         require(insights.all { it.category == category })
102     }
103 }
104