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監視データの価値を最大化し、迅速な意思決定を支援する重要な要素です。

重要ポイント

  1. 適切なグラフタイプ選択: データの性質に応じた最適な表現方法
  2. パフォーマンス最適化: 大量データの効率的な可視化
  3. ユーザビリティ重視: 直感的で理解しやすいデザイン
  4. 運用効率化: 自動化とメンテナンス性の向上

次のステップ

次章では、ネットワークマップ機能について詳しく解説し、インフラストラクチャの物理的・論理的構造を視覚化する手法を学習します。


関連リンク