1/* 2 * Copyright (C) 2025 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 17syntax = "proto2"; 18 19package perfetto.protos; 20 21import "protos/perfetto/perfetto_sql/structured_query.proto"; 22 23// The spec for a v2 trace-based metric. 24// 25// Conceptually, a v2 trace-based metric is very similar to metrics in other 26// analytics system: it corresponds to a "value", some numerical property of 27// the trace which can be measured and a set of "dimensions" which correspond to 28// extra context about that value. Metrics also have an "id" which uniquely 29// identifies them within a single trace summary. 30// 31// Finally, the `query` field specified how trace processor should compute the 32// metric from the trace. We use the standard `PerfettoSqlStructuredQuery` proto 33// for this: please see the documentation there for more details on writing it. 34// 35// For a simple example: suppose you wanted to average memory usage broken down 36// by process name. Since the PerfettoSQL Standard Library already has 37// primitives for this, this is easily accomplished with the following spec: 38// 39// ``` 40// id: "memory_per_process" 41// dimensions: "process_name" 42// value: "avg_rss_and_swap" 43// query: { 44// table: { 45// table_name: "memory_rss_and_swap_per_process" 46// module_name: "linux.memory.process" 47// } 48// group_by: { 49// column_names: "process_name" 50// aggregates: { 51// column_name: "rss_and_swap" 52// op: DURATION_WEIGHTED_MEAN 53// result_column_name: "avg_rss_and_swap" 54// } 55// } 56// } 57// ``` 58// 59// A common usecase is to restrict the period of interest to only certain time 60// periods of interest, for example, only the time spaned by a test run or a 61// Critical User Journey (CUJ). We can use the `interval_intersect` operation 62// for this. 63// 64// Suppose the CUJ of interest was represented by a slice matched by the glob 65// `<J>Cuj*`. The spec would look like: 66// 67// ``` 68// id: "memory_per_process_and_cuj" 69// dimensions: "process_name" 70// value: "avg_rss_and_swap" 71// query: { 72// interval_intersect: { 73// base: { 74// table: { 75// table_name: "memory_rss_and_swap_per_process" 76// module_name: "linux.memory.process" 77// } 78// } 79// interval_intersect: { 80// simple_slices: { 81// slice_name_glob: "<J>Cuj*" 82// } 83// select_columns: { 84// column_name: "slice_name" 85// alias: "cuj_name" 86// } 87// } 88// } 89// group_by: { 90// column_names: "process_name" 91// column_names: "cuj_name" 92// aggregates: { 93// column_name: "rss_and_swap" 94// op: DURATION_WEIGHTED_MEAN 95// result_column_name: "avg_rss_and_swap" 96// } 97// } 98// } 99// ``` 100// 101// A more complex example might: suppose you wanted to find the total CPU time 102// of the `foo` slice in the `bar` thread while the `baz` CUJ (represented by 103// a slice in `system_server`) was happening. You can accomplish that with the 104// spec: 105// ``` 106// id: "sum_foo_cpu_time_during_baz" 107// value: "sum_cpu_time" 108// query: { 109// interval_intersect: { 110// base: { 111// table: { 112// table_name: "thread_slice_cpu_time" 113// module_name: "linux.memory.process" 114// } 115// filters: { 116// column_name: "thread_name" 117// op: EQUAL 118// string_rhs: "bar" 119// } 120// } 121// interval_intersect: { 122// simple_slices: { 123// slice_name_glob: "baz" 124// process_name_glob: "system_server" 125// } 126// } 127// } 128// group_by: { 129// aggregates: { 130// column_name: "cpu_time" 131// op: SUM 132// result_column_name: "sum_cpu_time" 133// } 134// } 135// } 136// ``` 137// 138// 139// Note: if you are familiar with v1 trace-based metrics, there is a pretty big 140// difference between the two: while v1 metrics were very flexible with respect 141// to their output schema, v2 metrics give up that flexibility in exchange for 142// being able to build general pupose systems which consume the result of 143// metrics. This makes it possible e.g. to have an automatic metric viewer in 144// the Perfetto UI visualizing the results of running a metric. 145message TraceMetricV2Spec { 146 // The id of the metric. An opaque field but the convention is to use 147 // lowecase + underscores (i.e. foo_bar). Note however this is not enforced. 148 // Required. 149 optional string id = 1; 150 151 // The columns from `query` which will act as the "dimensions" for the metric. 152 // For a given set of dimensions, there must be exactly *one* value emitted. 153 // Optional. 154 repeated string dimensions = 2; 155 156 // The column from `query` which will act as the "value" for the metric. This 157 // must be a column containing only integers/doubles/nulls. Strings are *not* 158 // supported: prefer making the string a dimension and then *counting* the 159 // number of strings as the value. 160 // Required. 161 optional string value = 3; 162 163 // The structured query which will be used to compute the metric. See the 164 // documentation of `PerfettoSqlStructuredQuery` for more information. 165 // Required. 166 optional PerfettoSqlStructuredQuery query = 4; 167} 168 169// The output containing all the values for a single v2 trace-based metric. 170// 171// Note: see `TraceMetricV2Spec` for commentary on what a trace-based metric 172// is. 173// 174// For the `memory_per_process` example above, the output proto might look 175// something like: 176// ``` 177// row: { 178// value: 123456 179// dimensions: { 180// string_value: "my_special_process" 181// } 182// } 183// row: { 184// value: 9876 185// dimensions: { 186// string_value: "/bin/init" 187// } 188// } 189// spec { 190// id: "memory_per_process" 191// dimensions: "process_name" 192// value: "rss_and_swap" 193// query: { 194// table: { 195// table_name: "memory_rss_and_swap_per_process" 196// module_name: "linux.memory.process" 197// } 198// } 199// } 200// ``` 201// 202// And for the `memory_per_process_and_cuj` example: 203// ``` 204// row: { 205// value: 123456 206// dimensions: { 207// string_value: "<J>CujFoo" 208// string_value: "my_special_process" 209// } 210// } 211// row: { 212// value: 9876 213// dimensions: { 214// string_value: "<J>CujBar" 215// string_value: "/bin/init" 216// } 217// } 218// spec { 219// ...(contents of spec) 220// } 221// ``` 222// Note: if value of a row is NULL, the row will not be emitted. 223message TraceMetricV2 { 224 // A single metric row corresponding to a value associated with a (unique) set 225 // of dimensions 226 message MetricRow { 227 // The value of the metric associated with the `dimensions`. 228 optional double value = 1; 229 230 // The dimensions that `value` should be associated with. The order of 231 // dimensions matches precisely the order of dimension names given by the 232 // `spec`. 233 message Dimension { 234 message Null {} 235 oneof value_oneof { 236 string string_value = 1; 237 int64 int64_value = 2; 238 double double_value = 3; 239 Null null_value = 4; 240 } 241 } 242 repeated Dimension dimension = 2; 243 } 244 repeated MetricRow row = 1; 245 246 // The spec for the metric. This is simply an echo of the spec which was 247 // passed in to compute the metric. Useful for knowing what the dimension 248 // names/value names are. 249 optional TraceMetricV2Spec spec = 2; 250} 251