MQL 语言简介

本页面提供 Monitoring Query Language (MQL) 的常规信息,包括以下主题:

无论您是通过 Google Cloud 控制台还是 Cloud Monitoring API 使用 MQL,此信息都适用。如需了解 MQL 查询的结构,请参阅示例

表操作和函数的快捷方式

查询通常由竖线 (|) 连接的表操作链组成,每个表操作都以表操作的名称开头,后跟表达式列表。表达式可以包含显式列出其所有参数的函数调用。但是,Monitoring Query Language 允许通过多个快捷方式表示查询。

本部分介绍表操作的快捷方式,将函数用作表操作,以及值列作为函数参数时的快捷方式。

如需完整列表,请参阅表操作快捷方式

表操作的快捷方式

使用 fetchgroup_byfilter 操作时,如果参数足以确定预期的操作,则可以省略显式表操作。例如,以下查询:

gce_instance::compute.googleapis.com/instance/cpu/utilization

等效于:

fetch gce_instance::compute.googleapis.com/instance/cpu/utilization

以下 group_by 操作是等效的:

         [zone], mean(val())
group_by [zone], mean(val())

如果您为过滤器测试添加了括号,则可以省略 filter 一词。例如,以下两个 filter 操作是等效的:

       (instance_name =~ 'apache.*')
filter instance_name =~ 'apache.*'

您可以在查询中组合使用这些快捷方式。例如,以下查询:

gce_instance::compute.googleapis.com/instance/cpu/utilization
| (instance_name =~ 'apache.*')
|  [zone], mean(val())

等效于以下更为明确的形式:

fetch gce_instance::compute.googleapis.com/instance/cpu/utilization
| filter instance_name =~ 'apache.*'
| group_by [zone], mean(val())

如需详细了解表操作的快捷方式,请参阅 MQL 参考文档中的表操作快捷方式

将函数用作表操作

表操作通常以表操作的名称开头。但是,MQL 允许表操作以函数名称开头。

如果指定函数是要针对输入表的值列执行一些转换,则可以使用函数名称。此替换是 group_byalignvalue 表操作的快捷方式,具体取决于给定名称的函数的种类。

通用格式如下:

|  FUNCTION_NAME ARG, ARG ... 

在表操作中,函数将输入表的值列作为参数,后跟函数本身的所有参数。将函数用作表操作时,以表操作形式指定参数(逗号分隔列表),而不是使用函数常用的括号 (())。

通过展开快捷方式生成的完整表操作取决于函数的种类:

  • group_by:如果您将聚合函数与聚合所有时间序列标识符列的 group_by 操作(即,使用 [] 进行分组)搭配使用,则该函数可用作快捷方式。例如:

    | distribution powers_of(1.1)

    是以下表操作的快捷方式

    | group_by [], distribution(val(0), powers_of(1.1))
  • align:如果您将校准函数用作 align 操作的参数,则可以将函数作为快捷方式。例如:

    | delta

    是以下表操作的快捷方式

    | align delta()

    同样,

    | rate 10m

    是以下表操作的快捷方式

    | align rate(10m)

    请注意,校准器函数将输入时序作为隐式参数,因此此处没有显式提供值列。

  • value:其他所有函数可以充当 value 表操作的快捷方式。例如:

    | mul 3.3

    是以下表操作的快捷方式

    | value mul(val(0), 3.3)

    同样,

    | div

    是以下表操作的快捷方式

    | value div(val(0), val(1))

    请注意,div 快捷方式接受具有两个值列的输入表操作,并生成包含一个值列(即比率)的表操作。

值列函数的快捷方式

如果输入具有单个值列,则可以使用 .function 作为 function(val()) 的快捷方式;如果具有两个值列,则可用作 function(val(0), val(1)) 的快捷方式,以此类推。

前导句点表示“调用以下函数,将一个或多个输入点值列作为函数的参数提供。”

例如,.meanmean(val()) 的快捷方式。以下表达式是等效的:

group_by [zone], .mean
group_by [zone], mean(val())

如果输入表有多个值列,则每一列都会成为此快捷方式中函数的参数。例如,如果输入表有两个值列,则

.div

是以下表操作的快捷方式

div(val(0), val(1))

使用此快捷方式,您可以提供不引用值列的参数。其他参数在值列参数后提供。例如,如果输入表具有一个值列,则

.div(3)

等效于

div(val(0), 3)

fetch 的变体

fetch 操作通常返回由一对受监控的资源和指标类型命名的时间序列表。例如:

fetch gce_instance :: compute.googleapis.com/instance/cpu/utilization

如果指标仅适用于一个受监控的资源类型,则可以在查询中省略受监控的资源。以下查询与上述查询等效,因为 CPU 利用率指标仅适用于 gce_instance 受监控的资源:

fetch compute.googleapis.com/instance/cpu/utilization

fetch 操作只能指定一个受监控的资源类型,并在后续 metric 操作中指定指标。例如,以下示例与上述 fetch 示例等效:

fetch gce_instance
| metric metric compute.googleapis.com/instance/cpu/utilization

如果您想为同一个受监控的资源提取两个不同的指标,以这种方式拆分 fetch 会非常有用。例如,以下查询计算每 CPU 秒使用的数据包数量:

fetch gce_instance
| {
    metric compute.googleapis.com/instance/network/received_packets_count ;
    metric compute.googleapis.com/instance/cpu/usage_time
  }
| ratio

此外,拆分 fetch 还允许您仅将过滤应用于受监控的资源的标签:

fetch gce_instance
| filter resource.zone =~ "asia.*"
| {
    metric compute.googleapis.com/instance/network/received_packets_count ;
    metric compute.googleapis.com/instance/cpu/usage_time
  }
| ratio

仅命名一个受监控的资源类型的 fetch 必须后跟 metric 操作,中间可能有 filter 操作。

严格形式查询

严格查询是指在简明查询中不使用任何快捷方式或隐式值的查询。严格查询具有以下特征:

  • 所有快捷方式都会被替换。
  • 所有隐式参数都会显式表示。
  • 使用全名引用列。
  • 显式指定新列的名称。
  • 任何隐式提供的校准操作都会显式提供。

使用严格形式可使查询更灵活地适应输入表结构的更改,并且可以更清晰地显示查询的作用。使用查询的严格形式并不能提高查询效率。

当您为图表保存查询时,它会转换为严格形式。保存操作的确认对话框会显示严格形式。

提醒政策的简明查询不会转换为严格形式。提醒政策的查询将随您的提供而存储;您可使用简明形式,也可使用严格形式。

由于快捷方式和严格形式均可用,您可能会遇到看起来截然不同的等效 MQL 查询。例如,以下计算每 CPU 秒使用的已接收数据包数量的查询使用了许多快捷方式:

gce_instance
| (zone =~ ".*-a")
| {
    compute.googleapis.com/instance/network/received_packets_count ;
    compute.googleapis.com/instance/cpu/usage_time
  }
| join
| div

如果您将此查询保存为图表或保存为提醒政策的一部分,则生成的严格形式查询具有完全相同的作用。但是,严格形式看起来可能截然不同,如以下示例所示:

fetch gce_instance
| filter (resource.zone =~ '.*-a')
| { t_0:
      metric 'compute.googleapis.com/instance/network/received_packets_count'
      | align delta() ;
    t_1:
      metric 'compute.googleapis.com/instance/cpu/usage_time'
      | align delta() }
| join
| value [v_0: div(t_0.value.received_packets_count, t_1.value.usage_time)]

修改已保存的图表定义时,代码编辑器会显示严格形式。

匹配 resource.project_id

Google Cloud 项目具有显示名,显示名会显示在菜单中,但无法唯一标识项目。项目显示名可能是“Monitoring demo”。

项目还包含两个用作标识符的字段:

  • 项目 ID:具有唯一性的字符串标识符。它通常基于显示名。项目 ID 是在项目创建时设置的,通常是将项目名称的元素串联起来,并可能在末尾添加数字(如果唯一性需要如此)。项目 ID 的格式可能是“monitoring-demo”或“monitoring-demo-2349”。项目 ID 有时也称为项目名称。
  • 项目编号:具有唯一性的数字标识符。

每个受监控的资源类型都包含一个 project_id 标签,其中包含资源所属项目的项目编号的字符串表示形式以及该资源的相关数据。

