/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * 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
 *
 *      http://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.
 */

syntax = "proto2";

package perfetto.protos;

import "protos/perfetto/perfetto_sql/structured_query.proto";

// The spec for a v2 trace-based metric.
//
// Conceptually, a v2 trace-based metric is very similar to metrics in other
// analytics system: it corresponds to a "value", some numerical property of
// the trace which can be measured and a set of "dimensions" which correspond to
// extra context about that value. Metrics also have an "id" which uniquely
// identifies them within a single trace summary.
//
// Finally, the `query` field specified how trace processor should compute the
// metric from the trace. We use the standard `PerfettoSqlStructuredQuery` proto
// for this: please see the documentation there for more details on writing it.
//
// For a simple example: suppose you wanted to average memory usage broken down
// by process name. Since the PerfettoSQL Standard Library already has
// primitives for this, this is easily accomplished with the following spec:
//
// ```
// id: "memory_per_process"
// dimensions: "process_name"
// value: "avg_rss_and_swap"
// query: {
//   table: {
//     table_name: "memory_rss_and_swap_per_process"
//     module_name: "linux.memory.process"
//   }
//   group_by: {
//     column_names: "process_name"
//     aggregates: {
//       column_name: "rss_and_swap"
//       op: DURATION_WEIGHTED_MEAN
//       result_column_name: "avg_rss_and_swap"
//     }
//   }
// }
// ```
//
// A common usecase is to restrict the period of interest to only certain time
// periods of interest, for example, only the time spaned by a test run or a
// Critical User Journey (CUJ). We can use the `interval_intersect` operation
// for this.
//
// Suppose the CUJ of interest was represented by a slice matched by the glob
// `<J>Cuj*`. The spec would look like:
//
// ```
// id: "memory_per_process_and_cuj"
// dimensions: "process_name"
// dimensions: "cuj_name"
// value: "avg_rss_and_swap"
// query: {
//   interval_intersect: {
//      base: {
//        table: {
//          table_name: "memory_rss_and_swap_per_process"
//          module_name: "linux.memory.process"
//        }
//      }
//      interval_intersect: {
//        simple_slices: {
//          slice_name_glob: "<J>Cuj*"
//        }
//        select_columns: {
//          column_name: "slice_name"
//          alias: "cuj_name"
//        }
//      }
//   }
//   group_by: {
//     column_names: "process_name"
//     column_names: "cuj_name"
//     aggregates: {
//       column_name: "rss_and_swap"
//       op: DURATION_WEIGHTED_MEAN
//       result_column_name: "avg_rss_and_swap"
//     }
//   }
// }
// ```
//
// A more complex example might: suppose you wanted to find the total CPU time
// of the `foo` slice in the `bar` thread while the `baz` CUJ (represented by
// a slice in `system_server`) was happening. You can accomplish that with the
// spec:
// ```
// id: "sum_foo_cpu_time_during_baz"
// value: "sum_cpu_time"
// query: {
//   interval_intersect: {
//      base: {
//        table: {
//          table_name: "thread_slice_cpu_time"
//          module_name: "linux.memory.process"
//        }
//        filters: {
//          column_name: "thread_name"
//          op: EQUAL
//          string_rhs: "bar"
//        }
//      }
//      interval_intersect: {
//        simple_slices: {
//          slice_name_glob: "baz"
//          process_name_glob: "system_server"
//        }
//      }
//   }
//   group_by: {
//     aggregates: {
//       column_name: "cpu_time"
//       op: SUM
//       result_column_name: "sum_cpu_time"
//     }
//   }
// }
// ```
//
//
// Note: if you are familiar with v1 trace-based metrics, there is a pretty big
// difference between the two: while v1 metrics were very flexible with respect
// to their output schema, v2 metrics give up that flexibility in exchange for
// being able to build general pupose systems which consume the result of
// metrics. This makes it possible e.g. to have an automatic metric viewer in
// the Perfetto UI visualizing the results of running a metric.
//
// Next id: 12
message TraceMetricV2Spec {
  // The id of the metric. Required.
  //
  // An opaque field but the convention is to use lowecase + underscores (i.e.
  // foo_bar). Note that this is not enforced.
  optional string id = 1;

  // The columns from `query` which will act as the "dimensions" for the metric.
  // Optional.
  //
  // Either `dimensions` or `dimensions_specs` should be defined, but not both.
  // If you only need to specify the dimension names and have their types
  // inferred from the SQL column, use `dimensions`. For scenarios where you
  // must explicitly specify the type for each dimension, use
  // `dimensions_specs`.
  enum DimensionType {
    DIMENSION_TYPE_UNSPECIFIED = 0;
    STRING = 1;
    INT64 = 2;
    DOUBLE = 3;
    BOOLEAN = 4;
  }
  message DimensionSpec {
    // The name of the dimension. Required.
    optional string name = 1;
    // The type of the dimension. If specified, this must match the SQL type
    // of the column. Required.
    optional DimensionType type = 2;
    // A human-readable name for the dimension, used in user interfaces.
    // Optional.
    optional string display_name = 3;
    // Additional help text or a description for the dimension, providing more
    // context. Optional.
    optional string display_help = 4;
    // A URL linking to more detailed documentation about this dimension.
    // Optional.
    optional string doc_link = 5;
  }
  repeated DimensionSpec dimensions_specs = 5;
  repeated string dimensions = 2;

  // The column from `query` which will act as the "value" for the metric.
  // Required.
  //
  // This must be a column containing only integers/doubles/nulls. Strings are
  // *not* supported: prefer making the string a dimension and then *counting*
  // the number of strings as the value.
  optional string value = 3;

  // The structured query which will be used to compute the metric. Required.
  //
  // See the documentation of `PerfettoSqlStructuredQuery` for more information.
  optional PerfettoSqlStructuredQuery query = 4;

  // Whether or not the dimensions in the metric are unique. Optional.
  //
  // Rephrasing the above, this option specifies, whether for a given set of
  // dimensions, there is at most one `value` corresponding to that set of
  // dimensions. If `UNIQUE` is chosen, trace processor will validate that
  // the metric adheres to this constraint and will fail if it does not.
  //
  // `NOT_UNIQUE` is useful in situations whether the consumer of the metric
  // wants to aggregate *across traces* (e.g. compute a median or mean across
  // all rows in all traces). `UNIQUE` is instead useful when you want to get
  // a "single value" out of each trace with a different layer of aggregation
  // across traces (e.g. compute a SUM for a trace and then an average across
  // traces).
  //
  // If not specified, the default is `NOT_UNIQUE`.
  enum DimensionUniqueness {
    DIMENSION_UNIQUENESS_UNSPECIFIED = 0;
    // The dimensions are not unique, i.e. the same dimension can appear in
    // multiple rows.
    NOT_UNIQUE = 1;
    // The dimensions are unique, i.e. each row corresponds to a unique set of
    // dimensions.
    UNIQUE = 2;
  }
  optional DimensionUniqueness dimension_uniqueness = 6;

  // The unit of the metric's value. Optional.
  //
  // This allows for specifying either a standard, predefined unit from the
  // `MetricUnit` enum or a custom unit as a string. This helps in consistently
  // interpreting and displaying metric values across different tools and
  // surfaces.
  enum MetricUnit {
    // Next value: 33
    METRIC_UNIT_UNSPECIFIED = 0;
    // A unitless count of items or events.
    COUNT = 1;
    // Time measured in nanoseconds.
    TIME_NANOS = 2;
    // Time measured in microseconds.
    TIME_MICROS = 3;
    // Time measured in milliseconds.
    TIME_MILLIS = 4;
    // Time measured in seconds.
    TIME_SECONDS = 5;
    // Time measured in hours.
    TIME_HOURS = 6;
    // Time measured in days.
    TIME_DAYS = 7;
    // Data size measured in bytes.
    BYTES = 8;
    // Data size measured in kilobytes (1024 bytes).
    KILOBYTES = 9;
    // Data size measured in megabytes (1024*1024 bytes).
    MEGABYTES = 10;
    // A rate measured in seconds per hour.
    SECONDS_PER_HOUR = 11;
    // A percentage value bounded between 0 and 100.
    BOUNDED_PERCENTAGE = 12;
    // A generic percentage value.
    PERCENTAGE = 13;
    // A time duration measured in minutes per day.
    MINUTES_PER_DAY = 14;
    // Electric current measured in milliamps.
    MILLI_AMPS = 15;
    // A rate of change measured in percent per hour.
    PERCENT_PER_HOUR = 16;
    // Electric charge measured in milliamp-hours.
    MILLI_AMP_HOURS = 17;
    // A legacy rate of change measured in percent per hour.
    PERCENT_PER_HOUR_LEGACY = 18;
    // Power measured in milliwatts.
    MILLI_WATTS = 19;
    // A frequency measured in counts per second.
    COUNT_PER_SECOND = 20;
    // A rate measured in kilobytes per hour.
    KILOBYTES_PER_HOUR = 21;
    // Energy measured in milliwatt-hours.
    MILLI_WATT_HOURS = 22;
    // A frequency measured in counts per hour.
    COUNT_PER_HOUR = 23;
    // A rate of change in count measured per hour.
    COUNT_DELTA_PER_HOUR = 24;
    // A rate of change in data size measured in bytes per hour.
    BYTES_DELTA_PER_HOUR = 25;
    // A statistical correlation coefficient, typically between -1 and 1.
    CORRELATION_COEFFICIENT = 26;
    // Electric potential measured in millivolts.
    MILLI_VOLTS = 27;
    // Temperature measured in Celsius.
    CELSIUS = 28;
    // Electric charge measured in microamp-hours.
    MICRO_AMP_HOURS = 29;
    // Time measured in minutes.
    TIME_MINS = 30;
    // Signal strength measured in dBm (decibels relative to a milliwatt).
    DECIBEL_MILLIWATTS = 31;
    // A rate measured in kilobytes per second.
    KILOBYTES_PER_SECOND = 32;
  }
  oneof unit_oneof {
    // A standard, predefined unit for the metric's value.
    MetricUnit unit = 8;
    // A custom string-defined unit for when a predefined one is not suitable.
    string custom_unit = 9;
  }

  // Describes what a change in the metric's value implies. Optional.
  //
  // This helps determine whether an increase or decrease in the metric's value
  // is a regression or an improvement, which is crucial for automated
  // analysis and reporting.
  enum MetricPolarity {
    // Next value: 4
    POLARITY_UNSPECIFIED = 0;
    // An increase in the metric's value is considered an improvement.
    HIGHER_IS_BETTER = 1;
    // A decrease in the metric's value is considered an improvement.
    LOWER_IS_BETTER = 2;
    // The metric's value is informational and has no inherent "better" or
    // "worse" direction.
    NOT_APPLICABLE = 3;
  }
  optional MetricPolarity polarity = 10;

  // =============================================
  // Advanced features: HERE BE DRAGONS
  //
  // Please be careful when using the features below. They are complex and
  // could lead to unexpected results due to subtle, potentially unexpected
  // implications.
  // =============================================

  // Sets the id of the bundle this metric will be a part of in the output.
  //
  // It's *strongly* recommended to use the automatic bundling of metrics
  // generated by `TraceMetricV2TemplateSpec` instead of setting this field
  // manually.
  //
  // This is because using template automatically causes all the preconditions
  // of bundling to be satisfied, i.e. all metrics generated by the template
  // will have the same `query`, `dimensions` and `dimension_uniqueness`. If you
  // set this field manually, you must ensure that all the metrics in the bundle
  // have the same `query`, `dimensions` and `dimension_uniqueness` as well.
  //
  // This field exists as an escape hatch for cases where templates for some
  // reason do not work for you.
  optional string bundle_id = 7;

  // !!! THIS IS AN ADVANCED FEATURE !!!
  //
  // Specification for a interned dimension associated with this metric.
  // For a given dimension, there can only be one interned dimension attached to
  // it.
  // Interned dimensions are useful for providing additional information about
  // a dimension without adding that information as a dimension to every metric
  // row. This is particularly useful when you have columns associated with a
  // dimension that would otherwise blow up the proto size due to repetition. By
  // using interned dimensions, you can emit only the necessary keys, reducing
  // data redundancy.
  //
  // For example, if a metric is dimensioned by package name, you can use an
  // interned dimension to provide the APK version code for each package without
  // adding the version code as a dimension to every row.
  //
  // Note: To optimize the output size, only the keys that are actually present
  // in the main metric query's output will be included in the interned
  // dimension bundles.
  //
  // The query for an interned dimension must return a unique row per dimension
  // value, as only one row per input dimension is allowed.
  //
  // Example:
  // ```
  // id: "my_metric"
  // value: "dur"
  // dimensions_specs { name: "package_name" type: STRING }
  // query { ... }
  // interned_dimension_specs {
  //   key_column_spec { name: "package_name" type: STRING }
  //   data_column_specs { name: "version_code" type: INT64 }
  //   query {
  //     sql {
  //       sql: "SELECT name as package_name, version_code FROM package_list"
  //     }
  //   }
  // }
  // ```
  //
  // Next id: 4
  message InternedDimensionSpec {
    // Spec for a interned dimension column.
    //
    // Next id: 3
    message ColumnSpec {
      // The name of the column (e.g. name, version_code, etc.). Required.
      optional string name = 1;
      // The type of the column. Required.
      // This must match the SQL type of the column.
      optional DimensionType type = 2;
    }

    // Spec for column in |dimensions_spec| which acts as key for this
    // interned dimension. Required.
    optional ColumnSpec key_column_spec = 1;
    // The data columns of the interned dimension query's output. Required.
    repeated ColumnSpec data_column_specs = 2;
    // The query to run to obtain interned dimension. Required.
    optional PerfettoSqlStructuredQuery query = 3;
  }
  repeated InternedDimensionSpec interned_dimension_specs = 11;
}

