6.2 グラフとチャート
時系列データの効果的可視化と詳細分析を実現するZabbixグラフ・チャート機能の総合活用法
概要
グラフとチャートは、Zabbixで収集した時系列データを視覚的に表現し、トレンド分析や異常検知、容量計画を支援する重要な機能です。適切なグラフ設計により、複雑なデータパターンを直感的に理解し、迅速な意思決定を可能にします。
グラフ機能の価値
要素 | 効果 | 適用場面 |
---|---|---|
トレンド可視化 | 時系列パターンの把握 | 容量計画・性能分析 |
異常検知 | 正常値からの逸脱発見 | 障害予兆・問題特定 |
比較分析 | 複数メトリクスの関係性 | 相関分析・原因究明 |
レポート | 定期的な状況報告 | 管理層報告・文書化 |
予測分析 | 将来予測の基盤 | リソース計画・予算策定 |
基本的なグラフ作成
グラフの種類と特徴
単一アイテムグラフ
yaml
# 基本設定
グラフ名: "CPU utilization - Web Server 01"
アイテム: system.cpu.util[,user]
グラフタイプ: "線グラフ"
# 表示設定
Y軸:
最小値: 0
最大値: 100
単位: "%"
ラベル: "CPU使用率"
X軸:
期間: 24時間
グリッド: 有効
ラベル: "時刻"
# スタイル設定
線の設定:
色: "#0066CC"
太さ: 2
スタイル: "実線"
塗りつぶし: "無効"
複数アイテムグラフ
yaml
# メモリ使用量総合グラフ
グラフ名: "Memory Usage Overview"
アイテム構成:
- アイテム1:
名前: vm.memory.size[total]
表示名: "総メモリ"
色: "#4CAF50"
線タイプ: "線"
Y軸: "左"
- アイテム2:
名前: vm.memory.size[used]
表示名: "使用メモリ"
色: "#FF9800"
線タイプ: "線"
Y軸: "左"
- アイテム3:
名前: vm.memory.utilization
表示名: "使用率"
色: "#F44336"
線タイプ: "線"
Y軸: "右"
# Y軸設定
左Y軸:
単位: "B"
最小値: 0
最大値: "自動"
ラベル: "メモリ量"
右Y軸:
単位: "%"
最小値: 0
最大値: 100
ラベル: "使用率"
グラフタイプの選択
線グラフ(Line)
yaml
適用場面:
- 連続的な時系列データ
- トレンド分析
- パフォーマンス監視
- 容量使用状況
設定例:
グラフタイプ: "線"
用途: CPU使用率、メモリ使用率、ネットワークトラフィック
利点:
- トレンドが分かりやすい
- 複数系列の比較が容易
- 標準的で理解しやすい
- パフォーマンス良好
注意点:
- データポイントが多いと見づらい
- 瞬間的な変化が見えにくい場合
エリアグラフ(Filled area)
yaml
適用場面:
- 累積データの表示
- 全体に対する割合
- ボリューム感の表現
- スタック表示
設定例:
グラフタイプ: "塗りつぶし線"
用途: ディスク使用量、メモリ使用状況、ネットワーク帯域
利点:
- 量的な感覚が掴みやすい
- 全体に対する比率が分かる
- 視覚的インパクトが大きい
- 積み重ね表示に適している
注意点:
- 重なりで隠れるデータがある
- 色選択が重要
- パフォーマンスが重い場合
ステップライン(Stepped line)
yaml
適用場面:
- 離散的な状態変化
- ON/OFF状態
- 設定値の変更履歴
- イベントベースデータ
設定例:
グラフタイプ: "ステップ線"
用途: サービス稼働状態、エラーカウント、設定値
利点:
- 状態変化が明確
- 離散データに適している
- 変化のタイミングが分かる
- デジタル信号表現に最適
注意点:
- 連続データには不適切
- 滑らかなトレンドが見えない
高度なグラフ設定
複合グラフ(Y軸)
異なるスケールの組み合わせ
yaml
# サーバーパフォーマンス複合グラフ
グラフ名: "Server Performance Overview"
# 左Y軸グループ(絶対値)
左Y軸アイテム:
- CPU使用率 (%)
色: "#2196F3"
最小値: 0
最大値: 100
- メモリ使用率 (%)
色: "#4CAF50"
最小値: 0
最大値: 100
# 右Y軸グループ(カウント値)
右Y軸アイテム:
- プロセス数
色: "#FF9800"
最小値: 0
最大値: "自動"
- 接続数
色: "#9C27B0"
最小値: 0
最大値: "自動"
# 軸設定
Y軸設定:
左軸:
単位: "%"
グリッド: "主要"
ラベル: "使用率"
色: "#2196F3"
右軸:
単位: ""
グリッド: "補助"
ラベル: "数"
色: "#FF9800"
時間軸カスタマイズ
柔軟な時間範囲設定
yaml
# 動的時間範囲設定
時間範囲パターン:
リアルタイム監視:
期間: "過去1時間"
更新間隔: "30秒"
自動更新: "有効"
日次分析:
期間: "過去24時間"
更新間隔: "5分"
自動更新: "有効"
週次レビュー:
期間: "過去7日"
更新間隔: "30分"
自動更新: "無効"
月次レポート:
期間: "過去30日"
更新間隔: "1時間"
自動更新: "無効"
# カスタム時間範囲
カスタム設定:
開始時刻: "now-6h" # 6時間前から
終了時刻: "now" # 現在まで
特定期間:
開始: "2024-01-01 00:00:00"
終了: "2024-01-31 23:59:59"
相対指定:
期間: "24h" # 24時間
シフト: "-1d" # 1日前
データ集約とサンプリング
大量データの効率的表示
yaml
# データ集約設定
集約方法:
平均値(AVG):
用途: 一般的なトレンド表示
適用: CPU使用率、メモリ使用率
設定: aggregation_function = "avg"
最大値(MAX):
用途: ピーク値の把握
適用: レスポンス時間、負荷
設定: aggregation_function = "max"
最小値(MIN):
用途: ボトム値の把握
適用: 空き容量、最小接続数
設定: aggregation_function = "min"
合計値(SUM):
用途: 累積値の表示
適用: トランザクション数、バイト数
設定: aggregation_function = "sum"
# サンプリング間隔
間隔設定:
1時間表示: "1分間隔"
24時間表示: "5分間隔"
1週間表示: "30分間隔"
1ヶ月表示: "2時間間隔"
1年表示: "1日間隔"
カスタムグラフ作成
APIを活用したグラフ生成
プログラマティックグラフ作成
python
#!/usr/bin/env python3
"""Zabbix カスタムグラフ生成スクリプト"""
import json
import requests
from typing import List, Dict, Any
from datetime import datetime, timedelta
class ZabbixGraphGenerator:
def __init__(self, zabbix_url: str, auth_token: str):
self.zabbix_url = zabbix_url
self.auth_token = auth_token
def create_performance_graph(self, host_name: str, metrics: List[Dict[str, Any]]) -> str:
"""パフォーマンス監視グラフ作成"""
# ホストID取得
host_data = {
"jsonrpc": "2.0",
"method": "host.get",
"params": {
"filter": {"host": [host_name]},
"output": ["hostid"]
},
"auth": self.auth_token,
"id": 1
}
response = requests.post(self.zabbix_url, json=host_data)
host_id = response.json()["result"][0]["hostid"]
# アイテム情報取得
graph_items = []
for idx, metric in enumerate(metrics):
item_data = {
"jsonrpc": "2.0",
"method": "item.get",
"params": {
"hostids": [host_id],
"search": {"key_": metric["key"]},
"output": ["itemid", "name", "units"]
},
"auth": self.auth_token,
"id": 1
}
response = requests.post(self.zabbix_url, json=item_data)
item = response.json()["result"][0]
graph_items.append({
"itemid": item["itemid"],
"color": metric.get("color", self._get_default_color(idx)),
"drawtype": metric.get("drawtype", 0), # 0=line
"sortorder": idx,
"yaxisside": metric.get("yaxisside", 0), # 0=left, 1=right
"calc_fnc": metric.get("calc_fnc", 2) # 2=avg
})
# グラフ作成
graph_data = {
"jsonrpc": "2.0",
"method": "graph.create",
"params": {
"name": f"Performance Overview - {host_name}",
"width": 900,
"height": 200,
"ymin_type": 0, # 0=calculated
"ymax_type": 0,
"show_work_period": 1,
"show_triggers": 1,
"graphtype": 0, # 0=normal
"show_legend": 1,
"show_3d": 0,
"percent_left": 0,
"percent_right": 0,
"ymin_itemid": 0,
"ymax_itemid": 0,
"gitems": graph_items
},
"auth": self.auth_token,
"id": 1
}
response = requests.post(self.zabbix_url, json=graph_data)
result = response.json()
if "result" in result:
return result["result"]["graphids"][0]
else:
raise Exception(f"Graph creation failed: {result}")
def create_stacked_graph(self, host_name: str, title: str, items: List[str]) -> str:
"""積み重ねグラフ作成"""
# ホストID取得(省略...上記と同様)
graph_items = []
colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FECA57"]
for idx, item_key in enumerate(items):
# アイテムID取得(省略...)
graph_items.append({
"itemid": item_id,
"color": colors[idx % len(colors)],
"drawtype": 2, # 2=filled area
"sortorder": idx,
"yaxisside": 0,
"calc_fnc": 2
})
# 積み重ねグラフ設定
graph_data = {
"jsonrpc": "2.0",
"method": "graph.create",
"params": {
"name": title,
"width": 900,
"height": 200,
"graphtype": 1, # 1=stacked
"show_legend": 1,
"percent_left": 95.0, # 95%線表示
"gitems": graph_items
},
"auth": self.auth_token,
"id": 1
}
response = requests.post(self.zabbix_url, json=graph_data)
return response.json()["result"]["graphids"][0]
def _get_default_color(self, index: int) -> str:
"""デフォルト色セット"""
colors = [
"#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
"#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"
]
return colors[index % len(colors)]
def update_graph_threshold_lines(self, graph_id: str, thresholds: List[Dict[str, Any]]):
"""グラフに閾値線追加"""
# 現在のグラフ設定取得
get_data = {
"jsonrpc": "2.0",
"method": "graph.get",
"params": {
"graphids": [graph_id],
"selectGraphItems": "extend"
},
"auth": self.auth_token,
"id": 1
}
response = requests.post(self.zabbix_url, json=get_data)
graph = response.json()["result"][0]
# 閾値線アイテム追加
for threshold in thresholds:
# 仮想アイテム(閾値用)作成は複雑なため、
# 実際の実装では既存のアイテムを使用するか、
# 計算アイテムを活用することを推奨
pass
return graph_id
# 使用例
def main():
generator = ZabbixGraphGenerator(
zabbix_url="https://zabbix.example.com/api_jsonrpc.php",
auth_token="auth_token_here"
)
# パフォーマンスグラフ作成
performance_metrics = [
{
"key": "system.cpu.util",
"color": "FF6B6B",
"yaxisside": 0
},
{
"key": "vm.memory.utilization",
"color": "4ECDC4",
"yaxisside": 0
},
{
"key": "system.cpu.load[percpu,avg5]",
"color": "45B7D1",
"yaxisside": 1
}
]
graph_id = generator.create_performance_graph("web-server-01", performance_metrics)
print(f"Created performance graph: {graph_id}")
# 積み重ねグラフ作成
memory_items = [
"vm.memory.size[used]",
"vm.memory.size[buffers]",
"vm.memory.size[cached]"
]
stack_graph_id = generator.create_stacked_graph(
"web-server-01",
"Memory Usage Breakdown",
memory_items
)
print(f"Created stacked graph: {stack_graph_id}")
if __name__ == "__main__":
main()
動的グラフ生成
リアルタイム応答グラフ
javascript
// 動的グラフ更新クラス
class DynamicZabbixGraph {
constructor(containerId, graphConfig) {
this.container = document.getElementById(containerId);
this.config = graphConfig;
this.updateInterval = graphConfig.updateInterval || 30000; // 30秒
this.dataPoints = [];
this.maxDataPoints = graphConfig.maxDataPoints || 100;
this.initGraph();
this.startAutoUpdate();
}
initGraph() {
// Chart.js または D3.js を使用したグラフ初期化
this.chart = new Chart(this.container, {
type: 'line',
data: {
labels: [],
datasets: this.config.datasets.map(dataset => ({
label: dataset.name,
data: [],
borderColor: dataset.color,
backgroundColor: dataset.fillColor || 'transparent',
tension: 0.1
}))
},
options: {
responsive: true,
scales: {
x: {
type: 'time',
time: {
displayFormats: {
minute: 'HH:mm',
hour: 'HH:mm'
}
}
},
y: {
beginAtZero: true,
max: this.config.yMax
}
},
plugins: {
zoom: {
pan: {
enabled: true,
mode: 'x'
},
zoom: {
wheel: {
enabled: true
},
mode: 'x'
}
}
}
}
});
}
async fetchLatestData() {
try {
const response = await fetch('/api/zabbix/latest-data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
items: this.config.datasets.map(d => d.itemId),
period: this.config.period || 3600
})
});
const data = await response.json();
return data;
} catch (error) {
console.error('データ取得エラー:', error);
return null;
}
}
updateGraph(newData) {
const now = new Date();
// データポイント追加
this.config.datasets.forEach((dataset, index) => {
const value = newData[dataset.itemId];
if (value !== undefined) {
this.chart.data.datasets[index].data.push({
x: now,
y: value
});
// 最大データポイント数制限
if (this.chart.data.datasets[index].data.length > this.maxDataPoints) {
this.chart.data.datasets[index].data.shift();
}
}
});
// ラベル更新
this.chart.data.labels.push(now);
if (this.chart.data.labels.length > this.maxDataPoints) {
this.chart.data.labels.shift();
}
this.chart.update('none'); // アニメーション無しで更新
}
startAutoUpdate() {
setInterval(async () => {
const data = await this.fetchLatestData();
if (data) {
this.updateGraph(data);
}
}, this.updateInterval);
}
// 閾値線追加
addThresholdLine(value, color = '#FF0000', label = 'Threshold') {
this.chart.options.plugins.annotation = {
annotations: {
threshold: {
type: 'line',
yMin: value,
yMax: value,
borderColor: color,
borderWidth: 2,
label: {
content: label,
enabled: true,
position: 'end'
}
}
}
};
this.chart.update();
}
// 異常検知アラート
checkAnomalies(data) {
this.config.datasets.forEach(dataset => {
const value = data[dataset.itemId];
const threshold = dataset.threshold;
if (threshold && value > threshold.critical) {
this.showAlert(`${dataset.name} が危険閾値を超過: ${value}`, 'danger');
} else if (threshold && value > threshold.warning) {
this.showAlert(`${dataset.name} が警告閾値を超過: ${value}`, 'warning');
}
});
}
showAlert(message, type) {
// アラート表示ロジック
const alert = document.createElement('div');
alert.className = `alert alert-${type}`;
alert.textContent = message;
document.body.appendChild(alert);
setTimeout(() => {
alert.remove();
}, 5000);
}
}
// 使用例
const graphConfig = {
updateInterval: 30000,
maxDataPoints: 200,
yMax: 100,
datasets: [
{
name: 'CPU使用率',
itemId: 'system.cpu.util',
color: '#FF6B6B',
threshold: {
warning: 80,
critical: 90
}
},
{
name: 'メモリ使用率',
itemId: 'vm.memory.utilization',
color: '#4ECDC4',
threshold: {
warning: 85,
critical: 95
}
}
]
};
const dynamicGraph = new DynamicZabbixGraph('graph-container', graphConfig);
複合グラフ
相関分析グラフ
複数メトリクス関係性可視化
yaml
# CPU vs メモリ相関グラフ
相関グラフ設定:
タイトル: "CPU vs Memory Correlation"
タイプ: "散布図"
X軸:
アイテム: system.cpu.util
ラベル: "CPU使用率 (%)"
範囲: 0-100
Y軸:
アイテム: vm.memory.utilization
ラベル: "メモリ使用率 (%)"
範囲: 0-100
ポイント設定:
色: 時刻に基づくグラデーション
サイズ: 固定
透明度: 70%
トレンドライン:
表示: 有効
タイプ: 線形回帰
信頼区間: 95%
マルチホスト比較グラフ
クラスター全体パフォーマンス
python
#!/usr/bin/env python3
"""マルチホスト比較グラフ生成"""
def create_cluster_comparison_graph(hosts: List[str], metric_key: str):
"""クラスター比較グラフ作成"""
graph_config = {
"name": f"Cluster {metric_key} Comparison",
"width": 1200,
"height": 400,
"show_legend": 1,
"gitems": []
}
colors = [
"#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FECA57",
"#FF8E7B", "#6C5CE7", "#A29BFE", "#FD79A8", "#FDCB6E"
]
for idx, host in enumerate(hosts):
# ホスト別アイテム追加
graph_config["gitems"].append({
"itemid": get_item_id(host, metric_key),
"color": colors[idx % len(colors)],
"drawtype": 0, # 線グラフ
"sortorder": idx,
"yaxisside": 0,
"calc_fnc": 2 # 平均
})
return create_graph(graph_config)
def create_percentile_graph(hosts: List[str], metric_key: str):
"""パーセンタイルグラフ作成"""
# 各ホストのデータを取得してパーセンタイル計算
all_data = []
for host in hosts:
data = get_historical_data(host, metric_key, period="24h")
all_data.extend(data)
# パーセンタイル計算
percentiles = {
"P50": numpy.percentile(all_data, 50),
"P75": numpy.percentile(all_data, 75),
"P90": numpy.percentile(all_data, 90),
"P95": numpy.percentile(all_data, 95),
"P99": numpy.percentile(all_data, 99)
}
# パーセンタイル線をグラフに追加
graph_config = {
"name": f"Cluster {metric_key} Percentiles",
"annotations": []
}
for label, value in percentiles.items():
graph_config["annotations"].append({
"type": "line",
"yMin": value,
"yMax": value,
"borderColor": get_percentile_color(label),
"label": {
"content": f"{label}: {value:.2f}",
"enabled": True
}
})
return graph_config
グラフエクスポート
画像エクスポート
高品質レポート用画像生成
python
#!/usr/bin/env python3
"""Zabbixグラフ画像エクスポート"""
import requests
import base64
from datetime import datetime, timedelta
class ZabbixGraphExporter:
def __init__(self, zabbix_url: str, username: str, password: str):
self.base_url = zabbix_url.replace('/api_jsonrpc.php', '')
self.session = requests.Session()
self.login(username, password)
def login(self, username: str, password: str):
"""Webインターフェースログイン"""
login_data = {
'name': username,
'password': password,
'enter': 'Sign in'
}
response = self.session.post(
f"{self.base_url}/index.php",
data=login_data
)
if "Dashboard" not in response.text:
raise Exception("Login failed")
def export_graph_image(self, graph_id: str, width: int = 900, height: int = 200,
period: int = 3600, output_format: str = 'PNG') -> bytes:
"""グラフ画像エクスポート"""
# 時間範囲設定
to_time = datetime.now()
from_time = to_time - timedelta(seconds=period)
params = {
'graphid': graph_id,
'width': width,
'height': height,
'from': int(from_time.timestamp()),
'to': int(to_time.timestamp()),
'profileIdx': 'web.graphs.filter',
'profileIdx2': graph_id
}
response = self.session.get(
f"{self.base_url}/chart2.php",
params=params
)
return response.content
def export_custom_graph(self, items: List[str], title: str,
width: int = 900, height: int = 200,
period: int = 3600) -> bytes:
"""カスタムグラフ画像生成"""
# アイテムIDをカンマ区切りで連結
itemids = ','.join(items)
params = {
'itemids[0]': itemids,
'type': 0, # 線グラフ
'period': period,
'width': width,
'height': height,
'name': title,
'legend': 1,
'graph_type': 'png'
}
response = self.session.get(
f"{self.base_url}/chart.php",
params=params
)
return response.content
def create_dashboard_snapshot(self, dashboard_id: str,
width: int = 1920, height: int = 1080) -> bytes:
"""ダッシュボードスナップショット作成"""
# Selenium WebDriverを使用してブラウザ制御
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument(f'--window-size={width},{height}')
chrome_options.add_argument('--disable-gpu')
driver = webdriver.Chrome(options=chrome_options)
try:
# ダッシュボードページにアクセス
dashboard_url = f"{self.base_url}/zabbix.php?action=dashboard.view&dashboardid={dashboard_id}"
driver.get(dashboard_url)
# ログイン(必要に応じて)
# ...
# スクリーンショット撮影
screenshot = driver.get_screenshot_as_png()
return screenshot
finally:
driver.quit()
def generate_report_images(self, report_config: Dict[str, Any]) -> Dict[str, bytes]:
"""レポート用画像一括生成"""
images = {}
for section in report_config['sections']:
if section['type'] == 'graph':
image_data = self.export_graph_image(
graph_id=section['graph_id'],
width=section.get('width', 900),
height=section.get('height', 200),
period=section.get('period', 3600)
)
images[section['name']] = image_data
elif section['type'] == 'custom':
image_data = self.export_custom_graph(
items=section['items'],
title=section['title'],
width=section.get('width', 900),
height=section.get('height', 200),
period=section.get('period', 3600)
)
images[section['name']] = image_data
return images
# 使用例
def main():
exporter = ZabbixGraphExporter(
zabbix_url="https://zabbix.example.com",
username="admin",
password="password"
)
# 単一グラフエクスポート
graph_image = exporter.export_graph_image(
graph_id="123",
width=1200,
height=300,
period=86400 # 24時間
)
with open("performance_graph.png", "wb") as f:
f.write(graph_image)
# レポート用画像一括生成
report_config = {
'sections': [
{
'type': 'graph',
'name': 'cpu_usage',
'graph_id': '123',
'width': 900,
'height': 200,
'period': 86400
},
{
'type': 'custom',
'name': 'memory_overview',
'items': ['item1', 'item2', 'item3'],
'title': 'Memory Usage Overview',
'width': 900,
'height': 200,
'period': 86400
}
]
}
images = exporter.generate_report_images(report_config)
for name, image_data in images.items():
with open(f"report_{name}.png", "wb") as f:
f.write(image_data)
if __name__ == "__main__":
main()
データエクスポート
CSV形式データ出力
python
#!/usr/bin/env python3
"""グラフデータCSVエクスポート"""
import csv
import json
from datetime import datetime, timedelta
class ZabbixDataExporter:
def __init__(self, zabbix_api):
self.api = zabbix_api
def export_graph_data_csv(self, graph_id: str, period_hours: int = 24,
output_file: str = None) -> str:
"""グラフデータをCSV形式でエクスポート"""
# グラフ情報取得
graph_data = self.api.graph.get(
graphids=[graph_id],
selectGraphItems="extend"
)[0]
# 時間範囲設定
to_time = datetime.now()
from_time = to_time - timedelta(hours=period_hours)
# アイテムデータ取得
csv_data = []
headers = ['timestamp']
for graph_item in graph_data['gitems']:
item_id = graph_item['itemid']
# アイテム情報取得
item_info = self.api.item.get(
itemids=[item_id],
output=['name', 'units']
)[0]
headers.append(f"{item_info['name']} ({item_info['units']})")
# 履歴データ取得
history_data = self.api.history.get(
itemids=[item_id],
time_from=int(from_time.timestamp()),
time_till=int(to_time.timestamp()),
sortfield='clock',
sortorder='ASC'
)
# データポイント追加
for point in history_data:
timestamp = datetime.fromtimestamp(int(point['clock']))
# 既存の行を検索または新規作成
row_found = False
for row in csv_data:
if row[0] == timestamp:
row.append(float(point['value']))
row_found = True
break
if not row_found:
new_row = [timestamp] + [None] * (len(headers) - 1)
new_row[len(headers) - 1] = float(point['value'])
csv_data.append(new_row)
# 時刻でソート
csv_data.sort(key=lambda x: x[0])
# CSV出力
if not output_file:
output_file = f"graph_{graph_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
with open(output_file, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(headers)
for row in csv_data:
formatted_row = [row[0].strftime('%Y-%m-%d %H:%M:%S')] + row[1:]
writer.writerow(formatted_row)
return output_file
def export_multi_host_comparison(self, hosts: List[str], metric_key: str,
period_hours: int = 24) -> str:
"""マルチホスト比較データエクスポート"""
# ホスト情報取得
host_data = self.api.host.get(
filter={'host': hosts},
output=['hostid', 'host', 'name']
)
# 時間範囲設定
to_time = datetime.now()
from_time = to_time - timedelta(hours=period_hours)
csv_data = []
headers = ['timestamp'] + [f"{h['name']} ({h['host']})" for h in host_data]
# 各ホストのデータ取得
for host in host_data:
items = self.api.item.get(
hostids=[host['hostid']],
search={'key_': metric_key},
output=['itemid']
)
if not items:
continue
item_id = items[0]['itemid']
history_data = self.api.history.get(
itemids=[item_id],
time_from=int(from_time.timestamp()),
time_till=int(to_time.timestamp()),
sortfield='clock',
sortorder='ASC'
)
# データ統合
for point in history_data:
timestamp = datetime.fromtimestamp(int(point['clock']))
# 既存の行を検索
row_found = False
for row in csv_data:
if row[0] == timestamp:
# ホストのインデックス計算
host_index = next(i for i, h in enumerate(host_data) if h['hostid'] == host['hostid'])
row[host_index + 1] = float(point['value'])
row_found = True
break
if not row_found:
new_row = [timestamp] + [None] * len(host_data)
host_index = next(i for i, h in enumerate(host_data) if h['hostid'] == host['hostid'])
new_row[host_index + 1] = float(point['value'])
csv_data.append(new_row)
# CSV出力
output_file = f"multi_host_{metric_key}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
with open(output_file, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(headers)
# 時刻でソート
csv_data.sort(key=lambda x: x[0])
for row in csv_data:
formatted_row = [row[0].strftime('%Y-%m-%d %H:%M:%S')] + row[1:]
writer.writerow(formatted_row)
return output_file
パフォーマンス最適化
レンダリング最適化
大量データの効率的表示
yaml
# データ量最適化
最適化手法:
サンプリング:
大量データポイント: "間引き処理"
表示期間調整: "適切な粒度選択"
リアルタイム制限: "更新頻度制御"
キャッシュ活用:
ブラウザキャッシュ: "画像・CSS・JS"
アプリケーションキャッシュ: "グラフデータ"
CDN活用: "静的リソース配信"
遅延読み込み:
表示領域外: "オンデマンド読み込み"
タブ切り替え: "アクティブ時のみ更新"
スクロール連動: "表示時読み込み"
# レンダリング設定
描画最適化:
Canvas使用: "大量ポイント時"
SVG使用: "少量ポイント時"
WebGL: "3D・高度な可視化"
ハードウェア加速: "GPU活用"
メモリ使用量管理
ブラウザリソース最適化
javascript
// グラフメモリ管理クラス
class GraphMemoryManager {
constructor() {
this.activeGraphs = new Map();
this.memoryThreshold = 500 * 1024 * 1024; // 500MB
this.checkInterval = 60000; // 1分
this.startMemoryMonitoring();
}
registerGraph(graphId, graphInstance) {
this.activeGraphs.set(graphId, {
instance: graphInstance,
lastAccess: Date.now(),
dataPoints: 0
});
}
unregisterGraph(graphId) {
const graph = this.activeGraphs.get(graphId);
if (graph && graph.instance.destroy) {
graph.instance.destroy();
}
this.activeGraphs.delete(graphId);
}
startMemoryMonitoring() {
setInterval(() => {
this.checkMemoryUsage();
}, this.checkInterval);
}
checkMemoryUsage() {
if (!performance.memory) return;
const memoryUsage = performance.memory.usedJSHeapSize;
if (memoryUsage > this.memoryThreshold) {
this.cleanupInactiveGraphs();
}
// メモリ使用状況ログ
console.log('Memory status:', {
used: Math.round(memoryUsage / 1024 / 1024) + 'MB',
activeGraphs: this.activeGraphs.size,
threshold: Math.round(this.memoryThreshold / 1024 / 1024) + 'MB'
});
}
cleanupInactiveGraphs() {
const now = Date.now();
const inactiveThreshold = 300000; // 5分
for (const [graphId, graph] of this.activeGraphs.entries()) {
const inactiveTime = now - graph.lastAccess;
if (inactiveTime > inactiveThreshold) {
console.log(`Cleaning up inactive graph: ${graphId}`);
this.unregisterGraph(graphId);
}
}
// ガベージコレクション強制実行(可能であれば)
if (window.gc) {
window.gc();
}
}
updateLastAccess(graphId) {
const graph = this.activeGraphs.get(graphId);
if (graph) {
graph.lastAccess = Date.now();
}
}
}
// グローバルメモリマネージャー
const graphMemoryManager = new GraphMemoryManager();
// 使用例
class OptimizedGraph {
constructor(containerId, config) {
this.containerId = containerId;
this.config = config;
this.chart = null;
this.dataBuffer = [];
this.maxBufferSize = config.maxDataPoints || 1000;
this.init();
graphMemoryManager.registerGraph(containerId, this);
}
init() {
// Chart.js初期化(最適化設定付き)
const ctx = document.getElementById(this.containerId);
this.chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: this.config.datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0 // アニメーション無効化
},
elements: {
point: {
radius: 0 // ポイント非表示で軽量化
}
},
plugins: {
legend: {
display: true
},
tooltip: {
enabled: false // ツールチップ無効化
}
},
scales: {
x: {
type: 'time',
time: {
displayFormats: {
minute: 'HH:mm'
}
}
}
},
// パフォーマンス最適化
parsing: false,
normalized: true,
spanGaps: true
}
});
}
addDataPoint(timestamp, values) {
// アクセス時刻更新
graphMemoryManager.updateLastAccess(this.containerId);
// データバッファに追加
this.dataBuffer.push({timestamp, values});
// バッファサイズ制限
if (this.dataBuffer.length > this.maxBufferSize) {
this.dataBuffer.shift();
}
// バッチ更新(複数ポイントをまとめて処理)
this.batchUpdate();
}
batchUpdate() {
// データセット更新
this.chart.data.labels = this.dataBuffer.map(d => d.timestamp);
this.config.datasets.forEach((dataset, index) => {
this.chart.data.datasets[index].data = this.dataBuffer.map(d => ({
x: d.timestamp,
y: d.values[index]
}));
});
this.chart.update('none'); // アニメーション無し
}
destroy() {
if (this.chart) {
this.chart.destroy();
this.chart = null;
}
this.dataBuffer = [];
graphMemoryManager.unregisterGraph(this.containerId);
}
}
ベストプラクティス
設計原則
効果的なグラフ設計
yaml
# 視覚的設計原則
デザインガイドライン:
色の使い方:
基本色: "青系(正常)、赤系(警告・エラー)"
アクセント色: "オレンジ、緑系"
背景色: "白または淡いグレー"
コントラスト: "十分な色差確保"
線の太さ:
重要データ: "2-3px"
補助データ: "1px"
閾値線: "1px破線"
平均線: "2px点線"
レイアウト:
マージン: "適切な余白確保"
凡例: "右上または下部"
軸ラベル: "明確で簡潔"
タイトル: "内容を的確に表現"
# データ表現原則
データ設計:
時間軸:
適切な粒度: "表示期間に応じた間隔"
時刻表示: "24時間形式推奨"
タイムゾーン: "明示的な指定"
Y軸:
ゼロ起点: "率・使用量の場合"
自動スケール: "変動が大きい場合"
固定スケール: "比較が重要な場合"
複数軸: "単位が異なる場合のみ"
運用上の考慮事項
パフォーマンス監視
yaml
# グラフパフォーマンス監視
監視項目:
レンダリング時間:
目標: "< 2秒"
測定: "Performance API"
アラート: "> 5秒で警告"
メモリ使用量:
目標: "< 500MB"
測定: "performance.memory"
アラート: "> 1GB で警告"
データ転送量:
目標: "< 10MB/分"
測定: "Network API"
最適化: "データ圧縮・キャッシュ"
ユーザー体験:
応答性: "インタラクション < 100ms"
流暢性: "60fps維持"
安定性: "エラー率 < 1%"
まとめ
効果的なグラフとチャートは、Zabbix監視データの価値を最大化し、迅速な意思決定を支援する重要な要素です。
重要ポイント
- 適切なグラフタイプ選択: データの性質に応じた最適な表現方法
- パフォーマンス最適化: 大量データの効率的な可視化
- ユーザビリティ重視: 直感的で理解しやすいデザイン
- 運用効率化: 自動化とメンテナンス性の向上
次のステップ
次章では、ネットワークマップ機能について詳しく解説し、インフラストラクチャの物理的・論理的構造を視覚化する手法を学習します。