在 MQL 查询中,此标签称为 resource.project_idresource.project_id 标签的值为文本形式的项目编号,但在某些情况下,MQL 会将该值转换为项目 ID。

在以下情况下,MQL 将 resource.project_id 标签的值视为项目 ID 而不是项目编号:

  • 对于 resource.project_id 标签的值,图表的图例显示项目 ID,而不是项目编号。

  • resource.project_id 的值与字符串字面量的相等性比较可识别项目编号和项目 ID。例如,以下命令会针对此项目拥有的资源返回 true:

    • resource.project_id == "monitoring-demo"
    • resource.project_id == "530310927541"

    这种情况适用于 ==!= 运算符及其函数形式 eq()ne()

  • 针对 resource.project_id 标签的正则表达式匹配可使用项目编号或项目 ID。例如,对于此项目拥有的资源,以下两个表达式均返回 true

    • resource.project_id =~ "monitoring-.*"
    • resource.project_id =~ ".*27541"

    这种情况适用于 =~!~ 运算符以及函数形式 re_full_match

对于所有其他情况,将使用 resource.project_id 标签的实际值。例如,concatenate("project-", resource.project_id) 会生成值 project-530310927541,而不是 project-monitoring-demo

比率和“边缘效应”

通常,最好根据为单个指标类型收集的时序使用标签值计算比率。根据两种不同的指标类型计算出的比率可能会因不同的采样周期和校准时段而出现异常。

例如,假设您有两个不同的指标类型,一个是 RPC 总数,一个是 RPC 错误数量,并且您想要计算错误 RPC 与总 RPC 的比率。失败的 RPC 会计入这两个指标类型的时序中。因此,当您校准时序时,某个失败的 RPC 可能不会出现在两个时序的同一校准间隔中。导致这种差异的原因有很多,包括:

  • 由于两个不同的时序记录同一事件,因此有两个计数器值在实现集合,并且它们不会以原子方式更新。
  • 采样率可能会不一样。当时序与公共时间段校准时,单个事件的计数可能会出现在不同指标的时序的相邻校准间隔中。

相应校准间隔内值的数量差可能导致无意义的 error/total 比率值,例如 1/0 或 2/1。

较大数字的比率不太可能产生无意义的值。 您可以通过聚合获得更大的数字,方法是使用长于采样周期的校准时段,或对某些标签的数据进行分组。这些方法可以最大限度地减少给定时间间隔中数据点数量的微小差异的影响。也就是说,在一个时间间隔内,预期数据点数量为 3 时的两点间差异会比预期数量为 300 时更明显。

如果您使用的是内置指标类型,则可能只能计算不同指标类型的比率以获得所需的值。

如果您设计的自定义指标可能会在两个不同的指标中统计相同的内容(例如,返回错误状态的 RPC 数量),请考虑使用单个指标,以使每个计数仅被包含一次。例如,假设您在统计 RPC 数量并想要跟踪失败的 RPC 与所有 RPC 之比。要解决此问题,请创建单个指标类型来统计 RPC 数量,然后使用标签来记录调用状态,包括“成功”状态。然后,通过更新单个计数器即可记录每个状态值(在此例中为“错误”或“成功”)。

MQL 日期格式

MQL 目前仅支持有限数量的日期格式。在 MQL 查询中,日期的表示形式为以下之一:

  • d'BASE_STRING'
  • D'BASE_STRING'

BASE_STRING2010/06/23-19:32:15-07:00 形式的字符串。分隔日期和时间的第一个短划线 (-) 可以替换为空格。在时间组成部分中,时钟时间 (19:32:15) 的某些部分或时区说明符 (-07:00) 可以舍弃。

以下示例是 MQL 查询中的有效日期:

  • d'2010/06/23-19:32:15-07:00'
  • d'2010/06/23 19:32:15-07:00'
  • d'2010/06/23 19:32:15'
  • D'2010/06/23 19:32'
  • d'2010/06/23-19'
  • D'2010/06/23 -07:00'

下表列出了 BASE_STRING 的语法:

结构 含义
%Y/%m/%d 日期
%Y/%m/%d %H
%Y/%m/%d-%H
日期、小时
%Y/%m/%d %H:%M
%Y/%m/%d-%H:%M
日期、小时、分钟
%Y/%m/%d %H:%M:%S
%Y/%m/%d-%H:%M:%S
日期、小时、分钟、秒
%Y/%m/%d %H:%M:%E*S
%Y/%m/%d-%H:%M:%E*S
日期、小时、分钟、小数秒
%Y/%m/%d %Ez 带时区的日期
%Y/%m/%d %H%Ez
%Y/%m/%d-%H%Ez
带时区的日期、小时
%Y/%m/%d %H:%M%Ez
%Y/%m/%d-%H:%M%Ez
带时区的日期、小时、分钟
%Y/%m/%d %H:%M:%S%Ez
%Y/%m/%d-%H:%M:%S%Ez
带时区的日期、小时、分钟、秒
%Y/%m/%d %H:%M:%E*S%Ez
%Y/%m/%d-%H:%M:%E*S%Ez
带时区的日期、小时、分钟、小数秒

查询的长度和复杂度

Monitoring Query Language 查询可能很长且复杂,但也具有一些限制。

  • 编码为 UTF-8 的查询文本不得超过 10000 个字节。
  • 查询不得超过 2000 个语言构造;也就是说,AST 复杂度的上限为 2000 个节点。

抽象语法树(也称为 AST)是源代码(在本例中为 MQL 查询字符串)的一种表示法,树中的节点映射到代码中的语法结构。

MQL 宏

MQL 包含一个宏定义实用程序。您可以使用 MQL 宏替换重复的操作,方便读取复杂查询,从而简化查询开发。您可以为表操作和函数定义宏。

宏定义以关键字 def 开头。

将查询转换为严格形式后,宏调用会被替换为相应的文本,并且宏定义会被移除。

保存包含宏的图表查询时,系统会将查询转换为严格形式,因此所有宏都不会保留。当您为提醒政策中的条件保存查询时,查询不会转换为严格形式,因此宏会保留。

用于表格操作的宏

您可以编写宏以执行新的表操作。一般语法如下所示:

def MACRO_NAME [MACRO_PARAMETER[, MACRO_PARAMETER]] = MACRO_BODY ;

要调用宏,请使用以下语法:

@MACRO_NAME [MACRO_ARG [, MACRO_ARG]]

例如,假设您使用以下查询来提取 CPU 利用率数据:

fetch gce_instance::compute.googleapis.com/instance/cpu/utilization
| every 1m
| group_by [zone], mean(val())

可以使用以下宏替换第一行:

def my_fetch = fetch gce_instance::compute.googleapis.com/instance/cpu/utilization ;

如需在查询中调用宏,请按如下所示替换原始 fetch

def my_fetch = fetch gce_instance::compute.googleapis.com/instance/cpu/utilization ;

@my_fetch
| every 1m
| group_by [zone], mean(val())

您可以用接受参数的宏替换第二行和第三行。宏定义列出了宏的参数,在宏正文中,宏的参数为 $MACRO_PARAMETER。例如,您可以定义以下宏:

def my_every time_arg = every $time_arg ;

def my_group label, aggr = group_by [$label], $aggr ;

要调用这些宏并提供参数,请在宏调用的逗号分隔列表中指定参数。该查询显示了包含所有已定义宏及其调用的查询:

def my_fetch = fetch gce_instance::compute.googleapis.com/instance/cpu/utilization ;
def my_every time_arg = every $time_arg ;
def my_group label, aggr = group_by [$label], $aggr ;

{@my_fetch}
| @my_every 1m
| @my_group zone, mean(val())

若查询转换为严格格式,则系统不会保留宏。例如,上条查询的严格形式如下所示:

fetch gce_instance::compute.googleapis.com/instance/cpu/utilization
| align mean_aligner()
| every 1m
| group_by [resource.zone],
           [value_utilization_mean: mean(value.utilization)]

用于函数的宏

对于 MQL 函数,您可以在以逗号分隔的列表中指定任何参数。括号将函数宏与表操作宏区分开。即使没有任何参数,调用中也必须有父级出现。 若查询转换为严格格式,则系统不会保留宏。

def MACRO_NAME([MACRO_PARAMETER [, MACRO_PARAMETER]]) = MACRO_BODY ;

例如,以下查询检索两个指标的表,将两个表合并为包含两个值列,然后计算名为 received_percent 的列的接收字节数与总字节数的比率:

