Metrics configuration generation request JSON format

This guide details the JSON request format for the /api/v1/generate_metrics_config endpoint of the Metrics Configuration Generator (MCG) tool. This format lets you define a telemetry campaign—specifying data collection, on-device processing, and report generation—in a human-readable structure.

The MCG tool validates this JSON object, checking for issues like type mismatches, circular dependencies, or undefined references, and then compiles it into a MetricsConfig protocol buffer (protobuf) message. This MetricsConfig message is the binary format that the on-device Telemetry service executes to run the campaign.

Prerequisites: You should be familiar with JSON schemas, protobuf, and basic data structures. For a conceptual overview, see Metrics configuration concepts.

How to approach writing a configuration description

To design a metrics collection campaign, follow this logical flow:

  1. Specify data sources: Define where your data comes from.
  2. Define logic and processing: Set up rules for when to collect data and how to process it.
  3. Create output: Package the processed data into a final message.
  4. Top-level fields: Add top-level fields like UUID, signal definitions, and lifecycle control.

Example: Average speed report

This guide uses an example to show how all the components fit together. It creates a report that calculates the average vehicle speed every minute. It defines a Data Source (SpeedSource) to collect speed data, Triggers (OnNewSpeed, EveryMinute) to control execution flow, an Aggregator (SpeedAggregator) to calculate the average, and a Metrics Report Configuration (MinuteReport) to package the result. Examples in expandable sections build on this scenario.

Specify data sources

Telemetry supports collecting data from SDV Services (via the SDV Middleware) and Configurable Publisher Registry-based publishers.

To use data in SDV, define a data source (concept) and add it to the data_sources array.

Fields of a data source object (item in data_sources list)
name A user-defined string that identifies this data source within the metrics configuration.
source_identifier The identifier used to discover the service. For details, see source_identifier format.
connection_type SUBSCRIPTION for a continuous data stream (required for data triggers), or ON_DEMAND for on-demand fetching (for details, see configuration of data sources).
optional
configuration

RPC and Configurable Publisher Registry only. An object to configure the data source with the following fields:

type_url The FQN of the protobuf message of the configuration.
value_json,
value_textproto,
or value
The payload in JSON, textproto, or base64 format.
Fields for SUBSCRIPTION connection type
optional
sub_sampling_interval_ms
Pub/sub only. A non-negative integer (milliseconds) to throttle message frequency by specifying the minimum interval between messages.
optional
fetch_last_message
(default: false)
Pub/sub only. Boolean. If true, retrieves the latest message upon connection.

Not all options are available for all types of data sources. See the data source integration guide for detailed information.

Format of source_identifier

The Telemetry service accepts multiple source identifier formats. See Data source definition in metrics configurations for an overview.

MCG infers the protobuf message type from the source_identifier only if it uses the Unit Type Name format. If you use FQIN or a custom name (from the Configurable Publisher Registry), MCG can't infer the type. In these cases, you must provide data_source_message_types. If the type isn't in your catalog, you must also provide its definition in descriptor_protos.

Example: Defining a data source

This example reports vehicle speed. First, consult the VSIDL catalog to identify the service that publishes this data.

Using the sample catalog in the SDV codebase, identify the relevant service. Consistent with the Format of source_identifier, specify the protobuf message type mcg.test.subpkg.speed_msg. Because speed is usually published frequently, use sub_sampling_interval_ms to limit updates to one message per second:

{
  "name": "SpeedSource",
  "source_identifier": "mcg.test.subpkg.speed_msg",
  "connection_type": "SUBSCRIPTION",
  "sub_sampling_interval_ms": 1000 // Limit updates to 1 per second
}

Define logic and processing

Logic is handled by two components working together: Triggers (concept) define when something happens. Aggregators (concept) define how data is processed based on those triggers. Because expressions (concept) are key to trigger conditions and aggregation logic, this section first describes how to write them.

Expressions

Expressions let you define calculations and conditions using a human-readable syntax. MCG compiles these strings into an executable format optimized for on-device evaluation.

Expressions can express arithmetic calculations, logical conditions, and data comparisons. For the complete syntax, including operators and functions, see Expressions syntax.