// A template for generating multiple v2 trace-based metrics.
//
// Often when people start writing more complex metrics, they find that
// they are repeating themselves a lot (i.e. specifying the same query and set
// of dimensions in multiple queries). This proto is intended to help with that:
// it allows you to specify a set of metrics which share a common id prefix,
// common set of dimensions and SQL. The template will then generate a set of
// metrics each with different values.
//
// Note that using a template automatically causes the metrics to be "bundled"
// together in the output, i.e. all metrics will for a given template will be
// emitted in a single `TraceMetricV2Bundle` proto. This can be disabled by
// setting `disable_auto_bundling` to true.
//
// Next id: 10
message TraceMetricV2TemplateSpec {
  // The prefix for all metric ids generated by this template. The actual id
  // for each metric will be `<id_prefix>_<values>`.
  //
  // See `TraceMetricV2Spec.id` for more information.
  optional string id_prefix = 1;

  // The shared set of dimensions for all metrics generated by this template.
  // See `TraceMetricV2Spec.dimension_specs` and `TraceMetricV2Spec.dimensions`
  // for more details.
  //
  // Either `dimensions` or `dimensions_specs` should be defined, but not both.
  // If you only need to specify the dimension names and have their types
  // inferred from the SQL column, use `dimensions`. For scenarios where you
  // must explicitly specify the type for each dimension, use
  // `dimensions_specs`.
  repeated TraceMetricV2Spec.DimensionSpec dimensions_specs = 5;
  repeated string dimensions = 2;

  // A list of columns from `query` which each of which will generate a unique
  // metric. The id of each metric will be `<id_prefix>_<value_column>`.
  //
  // See `TraceMetricV2Spec.value` for more information.
  //
  // Either `value_columns` or `value_column_specs` should be defined, but not
  // both. If you only need to specify the column names without any advanced
  // properties like units or polarity, use this field. For more complex
  // scenarios, use `value_column_specs`.
  repeated string value_columns = 3;

  // A list of specifications for value columns, allowing each to have a
  // unique unit and polarity.
  //
  // Either `value_columns` or `value_column_specs` should be defined, but not
  // both. Use this field if you need to specify units, polarity, or other
  // properties for each value column.
  message ValueColumnSpec {
    // The name of the column from the query result to be used as the value.
    // Required.
    optional string name = 1;

    // The unit for this specific value column. This will be copied into the
    // `unit` or `custom_unit` field of the generated `TraceMetricV2Spec`.
    oneof unit_oneof {
      TraceMetricV2Spec.MetricUnit unit = 2;
      string custom_unit = 3;
    }

    // The polarity for this specific value column. This will be copied into
    // the `polarity` field of the generated `TraceMetricV2Spec`.
    optional TraceMetricV2Spec.MetricPolarity polarity = 4;
    // A human-readable name for the value column, used in user interfaces.
    // Optional.
    optional string display_name = 5;
    // Additional help text or a description for the value column, providing
    // more context. Optional.
    optional string display_help = 6;
    // A URL linking to more detailed documentation about this value column.
    // Optional.
    optional string doc_link = 7;

    // Vendor extensions. These might be used for system-specific
    // configuration on how to manage aggregation across traces.
    // See https://perfetto.dev/docs/analysis/trace-summary#extending
    extensions 500 to 1000;
  }
  repeated ValueColumnSpec value_column_specs = 8;

  // Specifications for interned dimension bundles associated with the metrics
  // generated by this template. Each InternedDimensionSpec defines a query to
  // fetch additional information keyed by one of the metric's dimensions.
  repeated TraceMetricV2Spec.InternedDimensionSpec interned_dimension_specs = 9;

  // The structured query which will be used to compute all the metrics.
  //
  // See `TraceMetricV2Spec.query` for more information.
  optional PerfettoSqlStructuredQuery query = 4;

  // Whether or not the dimensions in the metrics are unique.
  // See `TraceMetricV2Spec.dimension_uniqueness` for more information.
  optional TraceMetricV2Spec.DimensionUniqueness dimension_uniqueness = 6;

  // If true, the metrics generated by this template will not be bundled
  // together in the output. Instead, each metric will be emitted in its own
  // `TraceMetricV2Bundle` proto.
  //
  // Under the hood, this is equivalent to setting the `bundle_id` field
  // in each `TraceMetricV2Spec` generated by this template to a unique value
  // (i.e. the id of the metric itself). This is useful if you want to
  // generate a set of metrics which are computed togther out of convenience
  // but you want to consume them separately in the output.
  optional bool disable_auto_bundling = 7;
}

// The output containing all the values for a one or more v2 trace-based metric
// which are bundled together.
//
// Note: see `TraceMetricV2Spec` for commentary on what a trace-based metric
// is.
//
// For the `memory_per_process` example above, the output proto might look
// something like:
// ```
// row: {
//   values: {
//     double_value: 123456
//   }
//   dimensions: {
//     string_value: "my_special_process"
//   }
// }
// row: {
//   values: {
//     double_value: 9876
//   }
//   dimensions: {
//     string_value: "/bin/init"
//   }
// }
// specs {
//   id: "memory_per_process"
//   dimensions: "process_name"
//   value: "rss_and_swap"
//   query: {
//     table: {
//       table_name: "memory_rss_and_swap_per_process"
//       module_name: "linux.memory.process"
//     }
//   }
// }
// ```
//
// And for the `memory_per_process_and_cuj` example:
// ```
// row: {
//   values: {
//     value: 123456
//   }
//   dimensions: {
//     string_value: "<J>CujFoo"
//     string_value: "my_special_process"
//   }
// }
// row: {
//   values: {
//     value: 9876
//   }
//   dimensions: {
//     string_value: "<J>CujBar"
//     string_value: "/bin/init"
//   }
// }
// specs {
//   ...(contents of spec)
// }
// ```
//
// Next id: 5
message TraceMetricV2Bundle {
  // The id of the bundle.
  //
  // Useful for debugging purposes only, it's rare this is meaningful to anyone.
  optional string bundle_id = 1;

  // A row corresponding to n values associated with a set of dimensions.
  //
  // Depending on `dimension_uniqueness` in the spec, this might be the only row
  // for this set of dimensions or there might be other rows as well.
  message Row {
    // The value of all metrics in the bundle associated with the `dimensions`.
    //
    // The order of values matches precisely the order of metrics given by
    // the `specs`.
    //
    // Note: if *all* values in the row are null, the row will *not* be emitted
    // in the output. That is, you can always count on at least one of the
    // `values` being non-null.
    message Value {
      message Null {}
      oneof value_oneof {
        Null null_value = 1;
        double double_value = 2;
      }
    }
    repeated Value values = 1;

    // The dimensions that `value` should be associated with. The order of
    // dimensions matches precisely the order of dimension names given by the
    // `spec`.
    //
    // The type of the dimension is either inferred from the SQL column type
    // or specified in the `spec.dimensions_specs`. In case `dimensions_specs`
    // is specified, the SQL column type must match the type specified
    // in the spec.
    message Dimension {
      message Null {}
      oneof value_oneof {
        string string_value = 1;
        int64 int64_value = 2;
        double double_value = 3;
        Null null_value = 4;
        bool bool_value = 5;
      }
    }
    repeated Dimension dimension = 2;
  }
  repeated Row row = 2;

  // The spec for the metric.
  //
  // If the spec was directly specified when computing the metric, it is simply
  // an echo of that spec. However, if the metric spec was generated from a
  // template, it will be the "expanded" version of the spec
  //
  // Useful for knowing what the dimension names/value names are.
  repeated TraceMetricV2Spec specs = 3;

  // !!! THIS IS AN ADVANCED FEATURE !!!
  // Most users do not need to care about this proto.
  //
  // A bundle of interned dimension rows associated with one of template's
  // interned dimension specs.
  //
  // For the running example in `TraceMetricV2Spec`, if:
  // * The metric query returns `('com.foo', 10)` and `('com.bar', 20)` for
  // `(package_name, dur)`
  // * The `package_list` table contains `('com.foo', 100)` and
  // `('com.bar', 200)` for `(name, version_code)`
  //
  // Then for the `interned_dimension_spec` for `package_name`, the
  // corresponding metric bundle will be:
  // ```
  // metric_bundles {
  //   row {
  //     dimension {
  //       string_value: "com.foo"
  //     }
  //     values {
  //       double_value: 10
  //     }
  //   }
  //   row {
  //     dimension {
  //       string_value: "com.bar"
  //     }
  //     values {
  //       double_value: 20
  //     }
  //   }
  //   specs: { ... }
  //   interned_dimension_bundles {
  //     interned_dimension_rows {
  //       key_dimension_value {
  //         string_value: "com.foo"
  //       }
  //       data_dimension_values {
  //         int64_value: 100
  //       }
  //     }
  //     interned_dimension_rows {
  //       key_dimension_value {
  //         string_value: "com.bar"
  //       }
  //       data_dimension_values {
  //         int64_value: 200
  //       }
  //     }
  //   }
  // }
  // ```
  //
  // Next id: 4
  message InternedDimensionBundle {
    // A row of interned dimension, with values for columns specified in
    // |column_specs|.
    //
    // Next id: 3
    message InternedDimensionRow {
      // The key value in the row.
      optional TraceMetricV2Bundle.Row.Dimension key_dimension_value = 1;
      // The data values in the row.
      repeated TraceMetricV2Bundle.Row.Dimension interned_dimension_values = 2;
    }
    repeated InternedDimensionRow interned_dimension_rows = 2;
  }
  repeated InternedDimensionBundle interned_dimension_bundles = 4;
}
