Sobre a linguagem MQL

Esta página fornece informações gerais sobre a linguagem de consulta de monitoramento (MQL, na sigla em inglês), incluindo os seguintes tópicos:

Essas informações são aplicáveis se você usa o MQL no console do Google Cloud ou na API Cloud Monitoring. Para informações sobre a estrutura das consultas da MQL, consulte Exemplos.

Atalhos para funções e operações de tabela

As consultas geralmente consistem em redes de operações de tabela conectadas por barras verticais (|), e cada uma delas começa com o nome da operação da tabela seguido de uma lista de expressões. As expressões podem conter chamadas de função que listam todos os argumentos explicitamente. Entretanto, a MQL permite que as consultas sejam expressas com vários atalhos.

Nesta seção, descrevemos os atalhos para operações de tabela, como usar funções como operações de tabela e um atalho para colunas de valor como argumentos de funções.

Para ver uma lista completa, consulte Atalhos de operação de tabela.

Atalhos para operações de tabela

Ao usar as operações fetch, group_by e filter, é possível omitir a operação de tabela explícita quando os argumentos forem suficientes para determinar a operação pretendida. Por exemplo, a consulta a seguir:

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

é equivalente a:

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

As seguintes operações group_by são equivalentes:

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

É possível omitir a palavra filter se o teste de filtro for colocado entre parênteses. Por exemplo, as duas operações filter a seguir são equivalentes:

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

É possível combinar esses formulários de atalho nas consultas. Por exemplo, a consulta a seguir:

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

é equivalente a esta forma mais explícita:

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

Para mais informações sobre atalhos de operações de tabela, consulte Atalhos de operação de tabela na referência da MQL.

Como usar uma função como uma operação de tabela

Uma operação de tabela geralmente começa com o nome de uma operação de tabela. Entretanto, a MQL permite que uma operação de tabela comece com um nome de função.

É possível usar um nome de função quando a função executará uma transformação das colunas de valor da tabela de entrada. Essa substituição é um atalho para as operações de tabela group_by, align ou value, dependendo do tipo de função que recebeu um nome.

O formato geral é:

|  FUNCTION_NAME ARG, ARG ... 

Na operação de tabela, a função usa as colunas de valor da tabela de entrada como argumentos e os argumentos seguidos por qualquer argumento para a própria função. Ao usar uma função como uma operação de tabela, você especifica os argumentos na forma de operação de tabela como uma lista separada por vírgulas, em vez de usar parênteses adjacentes (()) normalmente usados com funções.

A operação completa de tabela gerada pela expansão do atalho depende do tipo de função:

  • group_by: se você estiver usando uma função de agregação com uma operação group_by que agrega todas as colunas do identificador de série temporal (ou seja, [] para agrupamento) é possível usar a função como um atalho. Por exemplo:

    | distribution powers_of(1.1)

    é um atalho para

    | group_by [], distribution(val(0), powers_of(1.1))
  • align: se você estiver usando uma função de alinhamento como um argumento para a operação align, será possível usar a função como um atalho. Por exemplo:

    | delta

    é um atalho para

    | align delta()

    Da mesma forma,

    | rate 10m

    é um atalho para

    | align rate(10m)

    Observe que as funções de alinhador usam a série temporal de entrada como argumento implícito. Portanto, as colunas de valor não são fornecidas explicitamente nesse caso.

  • value: todas as outras funções podem funcionar como atalhos para a operação de tabela value. Por exemplo:

    | mul 3.3

    é um atalho para

    | value mul(val(0), 3.3)

    Da mesma forma,

    | div

    é um atalho para

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

    Observe que o atalho div cria uma operação de tabela de entrada com duas colunas de valor e produz uma operação de tabela com uma coluna de valor que é a proporção.

Atalho para funções de valor e coluna

É possível usar .function como atalho para function(val()) se houver uma única coluna de valor na entrada ou como atalho para function(val(0), val(1)) se houver duas colunas de valor, e assim por diante.

