TICKscript 语法
概念
“引言”和“开始使用”章节介绍了 **节点** 和 **管道** 的关键概念。节点代表进程调用单元,它们接收批量数据或逐点流式数据,然后修改这些数据、存储这些数据,或基于数据的变化触发其他活动,例如警报。管道只是逻辑上组织的节点链。
在 Kapacitor 中,TICKscript 用于直接定义任务,以及定义模板任务,模板任务充当可重用的模板,用于生成新任务。
Go
TICKscript 语法受到了多种语言的启发。其中影响最大的是 Go。例如,这一点在变量声明惯用法、字符串模板、duration 等类型、lambda 表达式中使用的函数,以及文档其他地方的显式体现中可见。
语法子空间
在使用 TICKscript 时,会遇到几个可能引起用户困惑的语法子空间。首要的是 TICKscript 文件本身的 TICKscript 语法。它主要由变量声明和管道中链接在一起的节点组成。在创建时,query 节点需要一个表示 InfluxQL 语句的字符串。因此,InfluxQL 是可能用到的第一个语法子空间。其他节点和方法使用 Lambda 表达式,这是会遇到的第二个语法子空间。这些空间之间的语法,例如访问变量、标签和字段值时,可能不同,这有时会成为困惑的来源。
总而言之,在使用 TICKscript 时需要注意的两个语法子空间是:
有向无环图(DAG)
正如“开始使用”中所述,管道是一个有向无环图(DAG)。(更多信息请参阅 Wolfram 或 Wikipedia)。它包含有限数量的节点(也称为顶点)和边。每条边都从一个节点指向另一个节点。没有边路径可以回到路径中的前一个节点,否则就会形成周期或循环。TICKscript 路径(也称为管道和链)通常以一个数据源定义节点开始,通过一条边指向一个数据集定义节点,然后将其结果传递给数据处理节点。
TICKscript 语法
TICKscript 区分大小写并使用 Unicode。TICKscript 解析器自上而下、自左向右扫描 TICKscript 代码,在遇到变量和节点时实例化它们,然后将它们链接成管道。加载 TICKscript 时,解析器会检查节点上调用的链接方法是否有效。如果遇到无效的链接方法,解析器将抛出错误,并显示消息“no method or property <identifier> on <node type>”。
代码表示
源文件应使用 **UTF-8** 编码。脚本由 **声明** 和 **表达式** 组成。声明导致变量的创建,并且发生在单行上。表达式可以跨越多行,并导致创建整个管道、管道 **链** 或管道 **分支**。
**空格** 在声明中用于分隔变量名、运算符和字面量值。它也用于表达式中创建缩进,这些缩进指示方法调用的层次结构。这也有助于提高脚本的可读性。否则,空格将被忽略。
**注释** 可以通过在文本前使用一对斜杠“//”来创建单行注释。注释斜杠前面可以有空格,并且不必是新行的第一个字符。
关键字
关键字是语言中具有特殊含义的标记,因此不能用作函数或变量的标识符。TICKscript 非常简洁,仅包含少量关键字。
表 1 – 关键字
| 单词 | 用法 |
|---|---|
| TRUE | 字面量布尔值“true”。 |
| FALSE | 字面量布尔值“false”。 |
| AND | 标准布尔连接运算符。 |
| OR | 标准布尔析取运算符。 |
| lambda | 标识后面的内容将被解释为 lambda 表达式。 |
| var | 开始一个变量声明。 |
| dbrp | 开始一个数据库声明。 |
由于 TICKscript 中可用的原生节点类型有限,每种节点类型,如 batch 或 stream,都可以被认为是关键的。节点类型及其分类将在下面的“节点类型分类”部分中详细讨论。
运算符
TICKscript 支持传统的数学运算符以及一些适合其数据处理领域的运算符。
表 2 – 标准运算符
| 运算符 | 用法 | 示例 |
|---|---|---|
| + | 加法和字符串连接 | 3 + 6, total + count 和 'foo' + 'bar' |
| - | 减法 | 10 - 1, total - errs |
| * | 乘法 | 3 * 6, ratio * 100.0 |
| / | 除法 | 36 / 4, errs / total |
| == | 相等性比较 | 1 == 1, date == today |
| != | 不等性比较 | result != 0, id != "testbed" |
| < | 小于比较 | 4 < 5, timestamp < today |
| <= | 小于等于比较 | 3 <= 6, flow <= mean |
| > | 大于比较 | 6 > 3.0, delta > sigma |
| >= | 大于等于比较 | 9.0 >= 8.1, quantity >= threshold |
| =~ | 正则表达式匹配。右值必须是正则表达式 或包含此类表达式的变量。 | tag =~ /^cz\d+/ |
| !~ | 正则表达式不匹配。右值必须是正则表达式 或包含此类表达式的变量。 | tag !~ /^sn\d+/ |
| ! | 逻辑非 | !TRUE, !(cpu_idle > 70) |
| AND | 逻辑与 | rate < 20.0 AND rate >= 10 |
| OR | 逻辑或 | status > warn OR delta > sigma |
标准运算符用于 TICKscript 和 Lambda 表达式。
表 3 – 链接运算符
| 运算符 | 用法 | 示例 |
|---|---|---|
| | | 声明一个链接方法调用,创建一个新节点实例并将其链接到其上方的节点。 | stream| from() |
| . | 声明一个属性方法调用,设置或更改其所属节点的内部属性。 | from().database(mydb) |
| @ | 声明一个用户定义函数(UDF)调用。本质上是一个链接方法,它向管道添加一个新的 UDF 节点。 | from()...@MyFunc() |
链接运算符在表达式中使用,用于定义管道或管道段。
变量与字面量
TICKscript 中的变量可用于存储和重用值,以及提供一个友好的助记符以快速理解变量的含义。它们通常在赋值字面量值的同时声明。在用作 模板任务 的 TICKscript 中,它们也可以仅使用类型标识符声明。
变量
变量使用关键字 var 在声明的开头进行声明。变量是不可变的,不能在脚本的后续部分重新赋值,但可以在其他声明中使用,也可以传递给方法。在模板任务中,变量也用作占位符,在模板用于创建新任务时进行填充。
有关处理 **模板任务** 的详细介绍,请参阅指南 模板任务。如果一个 TICKscript 被证明很有用,可以将其用作模板任务,以便快速创建其他类似的任务。因此,建议尽可能多地使用变量。
命名变量
变量标识符必须以标准 ASCII 字母开头,后面可以跟任意数量的字母、数字和下划线。可以使用大写和小写。在用于直接定义任务的 TICKscript 中,变量保存的类型取决于为其分配的字面量值。在为任务模板编写的 TICKscript 中,也可以使用变量将要保存的类型的关键字来设置类型。在用于直接定义任务的 TICKscript 中,使用类型标识符将导致编译时错误 invalid TICKscript: missing value for var "<VARNAME>".。
示例 1 – 任务的变量声明
var my_var = 'foo'
var MY_VAR = 'BAR'
var my_float = 2.71
var my_int = 1
var my_node = stream模板中的变量声明不需要字面量赋值,如下面的示例 2 所示。
示例 2 – 任务模板中的变量声明
var measurement string
var frame duration
var warn = float
var period = 12h
var critical = 3.0字面量值
字面量值被解析为 TICKscript 中可用类型的实例。它们可以直接在方法参数中声明,也可以赋值给变量。解析器根据上下文解释类型,并创建以下基本类型的实例:布尔型、字符串型、浮点型、整型。正则表达式、列表、lambda 表达式、duration 结构和节点也被识别。解析器用来识别类型的规则在以下“类型”部分中讨论。
类型
TICKscript 识别五种类型标识符。这些标识符可直接用于为模板任务设计的 TICKscript 中。否则,字面量的类型将根据其声明进行解释。
表 4 – 类型标识符
| 标识符 | 用法 |
|---|---|
| string | 在模板任务中,将变量声明为 string 类型。 |
| 持续时间 | 在模板任务中,将变量声明为 duration 类型。 |
| int | 在模板任务中,将变量声明为 int64 类型。 |
| float | 在模板任务中,将变量声明为 float64 类型。 |
| lambda | 在模板任务中,将变量声明为 Lambda 表达式类型。 |
布尔值
布尔值使用布尔关键字:TRUE 和 FALSE 生成。请注意,这些关键字使用全大写字母。当使用小写字符(例如 True 或 true)时,解析器将抛出错误。
示例 3 – 布尔字面量
var true_bool = TRUE
...
|flatten()
.on('host','port')
.dropOriginalFieldName(FALSE)
...在上面的示例 3 中,第一行显示了一个简单的使用布尔字面量的赋值。第二个示例显示了在方法调用中使用布尔字面量 FALSE。
数值类型
任何仅包含数字且可选包含小数点的字面量标记都将导致生成数值类型实例。TICKscript 基于 Go 理解两种数值类型:int64 和 float64。任何包含小数点的数值标记都将生成 float64 值。任何不包含小数点的数值标记都将生成 int64 值。如果整数以零字符 0 作为前缀,则被解释为八进制。
示例 4 – 数值字面量
var my_int = 6
var my_float = 2.71828
var my_octal = 0400
...在上面的示例 4 中,my_int 的类型是 int64,my_float 的类型是 float64,my_octal 的类型是八进制 int64。
持续时间字面量
持续时间字面量定义一个时间跨度。它们的语法遵循 InfluxQL 中的语法。持续时间字面量由两部分组成:一个整数和一个持续时间单位。它本质上是一个整数,后面跟着一个或两个保留字符,代表一个时间单位。
下表列出了声明持续时间类型所使用的时间单位。
表 5 – 持续时间字面量单位
| 单位 | 含义 |
|---|---|
| u 或 µ | 微秒(百万分之一秒) |
| ms | 毫秒(千分之一秒) |
| s | second |
| m | minute |
| h | 小时 |
| d | 天 |
| w | 周 |
示例 5 – 持续时间表达式
var span = 10s
var frequency = 10s
...
var views = batch
|query('SELECT sum(value) FROM "pages"."default".views')
.period(1h)
.every(1h)
.groupBy(time(1m), *)
.fill(0)在上面的示例 5 中,前两行显示了持续时间类型的声明。第一个代表 10 秒的时间跨度,第二个代表 10 秒的时间帧。最后一个示例展示了在方法调用中直接声明持续时间字面量。
Strings
字符串以一个或三个单引号开始:' 或 '''。字符串可以使用加法 + 运算符连接。要在单引号分隔的字符串中转义引号,请使用反斜杠字符。如果字符串中预期会遇到大量单引号,则应改用三单引号分隔。三引号分隔的字符串不需要转义序列。在这两种字符串分隔方式下,用于访问字段和标签值的双引号都可以不经转义使用。
示例 6 – 基本字符串
var region1 = 'EMEA'
var old_standby = 'foo' + 'bar'
var query1 = 'SELECT 100 - mean(usage_idle) AS stat FROM "telegraf"."autogen"."cpu" WHERE cpu = \'cpu-total\' '
var query2 = '''SELECT 100 - mean(usage_idle) AS stat FROM "telegraf"."autogen"."cpu" WHERE cpu = 'cpu-total' '''
...
batch
|query('''SELECT 100 - mean(usage_idle) AS stat FROM "telegraf"."autogen"."cpu" WHERE cpu = 'cpu-total' ''')
...在上面的示例 6 中,第一行显示了一个简单的使用字符串字面量的字符串赋值。第二行使用了连接运算符。第三行和第四行展示了两种声明复杂字符串字面量的方法,分别带或不带内部转义的单引号。最后一个示例展示了在方法调用中直接使用字符串字面量。
为了使长而复杂的字符串更易读,允许在字符串中换行。
示例 7 – 多行字符串
batch
|query('SELECT 100 - mean(usage_idle)
AS stat
FROM "telegraf"."autogen"."cpu"
WHERE cpu = \'cpu-total\'
')在上面的示例 7 中,字符串被拆分,以便查询更容易理解。
字符串模板
字符串模板允许将节点属性、标签和字段添加到字符串中。格式与 Go 的 text.template 包提供的格式相同。这在编写警报消息时很有用。要将属性、标签或字段值添加到字符串模板中,需要将其包裹在双大括号“{{}}”中。
示例 8 – 字符串模板中的变量
|alert()
.id('{{ index .Tags "host"}}/mem_used')
.message('{{ .ID }}:{{ index .Fields "stat" }}')在上面的示例 8 中,三个值被添加到两个字符串模板中。在调用设置器 id() 时,标签 "host" 的值被添加到 id 的开头。然后调用设置器 message() 添加 id 和字段 "stat" 的值。
字符串模板目前适用于 Alert 节点,并在下面的“在字符串模板中访问值”部分中进一步讨论。
字符串模板还可以包含流程语句,例如 if...else,以及内部格式化方法的调用。
.message('{{ .ID }} is {{ if eq .Level "OK" }}alive{{ else }}dead{{ end }}: {{ index .Fields "emitted" | printf "%0.3f" }} points/10s.')字符串列表
字符串列表是包含在两个方括号之间的字符串集合。它们可以由字面量、其他变量的标识符或星号通配符“*”声明。它们可以传递给接受多个字符串参数的方法。它们在模板任务中尤其有用。请注意,在函数调用中使用列表时,列表内容会被展开,元素将作为所有参数传递给函数。当提供列表时,表示列表包含函数的所有参数。
示例 9 – 标准任务中的字符串列表
var foo = 'foo'
var bar = 'bar'
var foobar_list = [foo, bar]
var cpu_groups = [ 'host', 'cpu' ]
...
stream
|from()
.measurement('cpu')
.groupBy(cpu_groups)
...示例 9 声明了两个字符串列表。第一个包含其他变量的标识符。第二个包含字符串字面量。列表 cpu_groups 在 from.groupBy() 方法中使用。
示例 10 – 模板任务中的字符串列表
dbrp "telegaf"."not_autogen"
var measurement string
var where_filter = lambda: TRUE
var groups = [*]
var field string
var warn lambda
var crit lambda
var window = 5m
var slack_channel = '#alerts'
stream
|from()
.measurement(measurement)
.where(where_filter)
.groupBy(groups)
|window()
.period(window)
.every(window)
|mean(field)
|alert()
.warn(warn)
.crit(crit)
.slack()
.channel(slack_channel)示例 10,摘自 代码仓库 中的示例,定义了 implicit_template.tick。它使用 groups 列表来保存将传递给 from.groupBy() 方法的变量参数。groups 列表的内容将在使用模板创建新任务时确定。
正则表达式
正则表达式以斜杠开头和结尾:/。正则表达式语法与 Perl、Python 等语言相同。有关语法详情,请参阅 Go 的 正则表达式库。
示例 11 – 正则表达式
var cz_turbines = /^cz\d+/
var adr_senegal = /\.sn$/
var local_ips = /^192\.168\..*/
...
var locals = stream
|from()
.measurement('responses')
.where(lambda: "node" =~ local_ips )
var south_afr = stream
|from()
.measurement('responses')
.where(lambda: "dns_node" =~ /\.za$/ )在示例 11 中,前三行显示了将正则表达式赋值给变量。locals 流使用赋值给变量 local_ips 的正则表达式。south_afr 流使用正则表达式与作为 lambda 表达式一部分的字面量声明的正则表达式进行比较。
Lambda 表达式作为字面量
Lambda 表达式是一个参数,代表一个简短易懂的函数,用于传递给方法调用或存储在变量中。它可以包装布尔表达式、数学表达式、内部函数调用或这三者的组合。Lambda 表达式始终作用于点数据。它们通常很简洁,因此被用作字面量,最终传递给节点方法。可在 Lambda 表达式中使用的内部函数将在“类型转换”和“Lambda 表达式”部分中讨论。Lambda 表达式将在“Lambda 表达式”主题中详细介绍。
Lambda 表达式以关键字 lambda 开头,后跟冒号“:”,即 lambda:。
示例 12 – Lambda 表达式
var my_lambda = lambda: 1 > 0
var lazy_lambda = lambda: "usage_idle" < 95
...
var data = stream
|from()
...
var alert = data
|eval(lambda: sigma("stat"))
.as('sigma')
.keep()
|alert()
.id('{{ index .Tags "host"}}/cpu_used')
.message('{{ .ID }}:{{ index .Fields "stat" }}')
.info(lambda: "stat" > 70 OR "sigma" > 2.5)
.warn(lambda: "stat" > 80 OR "sigma" > 3.0)
.crit(lambda: "stat" > 90 OR "sigma" > 3.5)上面的示例 12 表明 lambda 表达式可以直接赋值给变量。在 eval 节点中,使用了一个调用 sigma 函数的 lambda 语句。alert 节点使用 lambda 表达式来定义给定事件的日志级别。
节点
与基本类型一样,节点类型也可以声明并赋值给变量。
示例 13 – 节点表达式
var data = stream
|from()
.database('telegraf')
.retentionPolicy('autogen')
.measurement('cpu')
.groupBy('host')
.where(lambda: "cpu" == 'cpu-total')
|eval(lambda: 100.0 - "usage_idle")
.as('used')
|window()
.period(span)
.every(frequency)
|mean('used')
.as('stat')
...
var alert = data
|eval(lambda: sigma("stat"))
.as('sigma')
.keep()
|alert()
.id('{{ index .Tags "host"}}/cpu_used')
...在上面的示例 13 中,第一部分创建了五个节点。顶层节点 stream 被赋值给变量 data。然后 stream 节点作为管道的根,节点 from、eval、window 和 mean 按顺序链接到它。第二部分通过赋值给变量 alert 来扩展管道,以便第二个 eval 节点可以应用于数据。
使用标签、字段和变量
在任何脚本中,仅仅声明变量是不够的。它们存储的值也必须被访问。在 TICKscript 中,还需要处理来自 InfluxDB 数据系列的标签和字段中存储的值。这一点在到目前为止的示例中最为明显。此外,lambda 表达式生成的值可以作为新字段添加到管道中的数据集中,然后作为这些表达式的命名结果进行访问。下一节将探讨如何处理变量以及可以从数据中提取的标签和字段值,以及命名结果。
访问值
访问数据标签和字段、使用字符串字面量以及访问 TICKscript 变量都涉及不同的语法。此外,还可以访问与某些节点一起使用的 lambda 表达式的结果。
- 变量 – 要访问一个 TICKscript 变量,只需使用其标识符。
示例 14 – 变量访问
var db = 'website'
...
var data = stream
|from()
.database(db)
...在示例 14 中,变量 db 被赋值为字面量值 'website'。然后,在 from() 的链接方法下,它被用在设置器 .database() 中。
- 字符串字面量 – 要声明一个 字符串字面量,请使用上面“字符串”部分中讨论的单引号。
- 标签和字段值 – 要在 Lambda 表达式中访问 标签值 或 字段值,请使用双引号。在方法调用中引用它们时,请使用单引号。在方法调用中,它们本质上是用于节点匹配数据系列中的标签或字段值的字符串字面量。
示例 15 – 字段访问
// Data frame
var data = stream
|from()
.database('telegraf')
.retentionPolicy('autogen')
.measurement('cpu')
.groupBy('host')
.where(lambda: "cpu" == 'cpu-total')
|eval(lambda: 100.0 - "usage_idle")
.as('used')
...在示例 15 中,访问了两个数据帧中的值。在 where() 方法调用中,lambda 表达式使用标签 "cpu" 将数据帧过滤到仅包含“cpu”标签等于 'cpu-total' 字面量值的那些数据点。链接方法 eval() 也接受一个 lambda 表达式,该表达式访问字段 "usage-idle" 来计算 cpu 处理能力“used”。请注意,groupBy() 方法使用字符串字面量 'host' 来匹配数据系列中的标签名。然后它将按此标签对数据进行分组。
- 命名 lambda 表达式结果 – lambda 表达式的结果通过
as()方法命名并作为字段添加到数据集中。将as()方法的作用理解为类似于 InfluxQL 中的“AS”关键字。参见上面示例 15 中的eval()方法。lambda 表达式的结果可以在其他 Lambda 表达式中使用双引号访问,在方法调用中使用单引号访问,这与数据标签和字段一样。
示例 16 – 命名 lambda 表达式访问
...
|window()
.period(period)
.every(every)
|mean('used')
.as('stat')
// Thresholds
var alert = data
|eval(lambda: sigma("stat"))
.as('sigma')
.keep()
|alert()
.id('{{ index .Tags "host"}}/cpu_used')
.message('{{ .ID }}:{{ index .Fields "stat" }}')
.info(lambda: "stat" > info OR "sigma" > infoSig)
.warn(lambda: "stat" > warn OR "sigma" > warnSig)
.crit(lambda: "stat" > crit OR "sigma" > critSig)示例 16 继续了示例 15 的管道。在示例 15 中,在 eval() 方法下命名为 'used' 的 lambda 表达式的结果在示例 16 中被访问,作为方法 'mean()' 的参数,该方法然后将其结果命名为 'stat'。然后开始一个新的语句。该语句包含对方法 'eval()' 的新调用,该调用具有一个 lambda 表达式,该表达式访问 "stat" 并将其结果命名为 'sigma'。命名结果 "stat" 也在 message() 方法和 alert() 链接方法下的阈值方法(info()、warn()、crit())中被访问。命名结果 "sigma" 也用于这些方法的 lambda 表达式中。
注意 – InfluxQL 节点和标签或字段访问 – InfluxQL 节点,例如示例 16 中的 mean(),是包装 InfluxQL 函数的特殊节点。请参阅下面的“节点类型分类”部分。当使用此节点类型访问字段值、标签值或命名结果时,使用单引号。
示例 17 – 使用 InfluxQL 节点进行字段访问
// Dataframe
var data = stream
|from()
.database('telegraf')
.retentionPolicy('autogen')
.measurement('cpu')
.groupBy('host')
.where(lambda: "cpu" == 'cpu-total')
|eval(lambda: 100.0 - "usage_idle")
.as('used')
|window()
.period(period)
.every(every)
|mean('used')
.as('stat')在上面的示例 17 中,eval 结果被命名为 used。链接方法 mean 是节点类型 InfluxQL 的别名。它包装了 InfluxQL mean 函数。在调用 mean 时,使用单引号访问命名结果 'used'。
在字符串模板中访问值
如“字符串模板”部分所述,可以将节点特定属性以及标签和字段中的值添加到输出字符串中。这可以在示例 16 中 alert 节点下看到。访问器表达式包含在两个大括号中。要访问属性,请在标识符前使用句点 .。要访问标签或字段中的值,请使用 index 标记,后跟一个空格和一个句点,然后是数据系列要访问的部分(例如 .Tags 或 .Fields);实际名称随后以双引号指定。
示例 18 – 在字符串模板中访问值
|alert()
.id('{{ index .Tags "host"}}/mem_used')
.message('{{ .ID }}:{{ index .Fields "stat" }}')在上面的示例 18 中,属性方法 .id() 使用数据流中键为 "host" 的标签值来设置 id 值的一部分。然后此值在属性方法 message() 中用作 .ID。此属性方法还访问命名结果 "stat" 的值。
有关更具体的信息,请参阅 Alert 节点。
类型转换
在 lambda 表达式中,可以使用无状态转换函数在类型之间转换值。
bool()- 将字符串、int64 或 float64 转换为布尔型。int()- 将字符串、float64、布尔型或 duration 类型转换为 int64。float()- 将字符串、int64 或布尔型转换为 float64。string()- 将 int64、float64、布尔型或 duration 值转换为字符串。duration()- 将 int64、float64 或字符串转换为 duration 类型。
示例 19 – 类型转换
|eval(lambda: float("total_error_responses")/float("total_responses") * 100.0)在上面的示例 19 中,使用 float 转换函数来确保计算出的百分比使用浮点精度,即使数据系列中的字段值可能以整数形式存储。
数值精度
在消息中或写入 InfluxDB 时,编写浮点值时,可能需要指定小数精度,以使值更具可读性或更易于比较。例如,在 alert 节点的 message() 方法中,可以将值“通过管道”传递给 printf 语句。
|alert()
.id('{{ index .Tags "host"}}/mem_used')
.message('{{ .ID }}:{{ index .Fields "stat" | printf "%0.2f" }}')在 lambda 表达式中使用浮点值时,也可以使用 floor 函数和 10 的幂来四舍五入到较低的精度值。请注意,在字符串模板中使用 printf 速度要快得多。同时也要注意,由于值以 64 位形式写入,这不会影响存储。如果将其与 InfluxDBOut 节点一起使用,例如在缩小数据时,可能会导致不必要的信息丢失。
示例 20 – 渲染较低精度的浮点数
stream
// Select just the cpu measurement from our example database.
|from()
.measurement('cpu')
|eval(lambda: floor("usage_idle" * 1000.0)/1000.0)
.as('thousandths')
.keep('usage_user','usage_idle','thousandths')
|alert()
.crit(lambda: "thousandths" < 95.000)
.message('{{ index .Fields "thousandths" }}')
// Whenever we get an alert write it to a file.
.log('/tmp/alerts.log')示例 20 完成了与使用 printf 类似的功能。usage_idle 值向下舍入到千分之几的百分比,然后用于在 alert 节点阈值方法中进行比较。然后将其写入 alert 消息。
时间精度
由于 Kapacitor 和 TICKscript 可用于将值写入 InfluxDB 数据库,在某些情况下,可能希望指定使用的时间精度。一个例子是在使用计算出的平均值缩小数据时。写入的精度可以设置为比默认值更粗糙的值,甚至超过存储桶大小,即通过调用 window.every() 等方法设置的值。不建议使用大于存储桶大小的精度。指定时间精度可以带来存储和性能的改进。最常见的例子发生在处理 InfluxDBOut 节点时,该节点的精度属性可以设置。请注意,InfluxDBOut 节点默认为最高精度,即纳秒。重要的是不要混淆通常用于字段值的 *数学* 精度与为时间戳指定的 *时间* 精度。
示例 21 – 使用 InfluxDBOut 设置时间精度
stream
|from()
.database('telegraf')
.measurement('cpu')
.groupBy(*)
|window()
.period(5m)
.every(5m)
.align()
|mean('usage_idle')
.as('usage_idle')
|influxDBOut()
.database('telegraf')
.retentionPolicy('autogen')
.measurement('mean_cpu_idle')
.precision('s')
...在示例 21 中(摘自指南主题 连续查询),写入数据库“telegraf”中度量 mean_cpu_idle 的序列的时间精度被设置为秒单位。
精度的有效值与 InfluxDB 中的值相同。
表 6 – 精度单位
| 字符串 | 单位 |
|---|---|
| “ns” | 纳秒 |
| “ms” | 毫秒 |
| “s” | 秒 |
| “m” | 分钟 |
| “h” | 小时 |
语句
TICKscript 中有两种类型的语句:声明和表达式。声明可以声明变量或 TICKscript 将要使用的数据库。表达式表示方法调用的管道(也称为链),它创建处理节点并设置其属性。
声明
TICKscript 处理两种类型的声明:数据库声明和变量声明。
数据库声明 以关键字 dbrp 开头,后跟两个用句点分隔的字符串。第一个字符串声明了脚本将使用的默认数据库。第二个字符串声明其保留策略。请注意,数据库和保留策略也可以使用标志 -dbrp 在命令行使用 kapacitor define 定义任务时声明,因此此语句是可选的。使用时,数据库声明语句应为 TICKscript 的第一个声明。
示例 22 – 数据库声明
dbrp "telegraf"."autogen"
...示例 22 声明该 TICKscript 将用于数据库 telegraf 及其保留策略 autogen。
变量声明 以关键字 var 开头,后跟要声明的变量的标识符。赋值运算符后面是具有字面量右侧值的赋值,这将设置新变量的类型和值。
示例 23 – 典型声明
...
var db = 'website'
var rp = 'autogen'
var measurement = 'responses'
var whereFilter = lambda: ("lb" == '17.99.99.71')
var name = 'test rule'
var idVar = name + ':{{.Group}}'
...示例 23 显示了六个声明语句。其中五个创建了存储字符串的变量,一个创建了存储 lambda 表达式的变量。
声明也可以用于将表达式赋值给变量。
示例 24 – 将表达式声明为变量
var data = stream
|from()
.database(db)
.retentionPolicy(rp)在示例 24 中,data 变量存储了以 stream 节点开头的表达式中声明的流管道。
表达式
表达式以节点标识符或包含另一个表达式的变量标识符开头。然后,它会连接额外的节点创建方法(链式方法)、属性设置器(属性方法)或用户定义的函数(UDF)。管道运算符“|”指示链式方法调用的开始,将新节点返回到链中。点运算符“.”添加一个属性设置器。at运算符“@”引入一个用户定义的函数。
表达式可以写在一行上,但这可能导致可读性问题。命令 kapacitor show <taskname> 将在控制台输出中显示 TICKscript。此命令会美化打印或使用换行和缩进,而不管定义 TICKscript 的方式如何。建议的 TICKscript 表达式书写方式是添加换行和缩进新的方法调用。通常,当在表达式中引入新的链式方法时,会创建一个新行,并将链中的新链接缩进三个或更多空格。同样,当调用新的属性设置器时,它会另起一行并额外缩进一定数量的空格。为了提高可读性,用户定义的函数应与链式方法缩进相同。
表达式以管道中最后一个节点的最后一个设置器结束。
示例 25 – 单行表达式
...
// Dataframe
var data = batch|query('''SELECT mean(used_percent) AS stat FROM "telegraf"."autogen"."mem" ''').period(period).every(every).groupBy('host')
// Thresholds
var alert = data|eval(lambda: sigma("stat")).as('sigma').keep()|alert().id('{{ index .Tags "host"}}/mem_used').message('{{ .ID }}:{{ index .Fields "stat" }}')
.info(lambda: "stat" > info OR "sigma" > infoSig).warn(lambda: "stat" > warn OR "sigma" > warnSig).crit(lambda: "stat" > crit OR "sigma" > critSig)
...示例 25 显示了一个在同一行上声明了多个节点和设置器的表达式。虽然这是可能的,但它不是推荐的样式。另外请注意,Kapacitor 发行版附带的命令行实用程序 tickfmt 可用于重新格式化 TICKscript 以遵循推荐的样式。
示例 26 – 推荐的表达式语法
...
// Dataframe
var data = batch
|query('''SELECT mean(used_percent) AS stat FROM "telegraf"."autogen"."mem" ''')
.period(period)
.every(every)
.groupBy('host')
// Thresholds
var alert = data
|eval(lambda: sigma("stat"))
.as('sigma')
.keep()
|alert()
.id('{{ index .Tags "host"}}/mem_used')
.message('{{ .ID }}:{{ index .Fields "stat" }}')
.info(lambda: "stat" > info OR "sigma" > infoSig)
.warn(lambda: "stat" > warn OR "sigma" > warnSig)
.crit(lambda: "stat" > crit OR "sigma" > critSig)
// Alert
alert
.log('/tmp/mem_alert_log.txt')
...示例 26,摘自代码库中的示例 mem_alert_batch.tick,展示了编写表达式的推荐样式。此示例包含三个表达式语句。第一个以数据帧的批处理节点声明开始。这被分配给变量 data。第二个表达式获取 data 变量并定义警告消息的阈值。这被分配给 alert 变量。第三个表达式设置 alert 节点的 log 属性。
节点创建
除了两个例外(stream 和 batch)之外,节点始终出现在管道表达式(链)中,其中它们是通过链式方法创建的。链式方法通常使用节点类型名称标识。一个显著的例外是 InfluxQL 节点,它使用别名。请参见下面的 节点类型分类 部分。
对于每种节点类型,创建该类型实例的方法都使用相同的签名。因此,如果一个 query 节点创建了一个 eval 节点并将其添加到链中,并且如果一个 from 节点也可以创建一个 eval 节点并将其添加到链中,那么创建新 eval 节点的链式方法将接受相同的参数(例如,一个或多个 lambda 表达式),而不管是由哪个节点创建的。
示例 27 – 在 stream 中实例化 eval 节点
...
var data = stream
|from()
.database('telegraf')
.retentionPolicy('autogen')
.measurement('cpu')
.groupBy('host')
.where(lambda: "cpu" == 'cpu-total')
|eval(lambda: 100.0 - "usage_idle")
.as('used')
.keep()
...示例 27 创建了三个节点:stream、from 和 eval。
示例 28 – 在 batch 中实例化 eval 节点
...
var data = batch
|query('''SELECT 100 - mean(usage_idle) AS stat FROM "telegraf"."autogen"."cpu" WHERE cpu = 'cpu-total' ''')
.period(period)
.every(every)
.groupBy('host')
|eval(lambda: sigma("stat"))
.as('sigma')
.keep()
...示例 28 也创建了三个节点:batch、query 和 eval。
示例 27 和 28 都创建了一个 eval 节点。尽管 eval 在示例 27 中链在 from 节点下方,在示例 28 中链在 query 节点下方,但链式方法签名保持不变。
节点简要分类请参见下面的 节点类型分类 部分。节点类型的目录可在 TICKscript 节点 主题下找到。
管道
重申一下,管道是由一个或多个表达式定义的逻辑排序的节点链。“逻辑排序”意味着节点不能以任意顺序链接,而是根据它们在处理数据中的作用在管道中出现。管道可以以两种模式定义节点之一开始:batch 或 stream。batch 管道的数据帧在 query 定义节点中定义。stream 管道的数据流在 from 定义节点中定义。定义节点之后,可以跟随任何其他类型的节点。
标准节点类型使用由管道字符“|”指示的链式方法添加到管道中。用户定义的函数可以使用 at 字符“@”添加。
管道中的每个节点都有内部属性,可以使用由句点“.”分隔的属性方法进行设置。这些方法在节点处理数据之前被调用。
管道中的每个节点都可以更改传递给后续节点的数据:过滤它、重构它、将其减少为新的度量,等等。在某些节点中,设置属性可以显著改变下游同级节点接收的数据。例如,使用 eval 节点时,使用 as 属性设置 lambda 函数的名称实际上会阻止字段和标记名称传递到下游。因此,为了在稍后节点需要它们时将它们保留在管道中,设置 keep 属性可能很重要。
在使用节点类型之前,熟悉每个节点类型的 参考文档 非常重要。
示例 29 – 一个典型的管道
// Dataframe
var data = batch
|query('''SELECT 100 - mean(usage_idle) AS stat FROM "telegraf"."autogen"."cpu" WHERE cpu = 'cpu-total' ''')
.period(period)
.every(every)
.groupBy('host')
// Thresholds
var alert = data
|eval(lambda: sigma("stat"))
.as('sigma')
.keep()
|alert()
.id('{{ index .Tags "host"}}/cpu_used')
.message('{{ .ID }}:{{ index .Fields "stat" }}')
.info(lambda: "stat" > info OR "sigma" > infoSig)
.warn(lambda: "stat" > warn OR "sigma" > warnSig)
.crit(lambda: "stat" > crit OR "sigma" > critSig)
// Alert
alert
.log('/tmp/cpu_alert_log.txt')示例 29 显示了一个 batch→query 管道,该管道使用两个变量分解为三个表达式。第一个表达式声明数据帧,第二个表达式声明警报阈值,最后一个表达式设置 alert 节点的 log 属性。整个管道以 batch 节点的声明开始,以属性方法 log() 的调用结束。
节点类型分类
为了帮助理解不同节点在管道中扮演的角色,定义了一个简短的分类。有关每个节点类型的完整文档,请参阅 TICKscript 节点 主题。
特殊节点
这些节点之所以特殊,是因为它们可以使用除其类型名称以外的标识符来创建和返回。可以使用代表其功能某方面的别名。这可能适用于所有实例,如 InfluxQL 节点,或仅适用于一个实例,如 Alert 节点。
alert- 可以作为deadman开关返回influxQL- 直接调用 InfluxQL 中的函数,因此在调用使用 InfluxQL 方法名称的 TICKScript 链式方法时可以返回。- 示例 1:
from()|mean()- 对 from 节点中定义的数据流调用 mean 函数并返回一个 InfluxQL 节点。 - 示例 2:
query()|mode()- 对 Query 节点中定义的数据帧调用 mode 函数并返回一个 InfluxQL 节点。
- 示例 1:
数据源定义节点
TICKscript 管道中的第一个节点是 batch 或 stream。它们定义了用于处理数据的数据源。
数据定义节点
模式定义节点通常后面跟着用于定义要由其他节点处理的数据帧或流的节点。
数据操作节点
可以使用操作节点来更改或生成数据集中值。
default- 具有空链式方法。其field和tag属性可用于设置数据系列中字段和标记的默认值。sample- 链式方法接受一个 int64 或持续时间字符串。它根据计数或时间段提取数据的样本。shift- 链式方法接受一个持续时间字符串。它会移动数据点时间戳。持续时间字符串前面可以加上负号,以将时间戳向后移动。where- 链式方法接受一个 lambda 节点。它与stream管道一起工作,类似于 InfluxQL 中的WHERE语句。window- 具有空链式方法。它通过属性方法进行配置。它在stream管道中通常跟在from节点之后,用于缓存移动时间范围内的数据。
处理节点
一旦定义了数据集,就可以将其传递给其他节点,这些节点将处理它、转换它或基于内部的变化触发其他进程。
用于更改数据结构或混合管道的节点
combine- 链式方法接受一个或多个 lambda 表达式的列表。它可以将单个节点的数据与自身组合。eval- 链式方法接受一个或多个 lambda 表达式的列表。它对接收到的每个数据点进行表达式求值,并使用其as属性,使结果可供管道后续节点使用。请注意,当使用多个 lambda 表达式时,as方法可以包含一个字符串列表来命名每个 lambda 的结果。groupBy- 链式方法接受一个或多个表示系列标记的字符串列表。它按标记对传入数据进行分组。join- 链式方法接受一个或多个引用管道表达式的变量的列表。它根据匹配的时间戳从任意数量的管道中连接数据。union- 链式方法接受一个或多个引用管道表达式的变量的列表。它创建任意数量管道的联合。
用于转换或处理数据集中数据点的节点
delete- 空链式方法。它依赖于属性(field、tag)来从数据点中删除字段和标记。derivative- 链式方法接受一个字符串,表示将计算其导数的字段。flatten- 空链式方法。它依赖于属性以在特定维度上展平一组点。influxQL- 特殊节点(见上文)。它提供对 InfluxQL 函数的访问。不能直接创建。stateCount- 链式方法接受一个 lambda 表达式。它计算处于给定状态的连续点的数量。stateDuration- 链式方法接受一个 lambda 表达式。它计算给定状态持续的时间。stats- 链式方法接受一个持续时间表达式。它以给定的时间间隔发出关于另一个节点的内部统计信息。
用于触发事件、进程的节点
alert- 空链式方法。它依赖于许多属性来配置警报的发出。deadman- 实际上是一个辅助函数,它是当数据流低于指定阈值时触发的alert的别名。httpOut- 链式方法接受一个字符串。它缓存每个接收到的组的最新数据,使其可以通过 Kapicator HTTP 服务器使用字符串参数作为最终定位符上下文。httpPost- 链式方法接受一个字符串数组。它也可以为空。它将数据发布到字符串数组中指定的 HTTP 端点。influxDBOut- 空链式方法 – 通过属性设置器配置。它在接收数据时将其写入 InfluxDB。k8sAutoscale- 空链式方法。它依赖于许多配置属性。它触发 Kubernetes™ 资源的自动伸缩。kapacitorLoopback- 空链式方法 – 通过属性设置器配置。它将数据写回 Kapacitor 流。log- 空链式方法。它依赖于level和prefix属性进行配置。它记录所有通过它的数据。
用户定义函数 (UDF)
用户定义函数是实现用户程序或脚本定义的功能的节点,这些程序或脚本作为独立进程运行,并通过套接字或标准系统数据流与 Kapacitor 通信。
UDF- 由用户定义的签名、属性和功能。要了解如何编写用户定义函数,请参阅 用户定义函数网络研讨会,可在 Influx 在线大学 上找到。
内部使用的节点 - 请勿使用
noOp- 一个执行无操作的辅助节点。请勿使用!
InfluxQL 在 TICKscript 中
InfluxQL 主要出现在 TICKscript 的 query 节点中,其链式方法接受 InfluxQL 查询字符串。这几乎总是 SELECT 语句。
InfluxQL 的语法与 SQL 非常相似。在为 TICKscript query 节点编写查询字符串时,通常只需要三个子句:SELECT、FROM 和 WHERE。通用模式如下:
SELECT {<FIELD_KEY> | <TAG_KEY> | <FUNCTION>([<FIELD_KEY>|<TAG_KEY])} FROM <DATABASE>.<RETENTION_POLICY>.<MEASUREMENT> WHERE {<CONDITIONAL_EXPRESSION>}- 基础
SELECT子句可以接受一个或多个字段或标记键,或函数。这些可以与数学运算和文字值结合。它们的值或结果将被添加到数据帧中,并可以通过AS子句进行别名。星号*通配符也可用于从度量中检索所有标记和字段。- 使用
AS子句时,可以在 TICKscript 稍后使用双引号通过命名结果来访问别名标识符。
- 使用
FROM子句需要从中选择值的数据库、保留策略和度量名称。这些令牌中的每个令牌都用句点分隔。数据库和保留策略的值需要用双引号引起来。WHERE子句需要一个条件表达式。这可能包括AND和OR布尔运算符以及数学运算。
示例 30 – 一个简单的 InfluxQL 查询语句
batch
|query('SELECT cpu, usage_idle FROM "telegraf"."autogen".cpu WHERE time > now() - 10s')
.period(10s)
.every(10s)
|httpOut('dump')示例 30 显示了一个简单的 SELECT 语句,该语句从 cpu 度量中提取 cpu 标记和 usage_idle 字段,记录时间为过去十秒。
示例 31 – 一个带变量的简单 InfluxQL 查询语句
var my_field = 'usage_idle'
var my_tag = 'cpu'
batch
|query('SELECT ' + my_tag + ', ' + my_field + ' FROM "telegraf"."autogen".cpu WHERE time > now() - 10s')
.period(10s)
.every(10s)
|httpOut('dump')示例 31 重申了示例 30 中的相同查询,但展示了如何将变量添加到查询字符串中。
示例 32 – 一个带函数调用的 InfluxQL 查询语句
...
var data = batch
|query('''SELECT 100 - mean(usage_idle) AS stat FROM "telegraf"."autogen"."cpu" WHERE cpu = 'cpu-total' ''')
.period(period)
.every(every)
.groupBy('host')
...示例 32 显示了一个 SELECT 语句,其中包含 SELECT 子句中的函数和数学运算,以及 AS 别名子句。
请注意,select 语句直接传递给 InfluxDB API。在 InfluxQL 查询字符串中,字段和标记名称不需要使用双引号访问,这与 TICKscript 中的其他地方不同。但是,数据库名称和保留策略确实用双引号括起来。字符串文字,如 'cpu-total',在查询字符串内用单引号表示。
有关查询语言的完整介绍,请参阅 InfluxQL 文档。
Lambda 表达式
Lambda 表达式出现在许多链式方法和属性方法中。两个最常见的用法是在创建 eval 节点和在 alert 节点上定义阈值属性。它们以关键字“lambda”后跟冒号 lambda: 声明。它们可以包含数学和布尔运算以及对大型内部函数库的调用。对于许多节点,可以通过在节点上设置 as 属性来捕获其结果。
内部函数可以是无状态的,例如常见的数学和字符串操作函数,也可以是有状态的,每次新调用都会更新内部值。截至 1.3 版,提供了三个有状态函数。
sigma- 计算给定值与运行平均值的标准差数量。count- 计算已处理值的数量。spread- 计算所有值的运行范围。
Lambda 表达式及其用法的完整范围在 Lambda 表达式 主题中介绍。
在 Lambda 表达式中,可以使用 TICKscript 变量的普通标识符访问它们。可以通过用双引号括起来来访问数据系列的标记和字段值。也可以直接使用文字。
示例 33 – Lambda 表达式
...
// Parameters
var info = 70
var warn = 85
var crit = 92
var infoSig = 2.5
var warnSig = 3
var critSig = 3.5
var period = 10s
var every = 10s
// Dataframe
var data = batch
|query('''SELECT mean(used_percent) AS stat FROM "telegraf"."autogen"."mem" ''')
.period(period)
.every(every)
.groupBy('host')
// Thresholds
var alert = data
|eval(lambda: sigma("stat"))
.as('sigma')
.keep()
|alert()
.id('{{ index .Tags "host"}}/mem_used')
.message('{{ .ID }}:{{ index .Fields "stat" }}')
.info(lambda: "stat" > info OR "sigma" > infoSig)
.warn(lambda: "stat" > warn OR "sigma" > warnSig)
.crit(lambda: "stat" > crit OR "sigma" > critSig)
// Alert
alert
.log('/tmp/mem_alert_log.txt')示例 33 包含四个 lambda 表达式。第一个表达式传递给 eval 节点。它调用内部有状态函数 sigma,并将命名结果 stat 传递给它,该结果使用 query 节点的查询字符串中的 AS 子句设置。通过 eval 节点的 .as() 设置器,其结果被命名为 sigma。另外三个 lambda 表达式出现在 alert 节点的阈值确定属性方法中。这些 lambda 表达式还访问命名结果 stat 和 sigma 以及脚本开头声明的变量。它们各自定义了一系列布尔运算,用于设置警报消息的级别。
语法子空间中的变量使用总结
下一节总结了如何在 TICKscript 中访问变量和数据系列标记及字段,以及不同的语法子空间。
TICKscript 变量
声明示例
var my_var = 'foo'
var my_field = `usage_idle`
var my_num = 2.71访问…
- 在 **TICKscript** 中,只需使用标识符。
var my_other_num = my_num + 3.14
...
|default()
.tag('bar', my_var)
...- 在 **查询字符串** 中,只需使用带字符串连接的标识符。
...
|query('SELECT ' + my_field + ' FROM "telegraf"."autogen".cpu WHERE host = \'' + my_var + '\'' )
...- 在 **Lambda 表达式** 中,只需使用标识符。
...
.info(lambda: "stat" > my_num )
...- 在 **InfluxQL 节点** 中,使用标识符。请注意,在大多数情况下,字符串将用作字段或标记名称。
...
|mean(my_var)
...标记、字段或命名结果
示例
...
|query('SELECT mean(usage_idle) AS mean ...')
...
|eval(lambda: sigma("stat"))
.as('sigma')
...访问…
- 在 **TICKscript** 方法调用中使用单引号。
...
|derivative('mean')
...- 在 **查询字符串** 中,直接在字符串中使用标识符。
...
|query('SELECT cpu, usage_idle FROM "telegraf"."autogen".cpu')
...- 在 **Lambda 表达式** 中,使用双引号。
...
|eval(lambda: 100.0 - "usage_idle")
...
|alert
.info(lambda: "sigma" > 2 )
...- 在 **InfluxQL 节点** 中,使用单引号。
...
|mean('used')
...陷阱
文字与字段值
请记住,文字字符串值使用单引号声明。双引号仅在 Lambda 表达式中用于访问标记和字段的值。在大多数情况下,使用双引号代替单引号将被捕获为错误:unsupported literal type。另一方面,在需要双引号时使用单引号(即访问字段值)不会被捕获,如果这种情况发生在 Lambda 表达式中,则可能会使用文字值而不是标记或字段所需的所需值。
从 Kapacitor 1.3 开始,可以使用双引号声明变量,这是无效的,并且解析器不会将其标记为错误。例如,var my_var = "foo" 将通过,只要它不被使用。但是,当此变量在 Lambda 表达式或其他方法调用中使用时,它将触发编译错误:unsupported literal type *ast.ReferenceNode。
循环重写
使用 InfluxDBOut 节点时,请小心不要创建指向同一数据库和同一度量(正在从中读取数据)的循环重写。
示例 34 – 循环重写
stream
|from()
.measurement('system')
|eval(lambda: "n_cpus" + 1)
.as('n_cpus')
|influxDBOut()
.database('telegraf')
.measurement('system')注意:示例 34 说明了如何创建无限循环。请勿使用它!
示例 34 中的脚本可用于定义数据库 telegraf、保留策略 autogen 上的任务。例如
kapacitor define circular_task -type stream -tick circular_rewrite.tick -dbrp telegraf.autogen在这种情况下,上述脚本将无限循环地添加一个具有新 n_cpus 字段值的数据点,直到任务停止。
警报和 ID
当使用 deadman 方法以及一个或多个 alert 节点,或者在管道中使用多个 alert 节点时,请务必使用 id() 属性方法设置 ID 属性。ID 的值在每个节点上都必须是唯一的。否则,Kapacitor 将假定它们是同一组警报,因此某些警报可能不会按预期显示。
下一步
请参阅 Github 上的代码库中的 示例。另请参阅 指南 部分中详细的用例解决方案。
此页面是否有帮助?
感谢您的反馈!
支持和反馈
感谢您成为我们社区的一员!我们欢迎并鼓励您对 Kapacitor 和本文档提供反馈和错误报告。要获取支持,请使用以下资源: