diff --git a/docs/UserGuide/Advanced-Features/Alerting.md b/docs/UserGuide/Advanced-Features/Alerting.md new file mode 100644 index 0000000000000..9b2b63e5988ff --- /dev/null +++ b/docs/UserGuide/Advanced-Features/Alerting.md @@ -0,0 +1,378 @@ + + +# Alerting + +## Overview +The alerting of IoTDB is expected to support two modes: + +* Writing triggered: the user writes data to the original time series, and every time a piece of data is inserted, the judgment logic of `trigger` will be triggered. +If the alerting requirements are met, an alert is sent to the data sink, +The data sink then forwards the alert to the external terminal. + * This mode is suitable for scenarios that need to monitor every piece of data in real time. + * Since the operation in the trigger will affect the data writing performance, it is suitable for scenarios that are not sensitive to the original data writing performance. + +* Continuous query: the user writes data to the original time series, +`ContinousQuery` periodically queries the original time series, and writes the query results into the new time series, +Each write triggers the judgment logic of `trigger`, +If the alerting requirements are met, an alert is sent to the data sink, +The data sink then forwards the alert to the external terminal. + * This mode is suitable for scenarios where data needs to be regularly queried within a certain period of time. + * It is Suitable for scenarios where the original data needs to be down-sampled and persisted. + * Since the timing query hardly affects the writing of the original time series, it is suitable for scenarios that are sensitive to the performance of the original data writing performance. + +With the introduction of the `trigger` module and the `sink` module into IoTDB, +at present, users can use these two modules with `AlertManager` to realize the writing triggered alerting mode. + + + +## Deploying AlertManager + +### Installation +#### Precompiled binaries +The pre-compiled binary file can be downloaded [here](https://prometheus.io/download/). + +Running command: +```` +./alertmanager --config.file= +```` + +#### Docker image +Available at [Quay.io](https://hub.docker.com/r/prom/alertmanager/) +or [Docker Hub](https://quay.io/repository/prometheus/alertmanager). + +Running command: +```` +docker run --name alertmanager -d -p 127.0.0.1:9093:9093 quay.io/prometheus/alertmanager +```` + +### Configuration + +The following is an example, which can cover most of the configuration rules. For detailed configuration rules, see +[here](https://prometheus.io/docs/alerting/latest/configuration/). + +Example: +``` yaml +# alertmanager.yml + +global: + # The smarthost and SMTP sender used for mail notifications. + smtp_smarthost: 'localhost:25' + smtp_from: 'alertmanager@example.org' + +# The root route on which each incoming alert enters. +route: + # The root route must not have any matchers as it is the entry point for + # all alerts. It needs to have a receiver configured so alerts that do not + # match any of the sub-routes are sent to someone. + receiver: 'team-X-mails' + + # The labels by which incoming alerts are grouped together. For example, + # multiple alerts coming in for cluster=A and alertname=LatencyHigh would + # be batched into a single group. + # + # To aggregate by all possible labels use '...' as the sole label name. + # This effectively disables aggregation entirely, passing through all + # alerts as-is. This is unlikely to be what you want, unless you have + # a very low alert volume or your upstream notification system performs + # its own grouping. Example: group_by: [...] + group_by: ['alertname', 'cluster'] + + # When a new group of alerts is created by an incoming alert, wait at + # least 'group_wait' to send the initial notification. + # This way ensures that you get multiple alerts for the same group that start + # firing shortly after another are batched together on the first + # notification. + group_wait: 30s + + # When the first notification was sent, wait 'group_interval' to send a batch + # of new alerts that started firing for that group. + group_interval: 5m + + # If an alert has successfully been sent, wait 'repeat_interval' to + # resend them. + repeat_interval: 3h + + # All the above attributes are inherited by all child routes and can + # overwritten on each. + + # The child route trees. + routes: + # This routes performs a regular expression match on alert labels to + # catch alerts that are related to a list of services. + - match_re: + service: ^(foo1|foo2|baz)$ + receiver: team-X-mails + + # The service has a sub-route for critical alerts, any alerts + # that do not match, i.e. severity != critical, fall-back to the + # parent node and are sent to 'team-X-mails' + routes: + - match: + severity: critical + receiver: team-X-pager + + - match: + service: files + receiver: team-Y-mails + + routes: + - match: + severity: critical + receiver: team-Y-pager + + # This route handles all alerts coming from a database service. If there's + # no team to handle it, it defaults to the DB team. + - match: + service: database + + receiver: team-DB-pager + # Also group alerts by affected database. + group_by: [alertname, cluster, database] + + routes: + - match: + owner: team-X + receiver: team-X-pager + + - match: + owner: team-Y + receiver: team-Y-pager + + +# Inhibition rules allow to mute a set of alerts given that another alert is +# firing. +# We use this to mute any warning-level notifications if the same alert is +# already critical. +inhibit_rules: +- source_match: + severity: 'critical' + target_match: + severity: 'warning' + # Apply inhibition if the alertname is the same. + # CAUTION: + # If all label names listed in `equal` are missing + # from both the source and target alerts, + # the inhibition rule will apply! + equal: ['alertname'] + + +receivers: +- name: 'team-X-mails' + email_configs: + - to: 'team-X+alerts@example.org, team-Y+alerts@example.org' + +- name: 'team-X-pager' + email_configs: + - to: 'team-X+alerts-critical@example.org' + pagerduty_configs: + - routing_key: + +- name: 'team-Y-mails' + email_configs: + - to: 'team-Y+alerts@example.org' + +- name: 'team-Y-pager' + pagerduty_configs: + - routing_key: + +- name: 'team-DB-pager' + pagerduty_configs: + - routing_key: +``` + +In the following example, we used the following configuration: +````yaml +# alertmanager.yml + +global: + smtp_smarthost: '' + smtp_from: '' + smtp_auth_username: '' + smtp_auth_password: '' + smtp_require_tls: false + +route: + group_by: ['alertname'] + group_wait: 1m + group_interval: 10m + repeat_interval: 10h + receiver: 'email' + +receivers: + - name: 'email' + email_configs: + - to: '' + +inhibit_rules: + - source_match: + severity: 'critical' + target_match: + severity: 'warning' + equal: ['alertname'] +```` + + +### API +The `AlertManager` API is divided into two versions, `v1` and `v2`. The current `AlertManager` API version is `v2` +(For configuration see +[api/v2/openapi.yaml](https://github.com/prometheus/alertmanager/blob/master/api/v2/openapi.yaml)). + +By default, the prefix is `/api/v1` or `/api/v2` and the endpoint for sending alerts is `/api/v1/alerts` or `/api/v2/alerts`. +If the user specifies `--web.route-prefix`, +for example `--web.route-prefix=/alertmanager/`, +then the prefix will become `/alertmanager/api/v1` or `/alertmanager/api/v2`, +and the endpoint that sends the alert becomes `/alertmanager/api/v1/alerts` +or `/alertmanager/api/v2/alerts`. + +## Creating trigger + +### Writing the trigger class + +The user defines a trigger by creating a Java class and writing the logic in the hook. +Please refer to [Triggers](Triggers.md) for the specific configuration process and the usage method of `AlertManagerSink` related tools provided by the Sink module. + +The following example creates the `org.apache.iotdb.trigger.AlertingTriggerExample` class, +Its alertManagerHandler member variables can send alerts to the AlertManager instance +at the address of `http://127.0.0.1:9093/`. + +When `value> 100.0`, send an alert of `critical` severity; +when `50.0 labels = new HashMap<>(); + + HashMap annotations = new HashMap<>(); + + @Override + public void onCreate(TriggerAttributes attributes) throws Exception { + + AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts"); + + alertManagerHandler.open(alertManagerConfiguration); + + alertname = "alert_test"; + + labels.put("series", "root.ln.wf01.wt01.temperature"); + labels.put("value", ""); + labels.put("severity", ""); + + annotations.put("summary", "high temperature"); + annotations.put("description", "{{.alertname}}: {{.series}} is {{.value}}"); + } + + @Override + public Double fire(long timestamp, Double value) throws Exception { + if (value > 100.0) { + + labels.put("value", String.valueOf(value)); + labels.put("severity", "critical"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + + } else if (value > 50.0) { + + labels.put("value", String.valueOf(value)); + labels.put("severity", "warning"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + + } + + return value; + } + + @Override + public double[] fire(long[] timestamps, double[] values) throws Exception { + for (double value : values) { + if (value > 100.0) { + + labels.put("value", String.valueOf(value)); + labels.put("severity", "critical"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + + } else if (value > 50.0) { + + labels.put("value", String.valueOf(value)); + labels.put("severity", "warning"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + + } + } + return values; + } +} +```` + +### Creating trigger + +The following SQL statement registered the trigger +named `root-ln-wf01-wt01-alert` +on the `root.ln.wf01.wt01.temperature` time series, +whose operation logic is defined +by `org.apache.iotdb.trigger.AlertingTriggerExample` java class. + +``` roomsql + CREATE TRIGGER root-ln-wf01-wt01-alert + AFTER INSERT + ON root.ln.wf01.wt01.temperature + AS "org.apache.iotdb.trigger.AlertingTriggerExample" +``` + + +## Writing data + +When we finish the deployment and startup of AlertManager as well as the creation of Trigger, +we can test the alerting +by writing data to the time series. + +``` roomsql +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (1, 0); +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (2, 30); +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (3, 60); +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (4, 90); +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (5, 120); +``` + +After executing the above writing statements, +we can receive an alerting email. Because our `AlertManager` configuration above +makes alerts of `critical` severity inhibit those of `warning` severity, +the alerting email we receive only contains the alert triggered +by the writing of `(5, 120)`. + +alerting + + diff --git a/docs/UserGuide/Advanced-Features/Triggers.md b/docs/UserGuide/Advanced-Features/Triggers.md index eb7253871e8bc..478445f4d4b30 100644 --- a/docs/UserGuide/Advanced-Features/Triggers.md +++ b/docs/UserGuide/Advanced-Features/Triggers.md @@ -566,7 +566,112 @@ for (int i = 0; i < 100; ++i) { } ``` +#### AlertManagerSink +In a trigger, you can use `AlertManagerSink` to send messages to AlertManager。 + +You need to specify the endpoint to send alerts of your AlertManager when constructing +`AlertManagerConfiguration` +``` +AlertManagerConfiguration(String endpoint); +``` + +`AlertManagerEvent` offers three types of constructors: +``` +AlertManagerEvent(String alertname); +AlertManagerEvent(String alertname, Map extraLabels); +AlertManagerEvent(String alertname, Map extraLabels, Map annotations); +``` + +* `alertname` is a required parameter to identify an `alert`. The `alertname` field can be used for grouping and deduplication when the `AlertManager` sends an alert. +* `extraLabels` is optional. In the backend, it is combined with `alertname` to form `labels` to identify an `alert`, which can be used for grouping and deduplication when `AlertManager` sends alarms. +* `annotations` is optional, and its value can use Go style template `{{.}}`. `{{.}}` will be replaced with `labels[]` when the message is finally generated. +* `labels` and `annotations` will be parsed into json string and sent to `AlertManager`: +``` +{ + "labels": { + "alertname": "", + "": "", + ... + }, + "annotations": { + "": "", + ... + } +} +``` + +Call the `onEvent(AlertManagerEvent event)` method of `AlertManagerHandler` to send an alert. + +Example 1: + +Only pass `alertname`. + +```java +AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"); +AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + +alertManagerHandler.open(alertManagerConfiguration); + +String alertName = "test0"; + +AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName); + +alertManagerHandler.onEvent(alertManagerEvent); +``` + +Example 2: + +Pass `alertname` and `extraLabels`. + +```java +AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"); +AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + +alertManagerHandler.open(alertManagerConfiguration); + +String alertName = "test1"; + +HashMap extraLabels = new HashMap<>(); +extraLabels.put("severity", "critical"); +extraLabels.put("series", "root.ln.wt01.wf01.temperature"); +extraLabels.put("value", String.valueOf(100.0)); + +AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName, extraLabels); + +alertManagerHandler.onEvent(alertManagerEvent); +``` + +Example 3: + +Pass `alertname`, `extraLabels` 和 `annotations`. + +The final value of the `description` field will be parsed as `test2: root.ln.wt01.wf01.temperature is 100.0`. + +```java +AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"); +AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + +alertManagerHandler.open(alertManagerConfiguration); + +String alertName = "test2"; + +HashMap extraLabels = new HashMap<>(); +extraLabels.put("severity", "critical"); +extraLabels.put("series", "root.ln.wt01.wf01.temperature"); +extraLabels.put("value", String.valueOf(100.0)); + +HashMap annotations = new HashMap<>(); +annotations.put("summary", "high temperature"); +annotations.put("description", "{{.alertname}}: {{.series}} is {{.value}}"); + +AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName, extraLabels, annotations); + +alertManagerHandler.onEvent(alertManagerEvent); +``` ## Maven Project Example diff --git a/docs/zh/UserGuide/Advanced-Features/Alerting.md b/docs/zh/UserGuide/Advanced-Features/Alerting.md new file mode 100644 index 0000000000000..97c94b59b28ff --- /dev/null +++ b/docs/zh/UserGuide/Advanced-Features/Alerting.md @@ -0,0 +1,380 @@ + + +# 告警 + +## 概览 +IoTDB 告警功能预计支持两种模式: + +* 写入触发:用户写入原始数据到原始时间序列,每插入一条数据都会触发 `trigger` 的判断逻辑, +若满足告警要求则发送告警到下游数据接收器, +数据接收器再转发告警到外部终端。这种模式: + * 适合需要即时监控每一条数据的场景。 + * 由于触发器中的运算会影响数据写入性能,适合对原始数据写入性能不敏感的场景。 + +* 持续查询:用户写入原始数据到原始时间序列, +`ContinousQuery` 定时查询原始时间序列,将查询结果写入新的时间序列, +每一次写入触发 `trigger` 的判断逻辑, +若满足告警要求则发送告警到下游数据接收器, +数据接收器再转发告警到外部终端。这种模式: + * 适合需要定时查询数据在某一段时间内的情况的场景。 + * 适合需要将原始数据降采样并持久化的场景。 + * 由于定时查询几乎不影响原始时间序列的写入,适合对原始数据写入性能敏感的场景。 + +随着 `trigger` 模块和 `sink` 模块的引入, +目前用户使用这两个模块,配合 `AlertManager` 可以实现写入触发模式的告警。 + +## 部署 AlertManager + +### 安装与运行 +#### 二进制文件 +预编译好的二进制文件可在 [这里](https://prometheus.io/download/) 下载。 + +运行方法: +```` +./alertmanager --config.file= +```` + +#### Docker 镜像 +可在 [Quay.io](https://hub.docker.com/r/prom/alertmanager/) +或 [Docker Hub](https://quay.io/repository/prometheus/alertmanager) 获得。 + +运行方法: +```` +docker run --name alertmanager -d -p 127.0.0.1:9093:9093 quay.io/prometheus/alertmanager +```` + +### 配置 + +如下是一个示例,可以覆盖到大部分配置规则,详细的配置规则参见 +[这里](https://prometheus.io/docs/alerting/latest/configuration/)。 + +示例: +``` yaml +# alertmanager.yml + +global: + # The smarthost and SMTP sender used for mail notifications. + smtp_smarthost: 'localhost:25' + smtp_from: 'alertmanager@example.org' + +# The root route on which each incoming alert enters. +route: + # The root route must not have any matchers as it is the entry point for + # all alerts. It needs to have a receiver configured so alerts that do not + # match any of the sub-routes are sent to someone. + receiver: 'team-X-mails' + + # The labels by which incoming alerts are grouped together. For example, + # multiple alerts coming in for cluster=A and alertname=LatencyHigh would + # be batched into a single group. + # + # To aggregate by all possible labels use '...' as the sole label name. + # This effectively disables aggregation entirely, passing through all + # alerts as-is. This is unlikely to be what you want, unless you have + # a very low alert volume or your upstream notification system performs + # its own grouping. Example: group_by: [...] + group_by: ['alertname', 'cluster'] + + # When a new group of alerts is created by an incoming alert, wait at + # least 'group_wait' to send the initial notification. + # This way ensures that you get multiple alerts for the same group that start + # firing shortly after another are batched together on the first + # notification. + group_wait: 30s + + # When the first notification was sent, wait 'group_interval' to send a batch + # of new alerts that started firing for that group. + group_interval: 5m + + # If an alert has successfully been sent, wait 'repeat_interval' to + # resend them. + repeat_interval: 3h + + # All the above attributes are inherited by all child routes and can + # overwritten on each. + + # The child route trees. + routes: + # This routes performs a regular expression match on alert labels to + # catch alerts that are related to a list of services. + - match_re: + service: ^(foo1|foo2|baz)$ + receiver: team-X-mails + + # The service has a sub-route for critical alerts, any alerts + # that do not match, i.e. severity != critical, fall-back to the + # parent node and are sent to 'team-X-mails' + routes: + - match: + severity: critical + receiver: team-X-pager + + - match: + service: files + receiver: team-Y-mails + + routes: + - match: + severity: critical + receiver: team-Y-pager + + # This route handles all alerts coming from a database service. If there's + # no team to handle it, it defaults to the DB team. + - match: + service: database + + receiver: team-DB-pager + # Also group alerts by affected database. + group_by: [alertname, cluster, database] + + routes: + - match: + owner: team-X + receiver: team-X-pager + + - match: + owner: team-Y + receiver: team-Y-pager + + +# Inhibition rules allow to mute a set of alerts given that another alert is +# firing. +# We use this to mute any warning-level notifications if the same alert is +# already critical. +inhibit_rules: +- source_match: + severity: 'critical' + target_match: + severity: 'warning' + # Apply inhibition if the alertname is the same. + # CAUTION: + # If all label names listed in `equal` are missing + # from both the source and target alerts, + # the inhibition rule will apply! + equal: ['alertname'] + + +receivers: +- name: 'team-X-mails' + email_configs: + - to: 'team-X+alerts@example.org, team-Y+alerts@example.org' + +- name: 'team-X-pager' + email_configs: + - to: 'team-X+alerts-critical@example.org' + pagerduty_configs: + - routing_key: + +- name: 'team-Y-mails' + email_configs: + - to: 'team-Y+alerts@example.org' + +- name: 'team-Y-pager' + pagerduty_configs: + - routing_key: + +- name: 'team-DB-pager' + pagerduty_configs: + - routing_key: +``` + +在后面的示例中,我们采用的配置如下: +````yaml +# alertmanager.yml + +global: + smtp_smarthost: '' + smtp_from: '' + smtp_auth_username: '' + smtp_auth_password: '' + smtp_require_tls: false + +route: + group_by: ['alertname'] + group_wait: 1m + group_interval: 10m + repeat_interval: 10h + receiver: 'email' + +receivers: + - name: 'email' + email_configs: + - to: '' + +inhibit_rules: + - source_match: + severity: 'critical' + target_match: + severity: 'warning' + equal: ['alertname'] +```` + + +### API +`AlertManager` API 分为 `v1` 和 `v2` 两个版本,当前 `AlertManager` API 版本为 `v2` +(配置参见 +[api/v2/openapi.yaml](https://github.com/prometheus/alertmanager/blob/master/api/v2/openapi.yaml))。 + +默认配置的前缀为 `/api/v1` 或 `/api/v2`, +发送告警的 endpoint 为 `/api/v1/alerts` 或 `/api/v2/alerts`。 +如果用户指定了 `--web.route-prefix`, +例如 `--web.route-prefix=/alertmanager/`, +那么前缀将会变为 `/alertmanager/api/v1` 或 `/alertmanager/api/v2`, +发送告警的 endpoint 变为 `/alertmanager/api/v1/alerts` +或 `/alertmanager/api/v2/alerts`。 + + +## 创建 trigger + +### 编写 trigger 类 + +用户通过自行创建 Java 类、编写钩子中的逻辑来定义一个触发器。 +具体配置流程以及 Sink 模块提供的 `AlertManagerSink` 相关工具类的使用方法参见 [Triggers](Triggers.md)。 + +下面的示例创建了 `org.apache.iotdb.trigger.AlertingTriggerExample` 类, +其 `alertManagerHandler` +成员变量可发送告警至地址为 `http://127.0.0.1:9093/` 的 AlertManager 实例。 + +当 `value > 100.0` 时,发送 `severity` 为 `critical` 的告警; +当 `50.0 < value <= 100.0` 时,发送 `severity` 为 `warning` 的告警。 + +````java +package org.apache.iotdb.trigger; + +/* +此处省略包的引入 +*/ + +public class AlertingTriggerExample implements Trigger { + + AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + + String alertname; + + HashMap labels = new HashMap<>(); + + HashMap annotations = new HashMap<>(); + + @Override + public void onCreate(TriggerAttributes attributes) throws Exception { + + AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts"); + + alertManagerHandler.open(alertManagerConfiguration); + + alertname = "alert_test"; + + labels.put("series", "root.ln.wf01.wt01.temperature"); + labels.put("value", ""); + labels.put("severity", ""); + + annotations.put("summary", "high temperature"); + annotations.put("description", "{{.alertname}}: {{.series}} is {{.value}}"); + } + + @Override + public Double fire(long timestamp, Double value) throws Exception { + if (value > 100.0) { + + labels.put("value", String.valueOf(value)); + labels.put("severity", "critical"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + + } else if (value > 50.0) { + + labels.put("value", String.valueOf(value)); + labels.put("severity", "warning"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + + } + + return value; + } + + @Override + public double[] fire(long[] timestamps, double[] values) throws Exception { + for (double value : values) { + if (value > 100.0) { + + labels.put("value", String.valueOf(value)); + labels.put("severity", "critical"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + + } else if (value > 50.0) { + + labels.put("value", String.valueOf(value)); + labels.put("severity", "warning"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + + } + } + return values; + } +} +```` + +### 创建 trigger + +如下的 sql 语句在 `root.ln.wf01.wt01.temperature` +时间序列上注册了名为 `root-ln-wf01-wt01-alert`、 +运行逻辑由 `org.apache.iotdb.trigger.AlertingTriggerExample` +类定义的触发器。 + +``` roomsql + CREATE TRIGGER root-ln-wf01-wt01-alert + AFTER INSERT + ON root.ln.wf01.wt01.temperature + AS "org.apache.iotdb.trigger.AlertingTriggerExample" +``` + +## 写入数据 + +当我们完成 AlertManager 的部署和启动、Trigger 的创建, +可以通过向时间序列写入数据来测试告警功能。 + +``` roomsql +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (1, 0); +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (2, 30); +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (3, 60); +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (4, 90); +INSERT INTO root.ln.wf01.wt01(timestamp, temperature) VALUES (5, 120); +``` + +执行完上述写入语句后,可以收到告警邮件。由于我们的 `AlertManager` 配置中设定 `severity` 为 `critical` 的告警 +会抑制 `severity` 为 `warning` 的告警,我们收到的告警邮件中只包含写入 +`(5, 120)` 后触发的告警。 + +alerting + + + + + + + + + diff --git a/docs/zh/UserGuide/Advanced-Features/Triggers.md b/docs/zh/UserGuide/Advanced-Features/Triggers.md index 501d7ad77e118..637bc3b8bf835 100644 --- a/docs/zh/UserGuide/Advanced-Features/Triggers.md +++ b/docs/zh/UserGuide/Advanced-Features/Triggers.md @@ -583,6 +583,113 @@ for (int i = 0; i < 100; ++i) { } ``` +#### AlertManagerSink + +触发器可以使用`AlertManagerSink` 向 AlertManager 发送消息。 + +`AlertManagerConfiguration` 的构造需传入 AlertManager 的发送告警的 endpoint。 +``` +AlertManagerConfiguration(String endpoint); +``` + +`AlertManagerEvent` 提供三种构造函数: +``` +AlertManagerEvent(String alertname); +AlertManagerEvent(String alertname, Map extraLabels); +AlertManagerEvent(String alertname, Map extraLabels, Map annotations); +``` +其中: +* `alertname` 是必传参数,用于标识一个 `alert`,`alertname` 字段可用于 `AlertManager` 发送告警时的分组和消重。 +* `extraLabels` 可选传,在后台与 `alertname` 组合成 `labels` 一起标识一个 `alert`,可用于 `AlertManager` 发送告警时的分组和消重。 +* `annotations` 可选传,它的 value 值可使用 Go 语言模板风格的 `{{.}}`, + `{{.}}` 在最终生成消息时会被替换为 `labels[]`。 +* `labels` 和 `annotations` 会被解析成 json 字符串发送给 `AlertManager`: +``` +{ + "labels": { + "alertname": "", + "": "", + ... + }, + "annotations": { + "": "", + ... + } +} +``` + +调用 `AlertManagerHandler` 的 `onEvent(AlertManagerEvent event)` 方法发送一个告警。 + +使用示例 1: + +只传 `alertname`。 + +```java +AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"); +AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + +alertManagerHandler.open(alertManagerConfiguration); + +String alertName = "test0"; + +AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName); + +alertManagerHandler.onEvent(alertManagerEvent); +``` + +使用示例 2: + +传入 `alertname` 和 `extraLabels`。 + +```java +AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"); +AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + +alertManagerHandler.open(alertManagerConfiguration); + +String alertName = "test1"; + +HashMap extraLabels = new HashMap<>(); +extraLabels.put("severity", "critical"); +extraLabels.put("series", "root.ln.wt01.wf01.temperature"); +extraLabels.put("value", String.valueOf(100.0)); + +AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName, extraLabels); + +alertManagerHandler.onEvent(alertManagerEvent); +``` + +使用示例 3: + +传入 `alertname`, `extraLabels` 和 `annotations` 。 + +最终 `description` 字段的值会被解析为 `test2: root.ln.wt01.wf01.temperature is 100.0`。 + +```java +AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v1/alerts"); +AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + +alertManagerHandler.open(alertManagerConfiguration); + +String alertName = "test2"; + +HashMap extraLabels = new HashMap<>(); +extraLabels.put("severity", "critical"); +extraLabels.put("series", "root.ln.wt01.wf01.temperature"); +extraLabels.put("value", String.valueOf(100.0)); + +HashMap annotations = new HashMap<>(); +annotations.put("summary", "high temperature"); +annotations.put("description", "{{.alertname}}: {{.series}} is {{.value}}"); + +AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName, extraLabels, annotations); + +alertManagerHandler.onEvent(alertManagerEvent); +``` + ## 完整的Maven示例项目 diff --git a/example/trigger/src/main/java/org/apache/iotdb/trigger/AlertingTriggerExample.java b/example/trigger/src/main/java/org/apache/iotdb/trigger/AlertingTriggerExample.java new file mode 100644 index 0000000000000..a049be89e00a5 --- /dev/null +++ b/example/trigger/src/main/java/org/apache/iotdb/trigger/AlertingTriggerExample.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.trigger; + + +import org.apache.iotdb.db.engine.trigger.api.Trigger; +import org.apache.iotdb.db.engine.trigger.api.TriggerAttributes; +import org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration; +import org.apache.iotdb.db.sink.alertmanager.AlertManagerEvent; +import org.apache.iotdb.db.sink.alertmanager.AlertManagerHandler; + +import java.util.HashMap; + +public class AlertingTriggerExample implements Trigger { + + AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + + String alertname; + + HashMap labels = new HashMap<>(); + + HashMap annotations = new HashMap<>(); + + @Override + public void onCreate(TriggerAttributes attributes) throws Exception { + + AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts"); + + alertManagerHandler.open(alertManagerConfiguration); + + alertname = "alert_test"; + + labels.put("series", "root.ln.wf01.wt01.temperature"); + labels.put("value", ""); + labels.put("severity", ""); + + annotations.put("summary", "high temperature"); + annotations.put("description", "{{.alertname}}: {{.series}} is {{.value}}"); + } + + @Override + public Double fire(long timestamp, Double value) throws Exception { + if (value > 100.0) { + labels.put("value", String.valueOf(value)); + labels.put("severity", "critical"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + } else if (value > 50.0) { + labels.put("value", String.valueOf(value)); + labels.put("severity", "warning"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + } + + return value; + } + + @Override + public double[] fire(long[] timestamps, double[] values) throws Exception { + for (double value : values) { + if (value > 100.0) { + labels.put("value", String.valueOf(value)); + labels.put("severity", "critical"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + } else if (value > 50.0) { + labels.put("value", String.valueOf(value)); + labels.put("severity", "warning"); + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertname, labels, annotations); + alertManagerHandler.onEvent(alertManagerEvent); + } + } + return values; + } +} + diff --git a/server/pom.xml b/server/pom.xml index 5b7abbaf49e95..ff7cd3be98593 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -207,6 +207,12 @@ org.fusesource.mqtt-client mqtt-client + + org.apache.httpcomponents + httpclient + 4.3.5 + compile + diff --git a/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerConfiguration.java b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerConfiguration.java new file mode 100644 index 0000000000000..00dd9eec7dabe --- /dev/null +++ b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerConfiguration.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.db.sink.alertmanager; + +import org.apache.iotdb.db.sink.api.Configuration; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +public class AlertManagerConfiguration implements Configuration { + + private final String endpoint; + + public AlertManagerConfiguration(String endpoint) { + + this.endpoint = endpoint; + } + + public String getEndpoint() { + return endpoint; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration)) { + return false; + } + + org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration that = + (org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration) o; + + return new EqualsBuilder() + .appendSuper(super.equals(o)) + .append(endpoint, that.endpoint) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37).appendSuper(super.hashCode()).append(endpoint).toHashCode(); + } +} diff --git a/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerEvent.java b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerEvent.java new file mode 100644 index 0000000000000..ea63898aa1a84 --- /dev/null +++ b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerEvent.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.db.sink.alertmanager; + +import org.apache.iotdb.db.sink.api.Event; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class AlertManagerEvent implements Event { + + private final Map labels; + + private final Map annotations; + + public AlertManagerEvent(String alertname) { + this.labels = new HashMap<>(); + this.labels.put("alertname", alertname); + this.annotations = null; + } + + public AlertManagerEvent(String alertname, Map extraLabels) { + this.labels = extraLabels; + this.labels.put("alertname", alertname); + this.annotations = null; + } + + public AlertManagerEvent( + String alertname, Map extraLabels, Map annotations) { + + this.labels = extraLabels; + this.labels.put("alertname", alertname); + this.annotations = new HashMap<>(); + + for (Map.Entry entry : annotations.entrySet()) { + this.annotations.put(entry.getKey(), fillTemplate(this.labels, entry.getValue())); + } + } + + public Map getAnnotations() { + return annotations; + } + + public Map getLabels() { + return labels; + } + + private static String fillTemplate(Map map, String template) { + if (template == null || map == null) return null; + StringBuffer sb = new StringBuffer(); + Matcher m = Pattern.compile("\\{\\{\\.\\w+}}").matcher(template); + while (m.find()) { + String param = m.group(); + String key = param.substring(3, param.length() - 2).trim(); + String value = map.get(key); + m.appendReplacement(sb, value == null ? "" : value); + } + m.appendTail(sb); + return sb.toString(); + } +} diff --git a/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerHandler.java b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerHandler.java new file mode 100644 index 0000000000000..67057a42a79ad --- /dev/null +++ b/server/src/main/java/org/apache/iotdb/db/sink/alertmanager/AlertManagerHandler.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.db.sink.alertmanager; + +import org.apache.iotdb.db.sink.api.Handler; +import org.apache.iotdb.db.sink.exception.SinkException; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import java.lang.reflect.Type; +import java.util.Map; + +public class AlertManagerHandler + implements Handler< + org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration, + org.apache.iotdb.db.sink.alertmanager.AlertManagerEvent> { + + private HttpPost request; + + @Override + public void open(org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration configuration) + throws Exception { + this.request = new HttpPost(configuration.getEndpoint()); + request.setHeader("Accept", "application/json"); + request.setHeader("Content-type", "application/json"); + } + + @Override + public void onEvent(AlertManagerEvent event) throws Exception { + + String json = eventToJson(event); + + request.setEntity(new StringEntity(json)); + + try (CloseableHttpClient client = HttpClients.createDefault()) { + + CloseableHttpResponse response = client.execute(request); + + if (response.getStatusLine().getStatusCode() != 200) { + throw new SinkException(response.getStatusLine().toString()); + } + + } + + } + + private static String eventToJson(AlertManagerEvent event) throws SinkException { + Gson gson = new Gson(); + Type gsonType = new TypeToken() {}.getType(); + + StringBuilder sb = new StringBuilder(); + sb.append("[{\"labels\":"); + + if (event.getLabels() == null) { + throw new SinkException("labels empty error"); + } + + String labelsString = gson.toJson(event.getLabels(), gsonType); + sb.append(labelsString); + + if (event.getAnnotations() != null) { + String annotationsString = gson.toJson(event.getAnnotations(), gsonType); + sb.append(","); + sb.append("\"annotations\":"); + sb.append(annotationsString); + } + sb.append("}]"); + return sb.toString(); + } +} diff --git a/server/src/test/java/org/apache/iotdb/db/sink/AlertManagerTest.java b/server/src/test/java/org/apache/iotdb/db/sink/AlertManagerTest.java new file mode 100644 index 0000000000000..7b56fe6decc10 --- /dev/null +++ b/server/src/test/java/org/apache/iotdb/db/sink/AlertManagerTest.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.db.sink; + +import org.apache.iotdb.db.sink.alertmanager.AlertManagerConfiguration; +import org.apache.iotdb.db.sink.alertmanager.AlertManagerEvent; +import org.apache.iotdb.db.sink.alertmanager.AlertManagerHandler; + +import org.junit.Test; + +import java.util.HashMap; + +public class AlertManagerTest { + + @Test + public void alertmanagerTest0() throws Exception { + AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts"); + AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + + alertManagerHandler.open(alertManagerConfiguration); + + String alertName = "test0"; + + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName); + + alertManagerHandler.onEvent(alertManagerEvent); + } + + @Test + public void alertmanagerTest1() throws Exception { + AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts"); + AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + + alertManagerHandler.open(alertManagerConfiguration); + + String alertName = "test1"; + + HashMap extraLabels = new HashMap<>(); + extraLabels.put("severity", "critical"); + extraLabels.put("series", "root.ln.wt01.wf01.temperature"); + extraLabels.put("value", String.valueOf(100.0)); + + AlertManagerEvent alertManagerEvent = new AlertManagerEvent(alertName, extraLabels); + + alertManagerHandler.onEvent(alertManagerEvent); + } + + @Test + public void alertmanagerTest2() throws Exception { + AlertManagerConfiguration alertManagerConfiguration = + new AlertManagerConfiguration("http://127.0.0.1:9093/api/v2/alerts"); + AlertManagerHandler alertManagerHandler = new AlertManagerHandler(); + + alertManagerHandler.open(alertManagerConfiguration); + + String alertName = "test2"; + + HashMap extraLabels = new HashMap<>(); + extraLabels.put("severity", "critical"); + extraLabels.put("series", "root.ln.wt01.wf01.temperature"); + extraLabels.put("value", String.valueOf(100.0)); + + HashMap annotations = new HashMap<>(); + annotations.put("summary", "high temperature"); + annotations.put("description", "{{.alertname}}: {{.series}} is {{.value}}"); + + AlertManagerEvent alertManagerEvent = + new AlertManagerEvent(alertName, extraLabels, annotations); + + alertManagerHandler.onEvent(alertManagerEvent); + } +} diff --git a/server/src/test/java/org/apache/iotdb/db/sink/TimeSeriesSinkTest.java b/server/src/test/java/org/apache/iotdb/db/sink/TimeSeriesSinkTest.java deleted file mode 100644 index 7787849557bff..0000000000000 --- a/server/src/test/java/org/apache/iotdb/db/sink/TimeSeriesSinkTest.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.db.sink; - -public class TimeSeriesSinkTest {}