O ponto inicial significa: "Chame a seguinte função, fornecendo a coluna (ou colunas) de valor do ponto de entrada como o(s) argumento(s) para a função."

Por exemplo, .mean é um atalho para mean(val()). Os seguintes são equivalentes:

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

Se a tabela de entrada tiver várias colunas de valor, cada coluna se tornará um argumento para a função nesse atalho. Por exemplo, se a tabela de entrada tiver duas colunas de valor, então

.div

é um atalho para

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

Com esse atalho, é possível fornecer argumentos que não se referem a colunas de valor. Os argumentos extras são fornecidos depois dos argumentos da coluna de valor. Por exemplo, se a tabela de entrada tiver uma coluna de valor, então

.div(3)

é equivalente a

div(val(0), 3)

Variações em um fetch

A operação fetch geralmente retorna uma tabela de séries temporais denominadas por um par de tipos de métricas e recursos monitorados. Exemplo:

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

Se a métrica se aplicar apenas a um tipo de recurso monitorado, será possível omitir o recurso monitorado da consulta. A consulta a seguir é equivalente à consulta anterior, porque a métrica de utilização da CPU aplica-se apenas aos recursos monitorados gce_instance:

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

A operação fetch pode especificar apenas um tipo de recurso monitorado, com a métrica especificada em operações metric subsequentes. Este exemplo é equivalente aos exemplos de fetch anteriores:

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

Dividir a fetch dessa maneira pode ser útil quando você quiser buscar duas métricas diferentes para o mesmo recurso monitorado. Por exemplo, a consulta a seguir calcula o número de pacotes por segundo de CPU consumidos:

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

Dividir fetch também permite aplicar a filtragem apenas aos rótulos do recurso monitorado:

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

Uma fetch que denomina apenas um tipo de recurso monitorado precisa ser seguida por uma operação metric, talvez com operações filter intermediárias.

Consultas de formato restrito

Uma consulta rígida é aquela que não tem atalhos ou valores implícitos usados em consultas concisas. As consultas restritas têm as seguintes características:

  • Todos os atalhos foram substituídos.
  • Todos os argumentos implícitos são explícitos.
  • As colunas são referenciadas por nomes completos.
  • Novas colunas recebem nomes explicitamente.
  • Todas as operações de alinhamento fornecidas implicitamente são fornecidas explicitamente.

O uso do formato restrito torna a consulta mais resiliente a mudanças na estrutura das tabelas de entrada e pode deixar mais claro o que a consulta está fazendo. Usar uma consulta de formato restrito não torna a consulta mais eficiente.

Quando você salva uma consulta em um gráfico, ela é convertida em formato estrito. A caixa de diálogo de confirmação da operação de salvamento exibe o formato restrito.

As consultas concisas de políticas de alertas não são convertidas em formato estrito. As consultas para políticas de alertas são armazenadas conforme você as fornece; use a forma concisa ou rigorosa.

Com atalhos e formatos restritos disponíveis, é possível encontrar consultas MQL equivalentes que são muito diferentes umas das outras. Por exemplo, a consulta a seguir, que calcula o número de pacotes recebidos por segundo de CPU consumida, usa muitos atalhos:

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

Quando você salva essa consulta em um gráfico ou como parte de uma política de alertas, a consulta de formato restrito faz exatamente a mesma coisa. No entanto, o formato restrito pode ser muito diferente, conforme mostrado no exemplo a seguir:

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)]

Quando você edita a definição salva do gráfico, o editor de código exibe o formulário restrito.

Como fazer a correspondência com a coluna resource.project_id

Os projetos do Google Cloud têm um nome de exibição que aparece nos menus, mas não identifica exclusivamente o projeto. O nome de exibição do projeto pode ser "Demonstração do Monitoring".

Os projetos também têm dois campos que atuam como identificadores:

  • ID do projeto: um identificador exclusivo da string. Ele geralmente é baseado no nome de exibição. Os IDs do projeto são definidos quando o projeto é criado, geralmente concatenando os elementos do nome do projeto e possivelmente adicionando números ao final, se necessário para gerar exclusividade. Um ID do projeto pode ter o formato "monitoring-demo" ou "monitoring-demo-2349". O ID do projeto às vezes é chamado de "nome do projeto".
  • Número do projeto: um identificador numérico exclusivo.

Cada tipo de recurso monitorado inclui um rótulo project_id com uma representação de string do número do projeto que é proprietário do recurso e dos dados desse recurso.

Nas consultas MQL, esse rótulo é chamado de resource.project_id. O rótulo resource.project_id tem o número do projeto em forma de texto como o valor, mas a MQL converte esse valor ao ID do projeto em determinadas situações.

Nos casos a seguir, a MQL trata o valor do rótulo resource.project_id como o ID do projeto em vez de número do projeto:

  • A legenda de um gráfico exibe o ID do projeto em vez do número do projeto para o valor do rótulo resource.project_id.

  • As comparações de igualdade de valor de resource.project_id com um literal de string reconhecem tanto o número quanto o ID do projeto. Por exemplo, os seguintes itens retornam "true" para os recursos que pertencem a este projeto:

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

    Esse caso se aplica aos operadores == e != e aos formatos de função eq() e ne().

  • Uma correspondência de expressão regular no rótulo resource.project_id funciona corretamente no número ou no ID do projeto. Por exemplo, as duas expressões a seguir retornam true para recursos pertencentes a esse projeto:

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

    Esse caso se aplica aos operadores =~ e !~ e ao formato de função re_full_match.

Para todos os outros casos, o valor real do rótulo resource.project_id é usado. Por exemplo, concatenate("project-", resource.project_id) resulta no valor project-530310927541, e não em project-monitoring-demo.

Proporções e o "efeito de borda"

Em geral, é melhor calcular proporções com base em séries temporais coletadas para um único tipo de métrica usando valores de rótulo. Uma proporção calculada em dois tipos de métricas diferentes está sujeita a anomalias devido a períodos de amostragem e janelas de alinhamento diferentes.

Por exemplo, suponha que você tem dois tipos de métrica diferentes, uma contagem total de RPC e uma contagem de erros de RPC, e quer calcular a proporção de contagem de erros de RPC em relação ao total de RPCs. As RPCs malsucedidas são contadas na série temporal dos dois tipos de métricas. Portanto, há uma chance de que, quando você alinha a série temporal, uma RPC malsucedida não apareça no mesmo intervalo de alinhamento para as duas séries temporais. Essa diferença pode ocorrer por vários motivos, incluindo:

  • Como há duas séries temporais diferentes gravando o mesmo evento, há dois valores de contador subjacentes implementando a coleta, e eles não são atualizados atomicamente.
  • As taxas de amostragem podem ser diferentes. Quando as séries temporais estão alinhadas a um período comum, as contagens de um único evento podem aparecer em intervalos de alinhamento adjacentes na série temporal de diferentes métricas.

A diferença no número de valores nos intervalos de alinhamento correspondentes pode levar a valores de proporção error/total ilógicos, como 1/0 ou 2/1.

Proporções de números maiores são menos propensas a resultar em valores sem sentido. É possível conseguir números maiores por agregação, usando uma janela de alinhamento maior que o período de amostragem ou agrupando dados para determinados rótulos. Essas técnicas minimizam o efeito de pequenas diferenças no número de pontos em um determinado intervalo. Ou seja, uma disparidade de dois pontos é mais significativa quando o número esperado de pontos em um intervalo é 3 do que quando o número esperado é 300.

Se você estiver usando tipos de métricas integrados, talvez não tenha opção senão calcular proporções nos tipos de métricas para obter o valor necessário.

Se você estiver criando métricas personalizadas para contar o mesmo resultado (como RPCs que retornam status de erro) em duas métricas diferentes, considere uma única métrica que inclua cada contagem apenas uma vez. Por exemplo, suponha que você esteja contando RPCs e queira rastrear a proporção de RPCs malsucedidas para todas as RPCs. Para resolver esse problema, crie um único tipo de métrica para contar RPCs e use um rótulo para registrar o status da invocação, incluindo o status "OK". Depois, cada valor de status, de erro ou "OK" será registrado ao atualizar um contador único para esse caso.