{
  fetch k8s_pod :: kubernetes.io/pod/network/received_bytes_count ;
  fetch k8s_pod :: kubernetes.io/pod/network/sent_bytes_count
}
| join
| value [received_percent: val(0) * 100 / (val(0) + val(1))]

您可以将 received_percent 计算替换为与以下示例类似的宏:

def recd_percent(recd, sent) = $recd * 100 / ($recd + $sent) ;

要调用函数宏,请使用以下语法:

@MACRO_NAME([MACRO_ARG[, MACRO_ARG]])

在调用不带任何参数的函数宏时,您必须指定空括号,以区分调用表操作宏的调用。

以下示例展示了包含比率计算宏的上一查询:

def recd_percent(recd, sent) = $recd * 100 / ($recd + $sent) ;

{
  fetch k8s_pod :: kubernetes.io/pod/network/received_bytes_count ;
  fetch k8s_pod :: kubernetes.io/pod/network/sent_bytes_count
}
| join
| value [received_percent: @recd_percent(val(0), val(1))]

宏功能

MQL 宏是语法元素,而不是 C 预处理器中使用的宏等文本元素。这种区别意味着 MQL 宏正文在语法上必须始终是有效的表达式。它在语义上可能无效,这还取决于宏参数和宏的展开位置。

由于 MQL 宏具有语法限制,因此很少限制它们可扩展到的表达式类型。语法宏只是操纵抽象语法树的另一种方式。以下示例演示了语法宏的一些功能:

# Abbreviating a column name.
def my_col() = instance_name;

# Map-valued macro.
def my_map(c) = [$c, @my_col()];

# Abbreviating a string.
def my_zone() = 'us-central.*';

# Abbreviating a filter expression.
def my_filter(f) = zone =~ @my_zone() && $f;

MQL 还支持隐式字符串字面量串联。在编写包含长指标名称的查询时,此功能非常有用。当字符串字面量和宏参数(也必须为字符串字面量)在宏正文中彼此相邻时,宏扩展功能会将它们串联为一个字符串字面量。

在以下示例中,gce_instanceBARE_NAME 词法元素。它会自动提升为字符串字面量,这对于构建表名称很有用:

# Builds a table name in domain 'd' with the suffix 'm'.
def my_table(d, m) = gce_instance::$d '/instance/' $m;

# Table name under the given domain.
def my_compute_table(m) = @my_table('compute.googleapis.com', $m);

简而言之,以下查询使用之前定义的所有宏:

fetch @my_compute_table('cpu/utilization')
| filter @my_filter(instance_name =~ 'gke.*')
| group_by @my_map(zone)

请注意,只要语法正确,宏参数也可以是任意表达式。例如,宏 my_filter 可以将类似 instance_name =~ 'gke.*' 的布尔表达式作为其第一个参数。

缩写表操作可能也非常有用,如以下查询所示:

# Calculate the ratio between compute metrics 'm1' and 'm2'.
def my_compute_ratio m1, m2 =
  { fetch @my_compute_table($m1); fetch @my_compute_table($m2) }
  | join | div;

# Use the table op macro to calculate the ratio between CPU utilization and
# the number of reserved cores per zone.
@my_compute_ratio 'cpu/utilization', 'cpu/reserved_cores' | group_by [zone]

最后,函数宏的行为类似于常规函数:也就是说,它们允许函数提升,其中输入表的一个或多个值列成为宏的第一个参数。以下示例展示了使用函数宏的上一查询的变体:

# Simple arithmetic macro.
def my_add_two(x) = $x + 2;

# Similar to previous query, but now using the new arithmetic macro with
# function argument promotion.
fetch @my_compute_table('cpu/utilization')
| filter @my_filter(instance_name =~ 'gke.*')
| group_by @my_map(zone), [.sum.@my_add_two]

限制

MQL 宏功能不支持以下各项:

  • 嵌套宏定义:您不能在另一个宏的正文中定义宏。
  • 递归定义的宏。任何宏正文都不能引用尚未完全定义的任何宏,包括其自身。
  • 将宏定义的函数用作表操作。
  • 将宏参数用作函数或表操作的名称。
  • 在查询转换为严格形式时保留宏。宏调用将替换为相应的表达式,并会移除宏定义。