本指南详细介绍了指标配置生成器 (MCG) 工具的 /api/v1/generate_metrics_config 端点的 JSON 请求格式。借助此格式,您可以采用人类可读的结构定义遥测活动,指定数据收集、设备端处理和报告生成。
MCG 工具会验证此 JSON 对象,检查是否存在类型不匹配、循环依赖或未定义的引用等问题,然后将其编译为 MetricsConfig 协议缓冲区 (protobuf) 消息。此 MetricsConfig 消息是设备端遥测服务执行以运行广告系列的二进制格式。
前提条件:您应熟悉 JSON 架构、protobuf 和基本数据结构。如需查看概念性概览,请参阅指标配置概念。
如何撰写配置说明
如需设计指标收集广告系列,请遵循以下逻辑流程:
示例:平均速度报告
本指南通过一个示例展示了所有组件如何协同工作。该示例创建了一个每分钟计算一次平均车速的报告。它定义了一个用于收集速度数据的数据源 (SpeedSource)、用于控制执行流程的触发器(OnNewSpeed、EveryMinute)、用于计算平均值的聚合器 (SpeedAggregator) 和用于打包结果的指标报告配置 (MinuteReport)。可展开部分中的示例基于此方案构建。
指定数据源
遥测支持从 SDV 服务(通过 SDV 中间件)和基于可配置发布者注册表的发布者收集数据。
基于 Pub/Sub 的 SDV 服务:数据可从 VSIDL 中定义的 Pub/Sub 渠道获取。
SDV 服务(基于 RPC):如果服务公开了
CreateSubscription或GetLatestMessageRPC,则数据可用。基于可配置发布商注册表的发布商:如果应用使用
IConfigurablePublisherRegistryAndroid Binder 接口或 Telemetry SDK 中的可配置发布商注册表库自行注册,则可获取数据。
如需在 SDV 中使用数据,请定义数据源(概念),并将其添加到 data_sources 数组中。
数据源对象(data_sources 列表中的项)的字段 |
|||||
|---|---|---|---|---|---|
name |
一个用户定义的字符串,用于在指标配置中标识相应数据源。 | ||||
source_identifier |
用于发现服务的标识符。如需了解详情,请参阅 source_identifier 格式。 |
||||
connection_type |
SUBSCRIPTION 表示连续数据流(数据触发器必需),ON_DEMAND 表示按需提取(详情请参阅数据源配置)。 |
||||
可选configuration |
仅限 RPC 和可配置的发布商注册表。用于配置数据源的对象,包含以下字段:
|
||||
SUBSCRIPTION 连接类型的字段 |
|||||
可选sub_sampling_interval_ms |
仅限 Pub/Sub。一个非负整数(毫秒),用于通过指定消息之间的最小间隔来限制消息频率。 | ||||
可选fetch_last_message(默认值: false) |
仅限 Pub/Sub。布尔值。如果为 true,则在连接时检索最新消息。 |
||||
并非所有选项都适用于所有类型的数据源。如需了解详情,请参阅数据源集成指南。
source_identifier 的格式
遥测服务接受多种来源标识符格式。如需概览,请参阅指标配置中的数据源定义。
仅当 source_identifier 采用“单元类型名称”格式时,MCG 才会从 source_identifier 推断 protobuf 消息类型。如果您使用 FQIN 或自定义名称(来自可配置的发布商注册表),MCG 将无法推断类型。在这些情况下,您必须提供 data_source_message_types。如果该类型不在您的目录中,您还必须在 descriptor_protos 中提供其定义。
示例:定义数据源
此示例报告车速。首先,请查阅 VSIDL 目录,以确定发布此数据的服务。
使用 SDV 代码库中的示例目录,确定相关服务。根据 source_identifier 的格式,指定 protobuf 消息类型 mcg.test.subpkg.speed_msg。由于速度通常会频繁发布,因此请使用 sub_sampling_interval_ms 将更新限制为每秒一条消息:
{ "name": "SpeedSource", "source_identifier": "mcg.test.subpkg.speed_msg", "connection_type": "SUBSCRIPTION", "sub_sampling_interval_ms": 1000 // Limit updates to 1 per second }
定义逻辑和处理
逻辑由两个协同工作的组件处理:触发器(概念)用于定义何时发生某事。聚合器(概念)用于定义如何根据这些触发器处理数据。由于表达式(概念)是触发条件和聚合逻辑的关键,因此本部分首先介绍如何编写表达式。
表达式
您可以使用直观易懂的语法,通过表达式定义计算和条件。MCG 会将这些字符串编译为针对设备端评估进行优化的可执行格式。
表达式可以表示算术计算、逻辑条件和数据比较。如需了解完整语法(包括运算符和函数),请参阅表达式语法。
数据新鲜度:当表达式访问数据源时,检索行为取决于连接类型:
SUBSCRIPTION:使用遥测服务缓存的上次收到的消息。(注意:如果设置了sub_sampling_interval_ms,此值可能与绝对最新的已发布消息不同。)ON_DEMAND:触发立即调用以从服务中提取新消息。
类型限制
- 数组:不支持直接数组索引访问(例如
my_data_source.my_array[0])。 - 类型安全:请确保您比较的是兼容的类型(例如,不要将字符串字段与整数列表进行比较)。
示例:表达式
该示例需要提取速度值以计算其平均值。它还需要确定车辆是否超速(超过 100 公里/小时),以触发条件性触发器。您可以在数据源使用的 protobuf 消息定义中找到字段名称(在本例中为 speed)。以下表达式可实现此目的:
SpeedSource.speed:从SpeedSource数据源中检索speed字段的值。SpeedSource.speed > 27.7:如果速度大于 27.7 米/秒(大约 100 公里/小时),则计算结果为true。
触发器:定义操作的发生时间
触发器用于定义何时执行操作:评估汇总器、生成报告或控制收集生命周期。将所有已定义的触发器添加到顶级 triggers 数组中。
触发器对象的字段(triggers 列表中的项) |
|
|---|---|
name |
指标配置中相应触发器的唯一标识符。 |
periodicdataconditional |
您必须提供以下字段中的一个来定义行为。 |
数据触发器
当 SUBSCRIPTION 数据源提供新消息(概念)时触发。
data 对象(在触发器中)的字段 |
|
|---|---|
source_name |
相应触发器所监听的 data_source 的 name。 |
示例:数据触发器
在此示例中,每次 SpeedSource 发布新消息时,都会更新平均速度计算。每次发生这种情况时,此数据触发器都会触发:
{ "name": "OnNewSpeed", "data": { "source_name": "SpeedSource" // Reference to the defined data source } }
周期性触发器
以固定间隔触发(概念)。
periodic 对象(在触发器中)的字段 |
|
|---|---|
period_ms |
一个非负数,用于定义间隔(以毫秒为单位)。 |
示例:周期性触发器
此示例要求每分钟生成一次报告。此周期性触发器每 60,000 毫秒触发一次,以生成该报告:
{ "name": "EveryMinute", "periodic": { "period_ms": 60000 // 60,000 ms = 60 seconds } }
条件触发器
根据表达式(概念)的评估结果触发。它需要一个或多个父触发器才能开始评估。这通常是表达式中数据源或聚合器的数据触发器,或用于轮询数据的周期性触发器。
conditional 对象(在触发器中)的字段 |
|
|---|---|
triggers |
一个数组,其中包含至少一个父触发器名称。当父触发器触发时,条件触发器会评估表达式。 |
expression |
要评估的表达式。请参阅表达式。 |
condition_type |
根据表达式的评估结果,指定触发器应在何时触发。 |
条件类型
condition_type 字典必须包含一个且仅一个可用条件类型作为键。如需了解详情,请参阅条件触发器。
condition_type 对象的字段(互斥) |
|
|---|---|
is_trueis_false |
当布尔表达式的计算结果分别为 true 或 false 时触发。 |
rising_edge |
如果是数值,则在其值增加时触发。如果表达式为布尔值,则在表达式从 可以包含 |
falling_edge |
如果表达式是数值,则在其值减小时触发。如果表达式是布尔值,则在其从 可以包含 |
all_changes |
当表达式的结果与之前的值不同时触发。如果设置了边缘选项,则仅支持数值和布尔值。 可以包含 |
边缘选项
rising_options 和 falling_options 对象具有以下字段。如果提供,相应的转场必须满足时长要求。未指定选项的转场会立即触发。
| 边缘选项对象的字段 | |
|---|---|
min_duration_ms |
一个非负数(以毫秒为单位),用于指定条件必须在新状态下保持多长时间,然后才能触发。 |
可选require_exact |
布尔值。如果为 true,则在相应时长内发布的所有值必须相同,才能触发触发器。 |
示例:条件触发器
以下触发器使用边缘选项。如果速度超过 27.7 米/秒,并且保持高速状态至少 5 秒,则会触发此事件:
{ "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 数组中。
聚合器可转换数据,并使其他聚合器和报告能够使用这些数据。
聚合器的字段(aggregators 列表中的项) |
|
|---|---|
name |
用于标识相应聚合器的用户定义字符串。表达式可以通过名称引用此聚合器,以访问其结果。 |
trigger_names |
一个或多个触发器名称的数组,用于触发对相应聚合的评估。 |
可选reset_on_get |
布尔值,默认值:`false`。如果为 `true`,则在其他聚合器、指标报告或条件触发器访问该值后,系统会重置聚合状态。 |
message_builder |
定义要读取、处理和汇总的数据。输出消息的字段随后会成为其他汇总器和报告的数据源。它使用 field_assignments,其中每个赋值都定义了输出消息中的一个字段。 |
消息构建工具
message_builder 对象用于定义输出消息结构以及用于计算其字段的聚合逻辑。它同时用于汇总器和报告配置。
message_builder 对象(在汇总或报告中)的字段 |
|
|---|---|
message_type |
输出的 protobuf 消息类型的完全限定名称。如果省略,MCG 会根据 field_assignments 推断的输出类型创建自定义消息类型。仅当消息构建器是指标报告的一部分,并且您的报告必须与特定的预定义 protobuf 消息定义匹配时,才使用此参数。 |
field_assignments |
字段定义对象的数组。每个对象都指定了输出消息中的一个字段以及用于计算其值的逻辑。 |
字段分配对象(field_assignments 列表中的项)的字段 |
|||||||
|---|---|---|---|---|---|---|---|
field_name |
用户定义的字段名称。在表达式中使用点表示法 (aggregator_name.field_name) 时,用于标识对象中的特定值。 |
||||||
aggregation |
一个用于定义系统如何计算字段值的对象,包含以下字段:
|
||||||
示例:聚合平台
对于此示例,请计算分钟报告之间的统计信息。此聚合器由 OnNewSpeed 触发,用于计算平均速度 (avg)、读数数量 (count) 并存储最后 5 个速度值 (vector)。将 reset_on_get 设置为 true。这样,每次 MinuteReport 读取统计信息时,都会重置这些信息,并为聚合器启动下一个分钟的新收集窗口:
{ "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 } } ] } }
创建输出
如需定义遥测活动的最终输出,请向 report_configs 数组(概念)添加对象。这些配置可确定处理后的数据如何打包以及何时生成。您可以在一个指标配置中定义多个报告配置,以重复使用组件。
您可以使用 trigger_names 字段控制报告生成。此外,您还可以使用 report_initial 在配置激活时立即生成报告,并使用 report_incomplete 在数据收集中断时生成最终报告。
注意:如需将处理与输出相关联,请使用 none 聚合类型 (@type: "none") 从 Aggregator 读取预先计算的值。由于报告通常是无状态的快照,因此这种方法可将复杂的有状态逻辑保留在汇总器中,并预留报告以进行格式设置。
报告配置对象(report_configs 列表中的项)的字段 |
|
|---|---|
name |
报告的唯一名称。此名称会显示在生成的报告元数据中。 |
trigger_names |
导致报告生成和发布的触发器名称数组。 |
message_builder |
有关详情,请参阅消息构建器。 此属性用于定义报告的内容。只有在触发报告时才会评估聚合。例如,向量聚合会为每个报告添加一个值,而计数聚合会反映报告编号。 |
可选report_incomplete(默认值:`false`) |
一个布尔值。如果为 `true`,系统会在关机时或数据收集结束时生成最终报告,即使缺少数据也是如此。 |
可选report_initial(默认值:`false`) |
一个布尔值。如果为 `true`,系统会在指标配置激活时立即生成报告。 |
示例:报告配置
最后,为示例定义报告配置。它由 EveryMinute 触发。它使用 none 聚合从 SpeedAggregator 读取计算出的平均速度和读数,并将预聚合的值传递给报告:
{ "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" } } ] } }
顶级字段
除了 data_sources、aggregators、triggers 和 report_configs 数组之外,指标配置的说明还需要引用信号目录。您还可以添加可选字段来分配特定 UUID 并管理收集生命周期。
设置 UUID
每个 MetricsConfig 都需要一个通用唯一标识符 (UUID)。如果您提供 existing_uuid,MCG 会使用它。否则,它会创建一个随机 UUID。为了确保部署和工具之间的一致性,请指定 existing_uuid。
该字符串必须是有效的连字符分隔 UUID,并且只能包含小写字母。
"existing_uuid": "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8",
信号定义
为了验证信号名称和类型,MCG 需要访问车辆信号目录。这是一个 protobuf FileDescriptorSet,其中包含您的 VSIDL .proto 定义(已打包并上传到 MCG)。如需详细了解如何创建和上传目录,请参阅车辆信号目录。
在 JSON 对象的 vs_version 字段中指定目录版本:
"vs_version": "v1.0",
定义生命周期触发器
在遥测活动的背景下,MetricsConfig 的生命周期决定了实际记录数据的时间。虽然活动管理系统会在设备上部署并激活配置,但您可以使用生命周期触发器来精确控制何时在该部署中收集数据。
这样一来,您就可以根据车辆使用模式(例如“行程”[IgnitionOn 到 IgnitionOff] 或“充电会话”)调整数据收集,而无需停用配置。
会话控制(开始/暂停):使用
start_trigger_name和stop_trigger_name控制何时进行收集。例如,如需仅在车辆行驶时收集数据,请使用IgnitionOn作为开始触发器,并使用IgnitionOff作为停止触发器。配置在设备上保持有效,但在触发停止触发器时会有效“暂停”(停止收集和处理),仅在再次触发开始触发器时恢复。重要提示:这只会暂停收集。它不会定义逻辑窗口、分离数据集或产生任何其他副作用。当收集暂停或恢复时,聚合器值不会重置。
一次性检测(完成):如果配置应仅运行一次(例如,检测特定故障代码的首次出现),然后在该设备上永久停用自身,即使从技术上讲,该广告系列仍处于有效状态,也应使用
deactivate_trigger_name。
如果您未指定任何生命周期触发条件,则当广告系列激活配置后,系统会立即开始收集数据,并持续收集数据,直到广告系列结束。
| 顶级生命周期字段(根对象) | |
|---|---|
可选start_trigger_name |
启动收集会话的触发器的名称。可以设置,无需 stop_trigger_name。 |
可选stop_trigger_name |
暂停收集会话的触发器的名称(例如,当车辆静止时)。如果设置了此字段,则还必须设置 start_trigger_name。 |
可选deactivate_trigger_name |
用于最终确定并完全停用 `MetricsConfig` 的触发器的名称。 |
高级:自定义 proto 定义
在以下两种主要情况下,仅凭 vs_version 不足以让 MCG 理解指标配置说明中的所有消息类型:
- 类型推断失败:如
source_identifier格式中所述,当source_identifier使用 FQIN 或自定义名称时,MCG 无法推断出类型。 - 自定义消息:指标配置说明使用了
vs_version中指定的 VSIDL 目录中未找到的 protobuf 消息。当在message_builder中为自定义报告格式设置message_type时,会发生这种情况。
在这些情况下,请使用 data_source_message_types 帮助 MCG 推断类型,并使用 descriptor_protos 提供消息定义。
data_source_message_types
将 source_identifier 字符串映射到其完全限定的 protobuf 消息类型。data_source_message_types 中的键必须与 data_sources 条目中的 source_identifier 值匹配:
"data_source_message_types": {
"MyCustomSpeedService": "com.sdv.example.SampleMessage"
}
descriptor_protos
为 data_source_message_types 或 message_builder 中使用的任何不在已配置的 vs_version 中的消息类型提供定义。
在 descriptor_protos 数组中传递 base64 编码的 descriptorpb.FileDescriptorSet。使用 Protobuf 编译器 protoc 从 .proto 文件生成此数组。
"descriptor_protos": [
"Cu8BCiZtY2cvdGVzdGRhdGEvbWF4YXZnY3..." // Base64 string
]
示例:完整的配置说明
前面的部分定义了示例的所有组件:每分钟生成一份报告,其中包含该分钟内观测到的平均车速。
这些组件包括:
- 一个数据源 (
SpeedSource),用于获取速度数据(每秒最多一次)。 - 一种数据触发器 (
OnNewSpeed),在SpeedSource发送数据时触发。 - 每 60 秒触发一次的周期性触发器 (
EveryMinute)。 - 一种使用
OnNewSpeed计算平均速度、读取次数和存储最近值的聚合器 (SpeedAggregator),在读取时重置。 - 一种报告配置 (
MinuteReport),使用EveryMinute触发包含来自SpeedAggregator的平均速度和计数的报告。 - 用于标识指标配置并指定要使用哪个 VSIDL 目录来定义信号的顶级字段(
existing_uuid、vs_version)。
这些部分组合在一起,构成了指标配置的完整说明:
{ "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" } } ] } } ] }
参考模板
如需查看 API 定义,请参阅 MCG API 参考文档。
以下部分提供了指标配置说明的完整参考。您可以将其作为构建自己的指标配置说明的指南。
顶级字段
{
"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
}
输入:数据源和汇总器
{
"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
}
}
]
}
}
]
}
逻辑和处理:触发器
{
"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 }
}
}
}
}
]
}
输出:报告配置
{
"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"
}
}
]
}
}
]
}
表达式语法
| 类别 | 语法 | 说明 |
|---|---|---|
| 数据访问权限 | source_namesource_name.fieldsource_name.field.subfield |
从数据源或聚合器访问完整消息 访问消息中的特定字段(包括嵌套字段) |
| 算术 | +、-、*、/、%、** |
标准数学。** 表示指数运算。 |
| 逻辑 | &&、||、!、^ |
AND、OR、NOT、XOR。 |
| 关系型 | ==、!=、<、<=、>、>= |
== 和 != 适用于所有类型。其他国家/地区则需要提供号码。 |
| 列表 | contains(list, item)doesnotcontain(list, item)alleq(list, value) |
适用于向量(数组)。如果 list 中的所有项都等于 value,则 alleq(list, value) 返回 true。 |
| 函数 | timestamp(clock_type) |
当前时间(以纳秒为单位)。clock_type:REALTIME_CLOCK 或 MONOTONIC_TIME_SINCE_BOOT_OR_RESUME |
abs(n) |
绝对值 | |
floor(n)、round(n)、ceil(n) |
取整函数 | |
| 运算顺序 | () |
标准分组(按优先级) |