简介

Prometheus提供函数式的表达式语言PromQL(Prometheus Query Language) ,可以使用户实时地查找和聚合时间序列数据。表达式计算结果可以在图表中展示,也可以在Prometheus表达式浏览器中以表格形式展示,或者作为数据源, 以HTTP API的方式提供给外部系统使用。

1. HTTP API

在prometheus服务器上,我们可以通过访问/api/v1来查询prometheus状态,也就是http://localhost:9090/api/v1

curl http://localhost:9090/api/v1
<a href="/api/v1/">Moved Permanently</a>

不过我们这样是没办法查询到数据的,毕竟这是接口的地址,但是出现这个输出就说明接口是可以访问的

1.1. 接口的格式

API的返回值是JSON格式的。每个请求成功的返回值都是以2xx开头的编码。

到达API处理的无效请求,返回一个JSON错误对象,并返回下面的错误码:

  • 400 Bad Request。当参数错误或者丢失时。
  • 422 Unprocessable Entity。当一个表达式不能被执行时。
  • 503 Service Unavailable。当查询超时或者中断时。

1.2. 表达式查询

查询语言表达式可以在瞬时向量或者范围向量中执行。

  • Instant queries(即时查询):

    GET /api/v1/query

    POST /api/v1/query

    URL查询参数:

    • query=<string>: Prometheus表达式要查询的字符串。
    • time=<rfc3339 | unix_timestamp>: 执行这个查询的时间戳,可选项,缺省则表示当前服务器时间
    • timeout=<duration>: 执行的超时时间设置,默认由-query.timeout参数设置

    查询结果如下

    {
     "resultType": "matrix" | "vector" | "scalar" | "string",
     "result": <value>
    }
    

    比如:下面例子执行的是在时刻是2015-07-01T20:10:51.781Zup表达式:

    $ curl 'http://localhost:9090/api/v1/query?query=up&time=2015-07-01T20:10:51.781Z'
    {
     "status": "success",
     "data":{
        "resultType": "vector",
        "result" : [
             {
                "metric" : {
                   "__name__" : "up",
                   "job" : "prometheus",
                   "instance" : "localhost:9090"
                },
                "value": [ 1435781451.781, "1" ]
             },
             {
                "metric" : {
                   "__name__" : "up",
                   "job" : "node",
                   "instance" : "localhost:9100"
                },
                "value" : [ 1435781451.781, "0" ]
             }
        ]
     }
    }
    
  • 范围查询

    GET /api/v1/query_range

    POST /api/v1/query_range

    URL查询参数

    • query=<string>: Prometheus表达式查询字符串。
    • start=<rfc3339 | unix_timestamp>: 开始时间戳。
    • end=<rfc3339 | unix_timestamp>: 结束时间戳。
    • step=<duration | float>: 查询时间步长,范围时间内每step秒执行一次。
    • timeout=<duration>: 执行超时时间设置,默认由-query.timeout标志设置

    下面查询结果格式的data部分:

    {
        "resultType": "matrix",
        "result": <value>
    }
    

    下面例子评估的查询条件up,且30s范围的查询,步长是15s。

    $ curl 'http://localhost:9090/api/v1/query_range?query=up&start=2015-07-01T20:10:30.781Z&end=2015-07-01T20:11:00.781Z&step=15s'
    {
       "status" : "success",
       "data" : {
          "resultType" : "matrix",
          "result" : [
             {
                "metric" : {
                   "__name__" : "up",
                   "job" : "prometheus",
                   "instance" : "localhost:9090"
                },
                "values" : [
                   [ 1435781430.781, "1" ],
                   [ 1435781445.781, "1" ],
                   [ 1435781460.781, "1" ]
                ]
             },
             {
                "metric" : {
                   "__name__" : "up",
                   "job" : "node",
                   "instance" : "localhost:9091"
                },
                "values" : [
               [ 1435781430.781, "0" ],
                   [ 1435781445.781, "0" ],
                   [ 1435781460.781, "1" ]
                ]
             }
          ]
       }
    }
    

1.3. 查询元数据

  • 通过标签匹配器找到度量指标列表

    下面例子返回了度量指标列表 且不返回时间序列数据值。

    GET /api/v1/series

    URL查询参数:

    • match[]=<series_selector>: 选择器是series_selector。这个参数个数必须大于等于1.
    • start=<rfc3339 | unix_timestamp>: 开始时间戳。
    • end=<rfc3339 | unix_timestamp>: 结束时间戳。

    返回结果的data部分,是由key-value键值对的对象列表组成的。

    下面这个例子返回时间序列数据, 选择器是up或者process_start_time_seconds{job="prometheus"}

    $ curl -g 'http://localhost:9090/api/v1/series?match[]=up&match[]=process_start_time_seconds{job="prometheus"}'
    {
       "status" : "success",
       "data" : [
          {
             "__name__" : "up",
             "job" : "prometheus",
             "instance" : "localhost:9090"
          },
          {
             "__name__" : "up",
             "job" : "node",
             "instance" : "localhost:9091"
          },
          {
             "__name__" : "process_start_time_seconds",
             "job" : "prometheus",
             "instance" : "localhost:9090"
          }
       ]
    }
    
  • 查询标签值

    下面这个例子,返回了带有指定标签的标签值列表

    GET /api/v1/label//values

    这个返回JSON结果的data部分是带有label_name=job的值列表:

    $ curl http://localhost:9090/api/v1/label/job/values
    {
       "status" : "success",
       "data" : [
          "node",
          "prometheus"
       ]
    }
    
  • 删除时间序列

    下面的例子,是从Prometheus服务中删除匹配的度量指标和标签列表:

    DELETE /api/v1/series

    URL查询参数

    • match[]=<series_selector>: 删除符合series_selector匹配器的时间序列数据。参数个数必须大于等于1.

    返回JSON数据中的data部分有以下的格式

     {
        "numDeleted": <number of deleted series>
     }
    

    下面的例子删除符合度量指标名称是up或者时间序为process_start_time_seconds{job="prometheus"}

    $ curl -XDELETE -g 'http://localhost:9090/api/v1/series?match[]=up&match[]=process_start_time_seconds{job="prometheus"}'
    {
       "status" : "success",
       "data" : {
          "numDeleted" : 3
       }
    }
    

2. 表达式语言数据类型

在Prometheus的表达式语言PromQL中,所有的表达式或者子表达式都可以归为四种类型:

  • string :字符串类型
  • scalar :标量(浮点值)

  • instant vector :瞬时向量,它是指在同一时刻,抓取的所有metrics指标数据。这些metrics指标数据的key都是相同的,也就是时间戳是相同的。
  • range vector:范围向量,它是指在任何一个时间范围之内,抓取的所有metrics指标数据。

2.1. 字符串

字符串需要用单引号'、双引号"或者反引号表示

PromQL遵循着与Go语言相同的转义规则。使用单引号,双引号中,反斜杠\作为转义字符,它后面还可以跟着a, b, f, n, r, t, v或者\。 也可以使用八进制(\nnn)或者十六进制(\xnn, \unnnn和\Unnnnnnnn)提供特定字符。

在反引号内不处理转义字符。与Go不同,PromQL不会丢弃反引号中的换行符。例如:

"this is a string"
'these are unescaped: \n \\ \t'
`these are not unescaped: \n ' " \t`

image-20200612214927733

2.2. 标量

标量浮点值可以直接写成这样的形式 [-](digits)[.(digits)]

-2.43

image-20200612215918944

2.3. 瞬时向量选择器

瞬时向量选择器可以对一组时间序列数据进行筛选,并给出结果中的每个结果键值对(时间戳-样本值)。最简单的形式是,只有一个metric名称被指定。在一个瞬时向量中这个结果包含有这个metrics指标名称的所有样本数据的key-value。

下面这个例子选择所有时间序列度量名称为http_requests_total的样本数据:

http_requests_total

通过在度量指标后面增加{}一组标签可以进一步地过滤这些时间序列数据。

下面这个例子选择了度量指标名称为http_requests_total,且一组标签为job=prometheus, group=canary:

http_requests_total{job=”prometheus”,group=”canary”}

可以采用不匹配的标签值也是可以的,或者用正则表达式不匹配标签。标签匹配操作如下所示:

  • =: 精确地匹配标签给定的值
  • !=: 不等于给定的标签值
  • =~: 正则表达匹配给定的标签值
  • !=: 给定的标签值不符合正则表达式

例如:度量指标名称为http_requests_total,正则表达式匹配标签environmentstaging, testing, development的值,且http请求方法不等于GET

http_requests_total{environment=~”staging testing development”, method!=”GET”}

匹配空标签值的标签匹配器也可以选择没有设置任何标签的所有时间序列数据。正则表达式完全匹配。

向量选择器必须指定一个度量指标名称或者至少不能为空字符串的标签值。以下表达式是非法的:

{job=~”.*”} #Bad!

上面这个例子既没有度量指标名称,标签选择器也可以正则匹配空标签值,所以不符合向量选择器的条件

相反地,下面这些表达式是有效的,第一个一定有一个字符。第二个有一个有用的标签method

{job=~”.+”} # Good!{job=~”.*”, method=”get”} # Good!

标签匹配器能够被应用到度量指标名称,使用__name__标签筛选度量指标名称。例如:表达式http_requests_total等价于{__name__="http_requests_total"}。 其他的匹配器,如:= ( !=, =~, !~)都可以使用。下面的表达式选择了度量指标名称以job:开头的时间序列数据:

{name=~”^job:.*”} #

2.4. 范围向量选择器

范围向量类似于瞬时向量, 所不同在于,它们从当前实例来选择样本范围区间。在语法上,时间长度被追加在向量选择器尾部的方括号[]之中,用以指定对于每个样本范围区间中的每个元素应该抓取的时间范围样本区间。

时间长度有一个数值决定,后面可以跟下面的单位:

  • s - seconds
  • m - minutes
  • h - hours
  • d - days
  • w - weeks
  • y - years

在下面这个例子中, 选择过去5分钟内,度量指标名称为http_requests_total, 标签为job="prometheus"的时间序列数据:

http_requests_total{job=”prometheus”}[5m]

2.5. 偏移修饰符

这个offset偏移修饰符允许在查询中改变单个瞬时向量和范围向量中的时间偏移

例如,下面的表达式返回相对于当前时间的前5分钟时的时刻, 度量指标名称为http_requests_total的时间序列数据:

http_requests_total offset 5m

注意:offset偏移修饰符必须直接跟在选择器后面,例如:

sum(http_requests_total{method=”GET”} offset 5m) // GOOD.

然而,下面这种情况是不正确的

sum(http_requests_total{method=”GET”}) offset 5m // INVALID.

offset偏移修饰符在范围向量上和瞬时向量用法一样的。下面这个返回了相对于当前时间的前一周时,过去5分钟的度量指标名称为http_requests_total的速率:

rate(http_requests_total[5m] offset 1w)

2.6. 子查询

子查询可以在一个给定的范围和结果内进行查询。子查询的结果是一个范围向量

'[' ':' [] ']' [ offset ]

2.7. 注释

PromQL支持行首以#开头的行为注释行

    # This is a comment

2.8. 运算符和函数

这两个也是查询中需要用到的,内容我们会在后面详细说

3. 复杂查询

我们今天主要就是要通过一个非常复杂的例子来帮大家了解我们日常生产系统中的查询是怎样组成的。我们的例子来自于我曾经做过的一个报警指标,里面的都是一些表达式。我们挑选CPU作为我们今天的例子。

round((1 - avg(rate(node_cpu_seconds_total{mode="idle"}[15m])) by (instance)) * 100) > 80

这个指标里面涉及了我们几乎所有的知识点,他的主要目标是通过监控CPU空闲率来计算出现在CPU的繁忙程度,如果超过80%就需要报警了。

3.1. 查询普通的指标

如果我们只需要查询某一个指标的值,我们只需要把这个值放入查询框,此例中是node_cpu_seconds_total

image-20200725222816264

3.2. 选择(带标签的)查询

如果我们想选出某个标签等于特定值的结果,比如:mode=“idle”,就需要使用node_cpu_seconds_total{mode="idle"}

image-20200725222844802

3.3. 指定时间段

在指标的后面使用[15m]这种带时间参数的,就可以选出在15m之内的数值node_cpu_seconds_total{mode="idle"}[15m]

image-20200725222943122

感觉输出有点不太对劲啊,这是因为,如果要求一段时间内的值,这是需要使用一些修饰的,也就是rate和irate

3.4. rate和irate

rate结果如下

image-20200725223103669

irate结果如下

image-20200725223125141

irate和rate都会用于计算某个指标在一定时间间隔内的变化速率。但是它们的计算方法有所不同:irate取的是在指定时间范围内的最近两个数据点来算速率,而rate会取指定时间范围内所有数据点,算出一组速率,然后取平均值作为结果。

所以官网文档说:irate适合快速变化的计数器(counter),而rate适合缓慢变化的计数器(counter)。

根据以上算法我们也可以理解,对于快速变化的计数器,如果使用rate,因为使用了平均值,很容易把峰值削平。除非我们把时间间隔设置得足够小,就能够减弱这种效应。

3.5. 求平均值的函数

使用avg()函数可以求函数值,当然,我们还可以有其他的比如min(),max(),sum()等

image-20200725223308328

感觉结果又不太正确了,这是因为这边结果需要加上by,这和sql中的group by的道理是一样的,avg函数需要按照某一个by的字段进行求平均数

image-20200725223428061

3.6. 求出我们要的值

系统的使用是由很多部分组成的,但是空闲的部分缺只有一个,所以我们想得到系统使用率就用1减去空闲的百分比就是我们的系统使用率了,而不用再把每个使用CPU的指标相加。

image-20200725223746626

4.7. 优化

我们发现其实我们得出的结果往往是带小数的,比如0.8032,但是在实际的使用中,如果我们把指标定为0.8就报警,如果CPU有一些抖动就会影响报警了,所以我们使用100去乘以结果,得到80.32,然后使用round()函数去四舍五入得到整数,再去和80比较,就会得到一个整数去和80比较,看上去就比较清晰。

image-20200725223838650

##