Data freshness: When an expression accesses a data source, the retrieval behavior depends on the connection type:

  • SUBSCRIPTION: Uses the last received message cached by the Telemetry service. (Note: If sub_sampling_interval_ms is set, this might differ from the absolute latest published message.)
  • ON_DEMAND: Triggers an immediate call to fetch the fresh message from the service.

Type constraints

  • Arrays: Direct array index access (for example, my_data_source.my_array[0]) isn't supported.
  • Type safety: Make sure you compare compatible types (for example, don't compare a string field to a list of integers).

Example: Expressions

The example needs to extract the speed value to compute its average. It also needs to determine if the vehicle is speeding (over 100 km/h) for the conditional trigger. You can find the field names (in this case speed) in the protobuf message definition used by the data source. The following expressions achieve this:

  • SpeedSource.speed: Retrieves the value of the speed field from the SpeedSource data source.
  • SpeedSource.speed > 27.7: Evaluates to true if the speed is greater than 27.7 m/s (approximately 100 km/h).

Triggers: Define when actions occur

Triggers define when actions execute: evaluating an aggregator, generating a report, or controlling the collection lifecycle. Add all defined triggers to the top-level triggers array.

Fields of a trigger object (item in triggers list)
name A unique identifier for this trigger in the metrics configuration.
periodic
data
conditional
You must provide exactly one of these fields to define the behavior.

Data trigger

Fires when a SUBSCRIPTION data source provides a new message (concept).

Fields of the data object (in a trigger)
source_name The name of the data_source that this trigger listens to.

Example: Data trigger

In the example, update the average speed calculation each time SpeedSource publishes a new message. This data trigger fires each time that happens:

{
  "name": "OnNewSpeed",
  "data": {
    "source_name": "SpeedSource" // Reference to the defined data source
  }
}

Periodic trigger

Fires at a regular interval (concept).

Fields of the periodic object (in a trigger)
period_ms A non-negative number that defines the interval in milliseconds.

Example: Periodic trigger

The example requires a report every minute. This periodic trigger fires every 60,000 ms to generate that report:

{
  "name": "EveryMinute",
  "periodic": {
    "period_ms": 60000 // 60,000 ms = 60 seconds
  }
}

Conditional trigger

Fires based on the evaluation of an expression (concept). It requires one or more parent triggers to start its evaluation. This is often a data trigger for the data sources or aggregators in the expression, or a periodic trigger to poll for data.

Fields of the conditional object (in a trigger)
triggers An array with at least one parent trigger name. When a parent trigger fires, the conditional trigger evaluates the expression.
expression The expression to evaluate. See Expressions.
condition_type Specifies when the trigger should fire, based on the expression's evaluation.
Condition types

The condition_type dictionary must contain exactly one of the available condition types as a key. For more information, see Conditional triggers.

Fields of the condition_type object (mutually exclusive)
is_true

is_false
Fires when a boolean expression evaluates to true or false, respectively.
rising_edge

If numeric, fires when its value increases. If the expression is boolean, fires when it changes from false to true.

Can contain a rising_options object (see Edge options).

falling_edge

If numeric, fires when its value decreases. If the expression is boolean, fires when it changes from true to false.

Can contain a falling_options object (see Edge options).

all_changes

Fires when the expression's result is different from its previous value. If edge options are set, it supports only numeric and boolean values.

Can contain rising_options, falling_options, or both (see Edge options).

Edge options

The rising_options and falling_options objects have the following fields. If provided, the corresponding transition must satisfy the duration requirement. Transitions without specified options fire immediately.

Fields for edge option objects
min_duration_ms A non-negative number (in milliseconds) specifying how long the condition must hold in the new state before the trigger fires.
optional
require_exact
Boolean. If true, all values published during the duration must be the same for the trigger to fire.

Example: Conditional trigger

The following trigger uses edge options. It fires if the speed exceeds 27.7 m/s and remains high for at least 5 seconds:

{
  "name": "SpeedingFor5Seconds",
  "conditional": {
    "triggers": ["OnNewSpeed"],
    "expression": "SpeedSource.speed > 27.7",
    "condition_type": {
      "rising_edge": {
        "rising_options": {
          "min_duration_ms": 5000 // Condition must hold for 5s in new state
        }
      }
    }
  }
}

Aggregators: Define how data is processed

Use an aggregator for stateful, intermediate data processing (concept). Define an aggregator and add it to the aggregators array.

An aggregator transforms data and makes it available to other aggregators and reports.

Fields of an aggregator (item in aggregators list)
name A user-defined string that identifies this aggregator. Expressions can reference this aggregator by name to access its results.
trigger_names An array of one or more trigger names that cause this aggregation to be evaluated.
optional
reset_on_get
Boolean, default: `false`. If `true`, the system resets the aggregation state after its value is accessed by another aggregator, metrics report or conditional trigger.
message_builder Defines the data to read, process, and aggregate. The output message's fields then become a data source for other aggregators and reports. It uses field_assignments, with each assignment defining a field in the output message.

Message builder

A message_builder object defines the output message structure and the aggregation logic used to calculate its fields. It is used in both Aggregators and Report Configurations.

Fields of a message_builder object (in aggregator or report)
message_type The fully qualified name of the protobuf message type for the output. If omitted, MCG creates a custom message type based on the field_assignments' inferred output types. Only use this if the message builder is part of a metrics report and your report must match a specific, predefined protobuf message definition.
field_assignments An array of field definition objects. Each object specifies a field in the output message and the logic to compute its value.
Fields of a field assignment object (item in field_assignments list)
field_name A user-defined name for the field. It identifies the specific value within the object when using dot notation in expressions (aggregator_name.field_name).
aggregation

An object that defines how the system computes the field's value with the following fields:

@type The aggregation function.

  • avg, min, max, sum, stddev: Standard math statistics.
  • count: Counts how many times this aggregator has been triggered.
  • delta: Difference between current and previous value.
  • vector: Creates a list of values (ring buffer).
  • none: Passes the raw value through.
expression The input expression for the aggregation. Required for all aggregation types except count.
max_length For @type: "vector" only. An optional integer that limits the vector size, creating a ring buffer.

Example: Aggregator

For the example, calculate statistics between minute reports. This aggregator is triggered by OnNewSpeed to calculate the average speed (avg), count readings (count), and store the last 5 speed values (vector). Set reset_on_get to true. This resets the statistics each time MinuteReport reads them, starting a new collection window for the aggregator for the next minute:

{
  "name": "SpeedAggregator",
  "trigger_names": ["OnNewSpeed"], // Update aggregation on new speed data
  "reset_on_get": true, // Reset stats after they are read by the report configuration
  "message_builder": {
    "field_assignments": [
      {
        "field_name": "average_speed",
        "aggregation": {
          "@type": "avg",
          "expression": "SpeedSource.speed"
        }
      },
      {
        "field_name": "speed_reading_count",
        "aggregation": {
          "@type": "count" // Counts triggers (that is, processed speed readings) since last reset
        }
      },
      {
        "field_name": "speed_history_last5",
        "aggregation": {
          "@type": "vector",
          "expression": "SpeedSource.speed",
          "max_length": 5 // Keep last 5 readings
        }
      }
    ]
  }
}

Create output

To define the final output of your telemetry campaign, add objects to the report_configs array (concept). These configurations determine how processed data is packaged and when it is generated. You can define multiple report configurations in one metrics configuration to reuse components.

You control report generation using the trigger_names field. In addition, you can use report_initial to generate a report immediately when the configuration activates, and report_incomplete to generate a final report when data collection is interrupted.

Note: To connect processing to output, use the none aggregation type (@type: "none") to read pre-calculated values from an Aggregator. Since reports are typically stateless snapshots, this keeps complex stateful logic within aggregators and reserves reports for formatting.

Fields of a report configuration object (item in report_configs list)
name A unique name for the report. This name appears in the generated report metadata.
trigger_names An array of trigger names that cause the report to generate and publish.
message_builder See Message builder for details. This defines the report's content. Aggregations are evaluated only when the report is triggered. For example, a vector aggregation adds one value per report, and a count aggregation mirrors the report number.
optional
report_incomplete
(default: `false`)
A boolean. If `true`, the system generates a final report on shutdown or when data collection ends, even if data is missing.
optional
report_initial
(default: `false`)
A boolean. If `true`, the system generates a report immediately when the metrics configuration activates.

Example: Report configuration

Finally, define the report configuration for the example. It is triggered by EveryMinute. It reads the calculated average speed and reading count from SpeedAggregator using a none aggregation, which passes the pre-aggregated value to the report:

{
  "name": "MinuteReport",
  "trigger_names": ["EveryMinute"], // Generate report every minute
  "message_builder": {
    "field_assignments": [
      {
        "field_name": "average_speed",
        "aggregation": {
          "@type": "none", // Read the value directly from the aggregator
          "expression": "SpeedAggregator.average_speed"
        }
      },
      {
        "field_name": "reading_count",
        "aggregation": {
          "@type": "none",
          "expression": "SpeedAggregator.speed_reading_count"
        }
      }
    ]
  }
}

Top-level fields

In addition to the data_sources, aggregators, triggers, and report_configs arrays, the description of a metrics configuration requires a reference to the signal catalog. You can also include optional fields to assign a specific UUID and manage the collection lifecycle.

Set UUID

Each MetricsConfig requires a Universally Unique Identifier (UUID). If you provide an existing_uuid, MCG uses it. Otherwise, it creates a random one. For consistency across deployments and tools, specify an existing_uuid.

The string must be a valid hyphenated UUID containing only lowercase letters.

"existing_uuid": "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8",

Signal definitions

To validate signal names and types, MCG requires access to a Vehicle Signal Catalog. This is a protobuf FileDescriptorSet containing your VSIDL .proto definitions (packaged and uploaded to MCG). For details on creating and uploading a catalog, see Vehicle Signal Catalogs.

Specify the catalog version in the JSON object's vs_version field:

"vs_version": "v1.0",

Define lifecycle triggers

In the context of a telemetry campaign, the lifecycle of a MetricsConfig determines when data is actually recorded. While the campaign management system deploys and activates the configuration on the device, you can use lifecycle triggers to precisely control when data is collected within that deployment.

This lets you align data collection with vehicle usage patterns, such as a "Trip" (IgnitionOn to IgnitionOff) or a "Charge Session", without needing to deactivate the configuration.

  • Session Control (Start/Pause): Use start_trigger_name and stop_trigger_name to control when collection happens. For example, to collect data only while the vehicle is driving, use IgnitionOn as the start trigger and IgnitionOff as the stop trigger. The configuration remains active on the device but effectively "pauses" (stops collecting and processing) when the stop trigger fires, resuming only when the start trigger fires again.

    Important: This merely pauses collection. It does not define logical windows, separate data sets, or have any other side effects. Aggregator values are not reset when collection pauses or resumes.

  • One-off Detection (Finish): Use deactivate_trigger_name if the configuration should run only once (for example, to detect the first occurrence of a specific fault code) and then permanently deactivate itself on that device, even if the campaign is technically still active.

If you don't specify any lifecycle triggers, data collection begins immediately when the configuration is activated by the campaign and continues continuously until the campaign ends.

Top-level lifecycle fields (root object)
optional
start_trigger_name
The name of a trigger that starts the collection session. Can be set without stop_trigger_name.
optional
stop_trigger_name
The name of a trigger that pauses the collection session (for example, when the vehicle is stationary). If set, start_trigger_name must also be set.
optional
deactivate_trigger_name
The name of a trigger that finalizes and deactivates the `MetricsConfig` entirely.

Advanced: Custom proto definitions

In two main cases, vs_version alone isn't enough for MCG to understand all message types in the description of a metrics configuration:

  1. Type inference failure: As explained in source_identifier format, MCG can't infer the type when source_identifier uses FQIN or a custom name.
  2. Custom messages: The metrics configuration description uses protobuf messages that aren't found in the VSIDL catalog specified in vs_version. This happens when setting message_type in a message_builder for a custom report format.

In these cases, use data_source_message_types to help MCG infer types and descriptor_protos to provide message definitions.

data_source_message_types

Map the source_identifier string to its fully-qualified protobuf message type. The key in data_source_message_types must match the source_identifier value from the data_sources entry:

"data_source_message_types": {
  "MyCustomSpeedService": "com.sdv.example.SampleMessage"
}

descriptor_protos

Provide definitions for any message types used in data_source_message_types or message_builder that are not in the configured vs_version.

Pass a base64-encoded descriptorpb.FileDescriptorSet in the descriptor_protos array. Generate this from .proto files using the Protobuf compiler protoc.

"descriptor_protos": [
  "Cu8BCiZtY2cvdGVzdGRhdGEvbWF4YXZnY3..." // Base64 string
]

Example: Complete configuration description

The preceding sections define all the components for the example: generating a report every minute with the average vehicle speed observed during that minute.

The components are:

  • A data source (SpeedSource) to get speed data up to once per second.
  • A data trigger (OnNewSpeed) that fires when SpeedSource sends data.
  • A periodic trigger (EveryMinute) that fires every 60 seconds.
  • An aggregator (SpeedAggregator) that uses OnNewSpeed to calculate average speed, count readings, and store recent values, resetting when read.
  • A report configuration (MinuteReport) that uses EveryMinute to trigger a report containing the average speed and count from SpeedAggregator.
  • Top-level fields (existing_uuid, vs_version) to identify the metrics configuration and specify which VSIDL catalog to use for signal definitions.

When combined, these pieces form the complete description of a metrics configuration:

{
  "existing_uuid": "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8", // Unique identifier for the configuration
  "vs_version": "example_version", // Version of the VSIDL catalog to use
  "data_sources": [
    {
      "name": "SpeedSource",
      "source_identifier": "mcg.test.subpkg.speed_msg",
      "connection_type": "SUBSCRIPTION",
      "sub_sampling_interval_ms": 1000
    }
  ],
  "aggregators": [
    {
      "name": "SpeedAggregator",
      "trigger_names": ["OnNewSpeed"],
      "reset_on_get": true,
      "message_builder": {
        "field_assignments": [
          {
            "field_name": "average_speed",
            "aggregation": {
              "@type": "avg",
              "expression": "SpeedSource.speed"
            }
          },
          {
            "field_name": "speed_reading_count",
            "aggregation": { "@type": "count" }
          },
          {
            "field_name": "speed_history_last5",
            "aggregation": {
              "@type": "vector",
              "expression": "SpeedSource.speed",
              "max_length": 5
            }
          }
        ]
      }
    }
  ],
  "triggers": [
    {
      "name": "OnNewSpeed",
      "data": { "source_name": "SpeedSource" }
    },
    {
      "name": "EveryMinute",
      "periodic": { "period_ms": 60000 }
    }
  ],
  "report_configs": [
    {
      "name": "MinuteReport",
      "trigger_names": ["EveryMinute"],
      "message_builder": {
        "field_assignments": [
          {
            "field_name": "average_speed",
            "aggregation": {
              "@type": "none",
              "expression": "SpeedAggregator.average_speed"
            }
          },
          {
            "field_name": "reading_count",
            "aggregation": {
              "@type": "none",
              "expression": "SpeedAggregator.speed_reading_count"
            }
          }
        ]
      }
    }
  ]
}

Reference template

For the API definition, see the MCG API reference.

The following sections provide a complete reference of the description of a metrics configuration. Use it as a guide to build your own description of a metrics configuration.

Top-level fields

{
  "existing_uuid": "00000000-0000-0000-0000-000000000000", // Optional
  "vs_version": "example_version", // Optional
  "descriptor_protos": ["..."], // Optional. Base64 encoded FileDescriptorSet
  "data_source_message_types": {
    "ExampleServiceName": "com.example.ProtoMessage"
  }, // Optional
  "start_trigger_name": "DataTriggerExample", // Optional
  "stop_trigger_name": "ConditionalTriggerExample", // Optional
  "deactivate_trigger_name": "PeriodicTriggerExample" // Optional
}

Input: data sources and aggregators

{
  "data_sources": [
    {
      "name": "SubscriptionExample",
      "source_identifier": "com.example.sdv.ExampleMessage|example-unit",
      "connection_type": "SUBSCRIPTION", // Options: SUBSCRIPTION (default), ON_DEMAND
      "sub_sampling_interval_ms": 100, // Optional
      "fetch_last_message": false // Optional. Default: false
    },
    {
      "name": "RegistryExample",
      // Configurable Publisher Registry-based publisher (matches data_source_message_types)
      "source_identifier": "ExampleServiceName",
      "connection_type": "SUBSCRIPTION"
    },
    {
      "name": "GetterExample",
      "source_identifier": "com.example.sdv.ExampleConfig|example-unit",
      "connection_type": "ON_DEMAND",
      "configuration": {
        "type_url": "type.googleapis.com/example.Config",
        "value_json": {} // Or value_textproto, value (base64)
      }
    }
  ],
  "aggregators": [
    {
      "name": "AggregatorExample",
      "trigger_names": ["DataTriggerExample"],
      "reset_on_get": false, // Optional. Default: false. If true, resets state after it's read
      "message_builder": {
        "message_type": "com.example.AggregatedMessage", // Optional
        "field_assignments": [
          {
            "field_name": "avg_example",
            "aggregation": {
              // Options: avg, count, min, max, sum, stddev, delta, vector, none
              "@type": "avg",
              "expression": "SubscriptionExample.value"
            }
          },
          {
            "field_name": "count_example",
            "aggregation": {
              "@type": "count" // Counts number of evaluations. No expression needed
            }
          },
          {
            "field_name": "vector_example",
            "aggregation": {
              "@type": "vector",
              "expression": "SubscriptionExample.value",
              "max_length": 10 // Optional. If set, creates a ring buffer
            }
          }
        ]
      }
    }
  ]
}

Logic and processing: triggers

{
  "triggers": [
    {
      "name": "PeriodicTriggerExample",
      "periodic": { "period_ms": 1000 }
    },
    {
      "name": "DataTriggerExample",
      "data": { "source_name": "SubscriptionExample" }
    },
    {
      "name": "ConditionalTriggerExample",
      "conditional": {
        "triggers": ["PeriodicTriggerExample"],
        "expression": "SubscriptionExample.value > 0",
        "condition_type": {
          // Options: is_true, is_false, rising_edge, falling_edge, all_changes
          "rising_edge": {
            "rising_options": { "min_duration_ms": 0, "require_exact": false }
          }
        }
      }
    }
  ]
}

Output: report configurations

{
  "report_configs": [
    {
      "name": "ReportExample",
      "trigger_names": ["PeriodicTriggerExample"],
      "report_incomplete": false, // Optional. Default: false
      "report_initial": false, // Optional. Default: false
      "message_builder": {
        "message_type": "com.example.ReportMessage", // Optional. Must be defined in VSIDL catalog or descriptor_protos. Message type will be inferred if not provided
        "field_assignments": [
          {
            "field_name": "avg_example",
            "aggregation": {
              "@type": "none", // Passthrough since aggregation is done in AggregatorExample
              "expression": "AggregatorExample.avg_example"
            }
          }
        ]
      }
    }
  ]
}

Expressions syntax

Category Syntax Description
Data access source_name
source_name.field
source_name.field.subfield
Access full message from a data source or aggregator
Access specific field in message (including nested fields)
Arithmetic +, -, *, /, %, ** Standard math. ** is exponentiation.
Logical &&, ||, !, ^ AND, OR, NOT, XOR.
Relational ==, !=, <, <=, >, >= == and != work on all types. Others require numbers.
List contains(list, item)
doesnotcontain(list, item)
alleq(list, value)
Operates on vectors (arrays). alleq(list, value) returns true if all items in list are equal to value.
Functions timestamp(clock_type) Current time in nanoseconds.
clock_type: REALTIME_CLOCK or
MONOTONIC_TIME_SINCE_BOOT_OR_RESUME
abs(n) Absolute value
floor(n), round(n), ceil(n) Rounding functions
Order of operations () Standard grouping for precedence