Formatos de data MQL

Atualmente, a MQL é compatível com um número limitado de formatos de data. Nas consultas de MQL, as datas são expressas como uma das seguintes opções:

  • d'BASE_STRING'
  • D'BASE_STRING'

A BASE_STRING é uma string no formato 2010/06/23-19:32:15-07:00. O primeiro traço (-), separando a data e a hora, pode ser substituído por um espaço. No componente de tempo, partes do horário do relógio (19:32:15) ou o especificador de fuso horário (-07:00) podem ser descartados.

Os exemplos a seguir são datas válidas nas consultas de 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'

A tabela a seguir lista a sintaxe de BASE_STRING:

Estrutura Significado
%Y/%m/%d Data
%Y/%m/%d %H
%Y/%m/%d-%H
Data e hora
%Y/%m/%d %H:%M
%Y/%m/%d-%H:%M
Data, hora e minuto
%Y/%m/%d %H:%M:%S
%Y/%m/%d-%H:%M:%S
Data, hora, minuto e segundo
%Y/%m/%d %H:%M:%E*S
%Y/%m/%d-%H:%M:%E*S
Data, hora, minuto e fração de segundo
%Y/%m/%d %Ez Data com fuso horário
%Y/%m/%d %H%Ez
%Y/%m/%d-%H%Ez
Data e hora, com fuso horário
%Y/%m/%d %H:%M%Ez
%Y/%m/%d-%H:%M%Ez
Data, hora e minuto, com fuso horário
%Y/%m/%d %H:%M:%S%Ez
%Y/%m/%d-%H:%M:%S%Ez
Data, hora, minuto e segundo, com fuso horário
%Y/%m/%d %H:%M:%E*S%Ez
%Y/%m/%d-%H:%M:%E*S%Ez
Data, hora, minuto e fração de segundo, com fuso horário

Comprimento e complexidade das consultas

As consultas da MQL podem ser longas e complexas, mas não ilimitadas.

  • Um texto de consulta, codificado como UTF-8, tem o limite de 10.000 bytes.
  • Uma consulta é limitada a 2.000 construções de idiomas. Ou seja, a complexidade do AST é limitada a 2.000 nós.

Uma árvore de sintaxe abstrata (AST na sigla em inglês) é uma representação do código-fonte (nesse caso, a string de consulta da MQL) em que nós no mapa são mapeadas para estruturas sintáticas no código.

Macros do MQL

O MQL inclui um utilitário de definição de macro. É possível usar as macros MQL para substituir operações repetidas, facilitar a leitura de consultas complexas e facilitar o desenvolvimento delas. É possível definir macros para operações de tabela e funções.

As definições de macro começam com a palavra-chave def.

Quando você converte uma consulta em formulário restrito, as invocações de macro são substituídas pelo texto correspondente, e as definições de macro são removidas.

Quando você salva uma consulta de gráfico que inclui macros, ela é convertida em um formato restrito, para que as macros não sejam preservadas. Quando você salva uma consulta para uma condição em uma política de alertas, ela não é convertida em formato estrito, de modo que as macros são preservadas.

Macros para operações de tabela

É possível escrever macros para fazer novas operações de tabela. A sintaxe geral é assim:

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

Para invocar a macro, use a seguinte sintaxe:

@MACRO_NAME [MACRO_ARG [, MACRO_ARG]]

Por exemplo, suponha que você esteja usando a seguinte consulta para buscar dados de utilização da CPU:

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

A primeira linha pode ser substituída pela seguinte macro:

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

Para invocar a macro na consulta, substitua o fetch original da seguinte maneira:

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

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

É possível substituir a segunda e a terceira linhas por macros com argumentos. A definição da macro lista os parâmetros para a macro e, no corpo da macro, consulte os parâmetros para a macro como $MACRO_PARAMETER. Por exemplo, defina as seguintes macros:

def my_every time_arg = every $time_arg ;

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

Para invocar essas macros e fornecer os argumentos, especifique os argumentos em uma lista delimitada por vírgulas nas invocações da macro. Veja a seguir a consulta com todas as macros definidas e as invocações:

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())

As macros não são preservadas quando a consulta é convertida em formato restrito. Por exemplo, a forma estrita da consulta anterior é semelhante a esta:

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

Macros de funções

Para funções MQL, especifique quaisquer parâmetros em uma lista delimitada por vírgulas entre parênteses. Os parênteses diferenciam uma macro de função de uma macro de operação de tabela. O parêntese precisa aparecer na invocação, mesmo que não haja argumentos. As macros não são preservadas quando a consulta é convertida em formato restrito.

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

Por exemplo, a consulta a seguir recupera tabelas de duas métricas, combina as duas tabelas em uma com duas colunas de valor e calcula a proporção de bytes recebidos para o total de bytes de uma coluna chamada 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))]

É possível substituir o cálculo received_percent por uma macro como no exemplo a seguir:

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

Para invocar uma macro de função, use esta sintaxe:

@MACRO_NAME([MACRO_ARG[, MACRO_ARG]])

Ao invocar uma macro de função sem argumentos, você precisa especificar os parênteses vazios para distinguir a invocação da invocação de uma macro de operação de tabela.

O exemplo a seguir mostra a consulta anterior com uma macro para o cálculo da proporção:

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))]

Recursos de macro

As macros MQL são elementos sintáticos, ao contrário dos elementos textuais, como as usadas no pré-processador C. Essa distinção significa que um corpo de macro MQL precisa ser sempre uma expressão sintaticamente válida. Talvez ela não seja semanticamente válida, o que também depende dos argumentos da macro e da localização em que a macro é expandida.

Como as macros MQL são sintáticas, há muito poucas restrições no tipo de expressão para que elas podem se expandir. Macros sintáticas são apenas outra maneira de manipular a árvore de sintaxe abstrata. Os exemplos a seguir mostram algumas coisas que você pode fazer com macros sintáticas:

# 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;

A MQL também é compatível com concatenação literal de strings implícitas. Esse recurso é muito útil na criação de consultas que incluem nomes de métricas longos. Quando um literal de string e um argumento de macro, que também precisam ser um literal de string, aparecem lado a lado no corpo da macro, a expansão de macro os concatena em um único literal de string.

No exemplo a seguir, gce_instance é um elemento lexical de BARE_NAME. Ela é promovida automaticamente para um literal de string, o que é útil na criação de nomes de tabelas:

# 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);

Juntando tudo, a seguinte consulta usa todos os macros definidos anteriormente:

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

Observe que os argumentos de macro também podem ser expressões arbitrárias, desde que estejam sintaticamente corretos. Por exemplo, a macro my_filter pode usar uma expressão booleana como instance_name =~ 'gke.*' como seu primeiro argumento.

Abreviar também as operações de tabela, como demonstra a consulta a seguir:

# 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]

Por fim, as macros de função podem se comportar como funções normais. Ou seja, eles permitem a promoção da função em que a coluna de valor ou as colunas da tabela de entrada se tornam os primeiros argumentos para a macro. O exemplo a seguir mostra uma variante da consulta anterior que usa uma macro de função:

# 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]

Limitações

O recurso de macro MQL não é compatível com o seguinte:

  • Aninhamento de definições de macro: não é possível definir uma macro no corpo de outra.
  • Macros definidas de modo recursivo. Nenhum corpo de macro pode referenciar qualquer macro, inclusive ela mesma, que ainda não tenha sido totalmente definida.
  • Uso de funções definidas por macro como operações de tabela.
  • Uso de argumentos macro como nomes de funções ou operações de tabela.
  • Preservação de macros quando a consulta é convertida em forma restrita. As invocações de macro são substituídas pelas expressões correspondentes, e as definições de macro são removidas.