Zabbix 6.0 機械学習系の関数を試してみる

先月(2/14)にZabbixの最新のLTS(長期サポート)版である6.0系がリリースされました。

リリース内容については、SRA OSSさんの[この辺りの記事](https://www.sraoss.co.jp/tech-blog/zabbix/zabbix60-newfeature-latest/)とかにまとまっているので参考にされると良いかと思います。

非常に多くの機能が実装されている中で、私が特に気になったのが、データ分析系の関数が新たに実装された点です。

まだ、リリースされて間もないため、公式ドキュメントにも使い方とかはあるもののその中でどういう処理になっているかなど、あまり情報がない状況です。理解のため、コードの内容も確認しつつ試してみたので情報共有します。

# 6.0で追加されたデータ分析系の関数

いろいろな関数が追加されていますが、特にこの辺りが分析系として活用できる部分かと思います。

- baselinedev
    - 過去データの標準偏差を元に直近データのブレ具合を評価する関数
- baselinewma
    - 過去データの加重移動平均の値を元に値を推測する関数
- trendstl
    - 過去データの変動から季節周期要素・トレンド要素を除外した残りのデータのブレ具合を評価する関数

今回はこの辺りの機能の使い方を紹介します。

これらの関数はいずれも、「計算アイテム」および「トリガー関数」として利用ができます。

また、いずれの関数も **「トレンドデータ」** を使うことが前提となっているため、注意が必要です。
長期的なデータの評価になるため、ヒストリの実データを使うわけではなく、1時間周期で平均・最大・最小・データ個数のサマリ情報を管理するトレンドデータを使って評価されます。

# 過去データの標準偏差を元に直近データのブレ具合を評価する「baselinedev」

## 用途

- 時系列のモニタリングデータに対し、過去の変動推移の傾向から、おおよそこれぐらいの変動幅の中で動くだろう値をベースに、直近どの程度変動のブレ方が大きくなってきているかを知るために使えます。
- 例えば、普段は、CPU使用率が、ある平均値を軸に上下10%程度のブレ幅で動いているのに、直近、平均値と比較して高い数値を示すタイミングが増えてブレ幅が大きくなってきたなといった状況を知ることに使えると思います。

## 関数の表記フォーマット

```
baselinedev(/host/key,data period:time shift,season_unit,num_seasons)
```

各引数の意味は以下。

|No.|引数|概要|指定値例|
|:---|:---|:---|:------|
|1|/host/key|評価対象のアイテム指定|/Zabbix server/vm.memory.size[available]|
|2|data period:time shift|評価対象の期間指定|1d:now/d-1d (※指定方法の見方は下部に補足)|
|3|season_unit|評価の基準にする過去の値を扱う期間単位指定(h,d,w,M,yのいずれか)|"w"|
|4|num_seasons|season_unitに指定の期間の何個分前まで評価対象にするかの指定|3|

例えば、以下のような指定にした場合、意味は次のようになります。

```
baselinedev(/Zabbix server/vm.memory.size[available],1d:now/d-1d,"w",3)
```

- Zabbix serverというホスト名に登録されているvm.memory.size[available]というキーのアイテムの監視データを使って評価します。
- 評価する対象は、評価実行時点を起点に1日前の1日分のデータを対象にします(時間は00:00に切り上げ)。
- 過去データとしては、3週間分のデータを対象にします。

イメージとしては以下の図のような感じです。

![baselinedev.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45851/5d8e4d05-db30-f33d-18da-888851d153c7.png)


### 【補足解説】data period:time shiftの指定方法

data period:time shiftの指定の仕方について補足しておきます。
詳細はZabbixの公式マニュアルを参照してください。
https://www.zabbix.com/documentation/current/en/manual/config/triggers/expression#function-specific-parameters

data periodの部分はシンプルで、「数 + 単位」で期間の長さを指定します。(例: 1h, 2d, 3w等)
time shiftの部分が特殊で、例えば、今から1時間前にずらしたタイミングを指定したい場合、「now-1h」をコロンの後ろにつけ、「1d:now-1h」といった指定の仕方になります。
また、nowの後ろに/で区切ると、現在時刻に対して指定単位で切り上げた値を起点に前後タイムシフトすることができます。
例えば、now/h-1hとすると、今の時間が10:30の場合、10:00に切り上げた後、1時間前にスライドするので09:00を示すことができます。


## 分析ロジック

baselinedevの処理ロジックは以下のようになっています。
対象のコードは以下の辺りを起点に確認すると良いと思います。

[6.0.1タグ箇所の関数の処理コード部分](https://github.com/zabbix/zabbix/blob/6.0.1/src/libs/zbxserver/evalfunc.c#L3509)

- 第2引数で指定した期間分のデータおよび、過去の対象シーズン期間(上記図で示した赤枠箇所)のデータを取得し、各赤枠毎に1つのトレンド平均値を算出(上記例の場合、4個のデータが算出されることになる)
- 上記全期間のデータを使って[母標準偏差](https://ja.wikipedia.org/wiki/%E6%A8%99%E6%BA%96%E5%81%8F%E5%B7%AE#%E6%AF%8D%E9%9B%86%E5%9B%A3%E3%81%AE%E6%A8%99%E6%BA%96%E5%81%8F%E5%B7%AE)を算出
- 「(第2引数で指定された期間のトレンド平均値 - 全体の平均値)の絶対値 / 母標準偏差」の結果が評価値として返る



## 結果の解釈の仕方

- この関数の結果としては、0や0.2, 1, 2, 3など、標準偏差に対してどれぐらい乖離しているかを示す表現で返ってきます。
- 監視データが正規分布に従うと仮定すると、1x標準偏差(σ)以内に収まる確率は約68%、2σで95%、3σで99.7%が収まることになるため、例えば、2を超えるとなると平均からの外れ度合いが全体の5%の率に位置する程度にブレているということがわかります。
- そのため、この関数の結果が大きくなればなるほど平均値から乖離しているんだということがわかります。

## 注意点

- 改めて注意点ですが、評価にはZabbix内で管理されているトレンドデータが使われます。
- 評価時、第2引数で指定した評価対象の期間のトレンドデータおよび過去シーズンの期間のトレンドデータを順に取得して評価をしていく実装になっています。その途中で評価する対象のデータがトレンドデータの登録テーブル(trends or trends_uintテーブル)から取得できない場合、評価に必要なデータが不足しているということで「not enough data」というエラーが出て評価されません。
- 特に注意が必要なのが、例えば直近のデータを評価したいと言って、第2引数を「1h:now/h」とかに指定した場合です。(この指定の場合、評価実施日時が、2022/3/9 10:30だとすると、時間で切り上げるので、2022/3/9 10:00の地点のトレンドデータを見に行こうとします。)
- トレンドデータは1時間の統計値が登録されるので、10:30の時点では、10:00のデータというのはtrendsテーブルに登録されていません。そのため、評価エラーになります。
- これらを考慮してどの範囲のデータで実施するかを確認することが必要になります。


# 過去データの加重移動平均の値を元に値を推測する「baselinewma」

## 用途

- 時系列のモニタリングデータに対し、過去の変動推移の傾向から、おおよそこれぐらいの値になるのではないかの推定値を確認するために使えます。
- 例えば、普段は、CPU使用率が、おおよそ30%程度の使用率で推移している(でもより遠くの過去だともう少し低い水準で動いている)、その場合、直近だとおそらく一番近いところの変動傾向と似ている可能性が高いだろうから30%近くまで上がるんじゃないかと推定されるといった感じです。

## 関数の表記フォーマット

```
baselinewma(/host/key,data period:time shift,season_unit,num_seasons)
```

先ほどのbaselinedevと引数としては同じになります。

## 分析ロジック

baselinewmaの処理ロジックは以下のようになっています。
対象のコードは先ほどのbaselinedevと[同様の辺り](https://github.com/zabbix/zabbix/blob/6.0.1/src/libs/zbxserver/evalfunc.c#L3509)を中心に見ていくと良いと思います。

- 先ほどのbaselinedevとは異なり、直近のデータを除く、過去の対象シーズン期間のデータを取得します。(先ほどの引数の例だと1週前、2週前、3週前の3期間の平均値)
- シーズンが過去にいけば行くほど加重を下げるよう重みが設定されます。(1週前は3, 2週前は2, 3週前は1)
- この平均と重みを用いて移動平均を算出します。

それぞれの値を以下変数で表現すると計算式は次の通りとなります。

- 1シーズン前のトレンド平均値: avg1
- 2シーズン前のトレンド平均値: avg2
- 3シーズン前のトレンド平均値: avg3 
- 1シーズン前の値に適用する重み: w1(3シーズンの場合は3)
- 2シーズン前の値に適用する重み: w2(3シーズンの場合は2)
- 3シーズン前の値に適用する重み: w2(3シーズンの場合は1)

```math
\frac{avg1\times w1 + avg2 \times w2 + avg3 + \times w3}{w1 + w2 + w3}
```

## 結果の解釈の仕方

- この関数の結果としては、過去の値の大きさに従って変動します。
- 過去の同様の時間帯の値を元に(特に評価期間に近いシーズンのデータに影響を受けて)推定される値がいくらぐらいであるかが確認できます。
- この値を軸に異常かどうか判断するには、比較対象となる値と比較させるなどの工夫が必要になります。
- baselinedevの場合は統計的に2σや3σ乖離していれば分布上乖離しているとみなせる形でしたが、baselinewmaの場合は監視データのスケールに応じて値が変わるためです。
- 過去データの推移をこの関数で解釈させ、直近の平均値と比較するなどの対応が良さそうです。

## 注意点

- baselinewmaの場合、baselinedevと異なり、第2引数で指定した期間のデータを評価に使うわけではありません。

# 過去データの変動から季節周期・トレンド要素を除外した残りデータのブレ具合を評価する「trendstl」

## 用途

- 周期的に変動しつつ、上昇/下降傾向もあるような時系列データに対し、いつも通りの稼働傾向で動いているのかそうでないのかを確認するために使えます。
- 例えば、普段から毎日日中負荷が上がり夜間は下がるといった傾向を繰り返し、かつ徐々に利用者が増加していき負荷自体は上昇しているようなケースを想定します。
- このようなケースだと過去の傾向と直近の傾向を見ると値として変化してくるのは問題なく、上昇の仕方や値のブレ方が違うといったタイミングを異常とみなしたいことがあるかと思います。
- そんな時にこのtrendstlの関数で評価することで、周期性やトレンド傾向の要素を排除した残りの要素の異常度合いを知ることができます。

## 関数の表記フォーマット

```
trendstl (/host/key,eval period:time shift,detection period,season,<deviations>,<devalg>,<s_window>)
```

各引数の意味は以下。

|No.|引数|概要|指定値例|
|:---|:---|:---|:------|
|1|/host/key|評価対象のアイテム指定|/Zabbix server/vm.memory.size[available]|
|2|eval period:time shift|評価(STL分解する)対象の期間指定|30d:now/d|
|3|detection period|異常発生しているかを確認する対象期間|1d|
|4|season|STL分解時のどの期間幅で周期性を判定させるかの指定|1d|
|5|deviations|異常とみなす基準を偏差の何倍にするかの指定(1以上)|2(指定無しの場合のデフォルトは3)|
|6|devalg|偏差の算出アルゴリズム指定(mad/stddevpop/stddevsampのいずれか)|"stddevpop"(指定無しの場合のデフォルトはmad)(※各アルゴリズムは補足解説参照)|
|7|s_window|loessで季節調整する際のウィンドウ幅を指定|10(指定無しの場合のデフォルトは第2引数で指定した期間の値の数*10+1)|


### 【補足解説】devalgのバリエーション

mad、stddevpop、stddevsampの3種類から選択可能です。
それぞれ以下を示します。

- mad: 中央絶対偏差
    - 期間中のデータの中央値を取り、期間中の各値と中央値の差の絶対値のさらに中央値を求めたものになります。([参考](https://atmarkit.itmedia.co.jp/ait/articles/2109/27/news027.html))
- stddevpop: 母集団の標準偏差
    - [参考](https://ja.wikipedia.org/wiki/%E6%A8%99%E6%BA%96%E5%81%8F%E5%B7%AE#%E6%AF%8D%E9%9B%86%E5%9B%A3%E3%81%AE%E6%A8%99%E6%BA%96%E5%81%8F%E5%B7%AE) 
- stddevsamp: 標本の標準偏差
    - [参考](https://ja.wikipedia.org/wiki/%E6%A8%99%E6%BA%96%E5%81%8F%E5%B7%AE#%E6%A8%99%E6%9C%AC%E6%A8%99%E6%BA%96%E5%81%8F%E5%B7%AE)

## 分析ロジック

trendstlの処理ロジックは以下のようになっています。
対象のコードは[この辺り](https://github.com/zabbix/zabbix/blob/6.0.1/src/libs/zbxserver/evalfunc.c#L2760)を参考に見ていくと良いと思います。

- 第2引数で指定した期間内のデータを用いてSTL分解し、季節周期要素、トレンド傾向要素、その他要素にします。(イメージは以下の図を参照)
    - この時、第4引数(season)で指定した期間で周期的な変動をするという前提で分解します。
- その他要素の部分のみを対象に第6引数(devalg)で指定したアルゴリズムを用いて偏差を算出します。
- 第3引数(detection period)で指定した期間のデータが、「上記偏差の値 * 第5引数で指定した倍数」の範囲に値が収まるかどうかを各データ評価
- 評価期間中の全件数の内、何割が収まらないかの数値を計算して返す

![trendstl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45851/62b64471-564e-87e7-b8da-09cb34461f9c.png)


## 結果の解釈の仕方

- この関数の結果として例えば0.5が返ってきたとすると、評価対象の期間のデータが、過去のデータと比較し、周期性・トレンド性を除外した残りの変動要素のデータに対してブレているデータ点が50%程度あるということを示し、評価対象期間中約半数が異常な状態だったのではないかという解釈になります。


## 注意点

- 値がどのような周期で変動するかなど把握した上で、seasonのパラメータ等調整して定義する必要があります。

# まとめ

- Zabbix6.0では、過去データを使って分析結果を算出するということも徐々にできるようになってきています。
- 従来の閾値ベースでは評価しづらかった状況の判断などにうまくこの辺り応用できると良いかと思います。
- ただ、使いこなすにはそれなりに引数の指定をうまくやらないと意味のない結果になったり、結果の解釈の仕方なども正しく理解した上で使う必要があるのでそれなりにハードルが高そうなので、注意が必要かと思います。

# その他参考情報

- [Zabbix公式 関数のリファレンス](https://www.zabbix.com/documentation/current/en/manual/appendix/functions/history)
- [Zabbix Summit 2021での紹介資料](https://assets.zabbix.com/files/events/zabbix_summit_2021/slides/Alex_Kalimulin_Machine_learning_baseline.pdf)