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:
- Specify data sources: Define where your data comes from.
- Define logic and processing: Set up rules for when to collect data and how to process it.
- Create output: Package the processed data into a final message.
- 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.
SDV Services (pub/sub-based): Data is available from pub/sub channels defined in VSIDL.
SDV Services (RPC-based): Data is available if the service exposes
CreateSubscriptionorGetLatestMessageRPCs.Configurable Publisher Registry-based publishers: Data is available if the application registers itself using the
IConfigurablePublisherRegistryAndroid Binder interface or the Configurable Publisher Registry library from the Telemetry SDK.
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). |
||||
optionalconfiguration |
RPC and Configurable Publisher Registry only. An object to configure the data source with the following fields:
|
||||
Fields for SUBSCRIPTION connection type |
|||||
optionalsub_sampling_interval_ms |
Pub/sub only. A non-negative integer (milliseconds) to throttle message frequency by specifying the minimum interval between messages. | ||||
optionalfetch_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: Ifsub_sampling_interval_msis 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 thespeedfield from theSpeedSourcedata source.SpeedSource.speed > 27.7: Evaluates totrueif 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. |
periodicdataconditional |
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_trueis_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 Can contain a |
falling_edge |
If numeric, fires when its value decreases. If the expression is
boolean, fires when it changes from Can contain a |
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 |
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. |
optionalrequire_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. |
optionalreset_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:
|
||||||
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. |
optionalreport_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. |
optionalreport_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_nameandstop_trigger_nameto control when collection happens. For example, to collect data only while the vehicle is driving, useIgnitionOnas the start trigger andIgnitionOffas 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_nameif 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) | |
|---|---|
optionalstart_trigger_name |
The name of a trigger that starts the collection session. Can be set
without stop_trigger_name. |
optionalstop_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. |
optionaldeactivate_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:
- Type inference failure: As explained in
source_identifierformat, MCG can't infer the type whensource_identifieruses FQIN or a custom name. - Custom messages: The metrics configuration description uses protobuf
messages that aren't found in the VSIDL catalog specified in
vs_version. This happens when settingmessage_typein amessage_builderfor 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 whenSpeedSourcesends data. - A periodic trigger (
EveryMinute) that fires every 60 seconds. - An aggregator (
SpeedAggregator) that usesOnNewSpeedto calculate average speed, count readings, and store recent values, resetting when read. - A report configuration (
MinuteReport) that usesEveryMinuteto trigger a report containing the average speed and count fromSpeedAggregator. - 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_namesource_name.fieldsource_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 orMONOTONIC_TIME_SINCE_BOOT_OR_RESUME |
abs(n) |
Absolute value | |
floor(n), round(n), ceil(n) |
Rounding functions | |
| Order of operations | () |
Standard grouping for precedence |