導入 — 購読者へ向けた結論

WebSocket 経由の AI 流式応答(Streaming Response)を実装する際、接続切断・タイムアウト・順序保証などの問題を可視化・監視するツール選定は、Production 環境の信頼性に直結します。本稿では HolySheep AI を始めとする主要 API を提供する7サービスを比較し、接続状態可視化の実装コードとよくあるエラー対処法を体系的に解説します。

結論:開発コスト重視なら HolySheep AI の ¥1=$1 レート( 공식 ¥7.3=$1 比 85% 節約)と <50ms レイテンシが最適。企業要件で OpenAI 互換性が必須の場合は API 直契約を検討してください。

主要 API サービスの比較

サービス名 料金($1 辺り) レイテンシ 決済手段 対応モデル WebSocket 対応 最適なチーム
HolySheep AI ¥1 = $1(85% 節約) <50ms WeChat Pay / Alipay / クレジットカード GPT-4.1, Claude Sonnet 4.5, Gemini 2.5 Flash, DeepSeek V3.2 ✅ SSE + WebSocket コスト重視のスタートアップ・個人開発者
OpenAI 公式 $1 = ¥7.3(基準) 80-150ms クレジットカード/API キー GPT-4o, GPT-4o-mini, o1 ✅ Server-Sent Events Enterprise・OpenAI 必須要件
Anthropic 公式 $1 = ¥7.3 100-200ms クレジットカード Claude 3.5 Sonnet, Claude 3 Opus ✅ SSE 高品質な文章生成が必要なチーム
Google AI Studio $1 = ¥7.3 60-120ms クレジットカード Gemini 1.5 Pro, Gemini 2.0 Flash ✅ SSE + REST Google エコシステム利用者
DeepSeek 公式 $1 ≈ ¥7.3 150-300ms クレジットカード DeepSeek V3, DeepSeek Coder ✅ SSE 中国語対応が必要な開発者
Azure OpenAI $1 ≈ ¥7.3 + 管理費 100-180ms Enterprise 契約 GPT-4o, GPT-4 Turbo ✅ SSE 大企業・規制業界
AWS Bedrock $1 ≈ ¥7.3 + リクエスト料 120-250ms AWS 請求 Claude, Titan, Llama ✅ SSE AWS 既存インフラを持つチーム

2026年 出力トークン単価比較($ / MTok)

モデル 入力価格 出力価格 備考
GPT-4.1 $2.50/MTok $8/MTok OpenAI 公式
Claude Sonnet 4.5 $3/MTok $15/MTok Anthropic 公式
Gemini 2.5 Flash $0.30/MTok $2.50/MTok Google 公式
DeepSeek V3.2 $0.27/MTok $0.42/MTok 最安値 • HolySheep で ¥1=$1

WebSocket 流式応答アーキテクチャの設計

AI API への WebSocket 接続を実装する際、接続状態の管理・ping/pong 心拍・再接続ロジック・チャンク単位での応答処理を理解する必要があります。私は過去3年間、流式応答のプロダクション環境を構築してきましたが、接続状態可視化を怠ると障害対応に3倍以上の時間を要するケースが頻発しました。

接続状態可視化ダッシュボードの実装

HolySheep AI の API Endpoint https://api.holysheep.ai/v1 を使用して、WebSocket 風の Server-Sent Events(SSE)で流式応答を実装し、接続状態をリアルタイム可視化するダッシュボードを以下に示します。

// client-monitor.html - 接続状態可視化ダッシュボード
// HolySheep AI 流式応答監視ツール

class ConnectionMonitor {
  constructor(apiKey, baseUrl = 'https://api.holysheep.ai/v1') {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
    this.connectionState = 'DISCONNECTED';
    this.latencySamples = [];
    this.messageQueue = [];
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    
    // DOM 要素参照
    this.statusEl = document.getElementById('connection-status');
    this.latencyEl = document.getElementById('latency-value');
    this.messagesEl = document.getElementById('message-stream');
    this.metricsEl = document.getElementById('metrics-panel');
    
    // EventSource は SSE 用。WebSocket 場合は WebSocket API を使用
    this.eventSource = null;
    this.lastPingTime = null;
  }

  // 接続状態_enum
  static State = {
    DISCONNECTED: 'DISCONNECTED',
    CONNECTING: 'CONNECTING',
    CONNECTED: 'CONNECTED',
    STREAMING: 'STREAMING',
    RECONNECTING: 'RECONNECTING',
    ERROR: 'ERROR'
  };

  // 接続開始
  async connect(prompt) {
    this.updateState(ConnectionMonitor.State.CONNECTING);
    const startTime = performance.now();
    
    try {
      // HolySheep AI SSE エンドポイント
      const response = await fetch(${this.baseUrl}/chat/completions, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': Bearer ${this.apiKey}
        },
        body: JSON.stringify({
          model: 'gpt-4.1',
          messages: [{ role: 'user', content: prompt }],
          stream: true  // 流式応答を有効化
        })
      });

      if (!response.ok) {
        throw new Error(HTTP ${response.status}: ${response.statusText});
      }

      const connectionLatency = performance.now() - startTime;
      this.recordLatency(connectionLatency);
      this.updateState(ConnectionMonitor.State.CONNECTED);

      // SSE ストリーム処理
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';
      let messageCount = 0;

      while (true) {
        const { done, value } = await reader.read();
        
        if (done) {
          this.updateState(ConnectionMonitor.State.DISCONNECTED);
          break;
        }

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() || '';

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6);
            if (data === '[DONE]') {
              this.updateState(ConnectionMonitor.State.DISCONNECTED);
              return;
            }
            
            try {
              const json = JSON.parse(data);
              const chunkLatency = performance.now() - startTime;
              this.processChunk(json, chunkLatency);
              messageCount++;
              this.updateMetrics(messageCount, chunkLatency);
            } catch (e) {
              // JSON パースエラーは無視(途中のデータ)
            }
          }
        }
      }
    } catch (error) {
      console.error('Connection error:', error);
      this.handleError(error);
    }
  }

  // チャンク処理
  processChunk(data, timestamp) {
    const delta = data.choices?.[0]?.delta?.content || '';
    if (delta) {
      this.messageQueue.push({ delta, timestamp });
      this.renderMessage(delta);
    }
  }

  // レイテンシ記録
  recordLatency(ms) {
    this.latencySamples.push(ms);
    if (this.latencySamples.length > 100) {
      this.latencySamples.shift();
    }
    const avg = this.latencySamples.reduce((a, b) => a + b, 0) / this.latencySamples.length;
    this.latencyEl.textContent = ${avg.toFixed(2)}ms;
    this.latencyEl.style.color = avg < 50 ? '#22c55e' : avg < 100 ? '#eab308' : '#ef4444';
  }

  // 状態更新
  updateState(newState) {
    const prevState = this.connectionState;
    this.connectionState = newState;
    
    const stateColors = {
      DISCONNECTED: '#6b7280',
      CONNECTING: '#3b82f6',
      CONNECTED: '#22c55e',
      STREAMING: '#10b981',
      RECONNECTING: '#f59e0b',
      ERROR: '#ef4444'
    };

    this.statusEl.textContent = newState;
    this.statusEl.style.backgroundColor = stateColors[newState] || '#6b7280';
    this.statusEl.style.color = 'white';
    this.statusEl.style.padding = '4px 12px';
    this.statusEl.style.borderRadius = '4px';
    
    this.log(State: ${prevState} → ${newState});
  }

  // メトリクス更新
  updateMetrics(messageCount, latestLatency) {
    const successRate = ((messageCount / (this.reconnectAttempts + 1)) * 100).toFixed(1);
    this.metricsEl.innerHTML = `
      <div>メッセージ数: ${messageCount}</div>
      <div>最新レイテンシ: ${latestLatency.toFixed(2)}ms</div>
      <div>平均レイテンシ: ${this.latencySamples.length ? (this.latencySamples.reduce((a,b)=>a+b,0)/this.latencySamples.length).toFixed(2) : 0}ms</div>
      <div>接続状態: ${this.connectionState}</div>
      <div>再接続試行: ${this.reconnectAttempts}</div>
    `;
  }

  // エラーハンドリング
  handleError(error) {
    this.updateState(ConnectionMonitor.State.ERROR);
    this.log(ERROR: ${error.message}, 'error');
    
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      this.updateState(ConnectionMonitor.State.RECONNECTING);
      const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
      this.log(Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}));
      setTimeout(() => this.connect(this.lastPrompt), delay);
    }
  }

  // ログ出力
  log(message, level = 'info') {
    const logEl = document.getElementById('log-panel');
    const timestamp = new Date().toISOString().substr(11, 12);
    const color = level === 'error' ? '#ef4444' : level === 'warn' ? '#f59e0b' : '#3b82f6';
    logEl.innerHTML += <div style="color:${color}">[${timestamp}] ${message}</div>;
    logEl.scrollTop = logEl.scrollHeight;
  }

  // メッセージ描画
  renderMessage(delta) {
    this.messagesEl.textContent += delta;
    this.messagesEl.scrollTop = this.messagesEl.scrollHeight;
  }

  // 切断
  disconnect() {
    if (this.eventSource) {
      this.eventSource.close();
      this.eventSource = null;
    }
    this.updateState(ConnectionMonitor.State.DISCONNECTED);
  }
}

// 使用例
const monitor = new ConnectionMonitor('YOUR_HOLYSHEEP_API_KEY');
monitor.connect('Explain WebSocket connection states in detail');

Node.js バックエンド 接続プール管理

// server-connection-pool.ts - WebSocket 接続プール管理
// HolySheep AI API 向け接続プール実装

import { EventEmitter } from 'events';

// 接続状態型定義
enum ConnectionState {
  IDLE = 'IDLE',
  ACTIVE = 'ACTIVE',
  STREAMING = 'STREAMING',
  CLOSING = 'CLOSING',
  CLOSED = 'CLOSED',
  ERROR = 'ERROR'
}

// 接続情報を保持するインターフェース
interface ConnectionInfo {
  id: string;
  state: ConnectionState;
  createdAt: Date;
  lastActivity: Date;
  messageCount: number;
  totalLatency: number;
  errors: Error[];
}

interface PoolConfig {
  maxConnections: number;
  connectionTimeout: number;
  idleTimeout: number;
  maxReconnectAttempts: number;
}

class HolySheepConnectionPool extends EventEmitter {
  private connections: Map<string, ConnectionInfo> = new Map();
  private connectionQueue: Array<{ resolve: (conn: ConnectionInfo) => void; reject: (err: Error) => void }> = [];
  private baseUrl = 'https://api.holysheep.ai/v1';
  private apiKey: string;
  private config: PoolConfig;
  private healthCheckInterval: NodeJS.Timeout | null = null;

  constructor(apiKey: string, config: Partial<PoolConfig> = {}) {
    super();
    this.apiKey = apiKey;
    this.config = {
      maxConnections: 100,
      connectionTimeout: 30000,
      idleTimeout: 60000,
      maxReconnectAttempts: 5,
      ...config
    };
    this.startHealthCheck();
  }

  // 接続取得(プールから取得または新規作成)
  async acquire(): Promise<ConnectionInfo> {
    // アイドル接続を探す
    for (const [id, conn] of this.connections) {
      if (conn.state === ConnectionState.IDLE) {
        conn.state = ConnectionState.ACTIVE;
        conn.lastActivity = new Date();
        this.emit('connection:acquired', { id, poolSize: this.connections.size });
        return conn;
      }
    }

    // プールに空きがある
    if (this.connections.size < this.config.maxConnections) {
      return this.createConnection();
    }

    // 空きを待つ
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        const index = this.connectionQueue.findIndex(q => q.resolve === resolve);
        if (index !== -1) this.connectionQueue.splice(index, 1);
        reject(new Error('Connection pool timeout'));
      }, this.config.connectionTimeout);

      this.connectionQueue.push({
        resolve: (conn) => {
          clearTimeout(timeout);
          resolve(conn);
        },
        reject: (err) => {
          clearTimeout(timeout);
          reject(err);
        }
      });
    });
  }

  // 新規接続作成
  private async createConnection(): Promise<ConnectionInfo> {
    const id = conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)};
    const conn: ConnectionInfo = {
      id,
      state: ConnectionState.IDLE,
      createdAt: new Date(),
      lastActivity: new Date(),
      messageCount: 0,
      totalLatency: 0,
      errors: []
    };

    this.connections.set(id, conn);
    this.emit('connection:created', { id, poolSize: this.connections.size });
    
    console.log([Pool] Created new connection: ${id} (Total: ${this.connections.size}/${this.config.maxConnections}));
    
    return conn;
  }

  // 接続解放(プールに戻す)
  release(connId: string): void {
    const conn = this.connections.get(connId);
    if (!conn) return;

    conn.state = ConnectionState.IDLE;
    conn.lastActivity = new Date();
    this.emit('connection:released', { id: connId });

    // 待機中のリクエストを処理
    if (this.connectionQueue.length > 0) {
      const request = this.connectionQueue.shift();
      if (request) {
        conn.state = ConnectionState.ACTIVE;
        request.resolve(conn);
      }
    }
  }

  // HolySheep AI API へ SSE リクエスト送信
  async sendStreamRequest(connId: string, prompt: string): Promise<AsyncIterable<string>> {
    const conn = this.connections.get(connId);
    if (!conn) throw new Error(Connection ${connId} not found);

    conn.state = ConnectionState.STREAMING;
    const startTime = Date.now();

    try {
      const response = await fetch(${this.baseUrl}/chat/completions, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': Bearer ${this.apiKey}
        },
        body: JSON.stringify({
          model: 'gpt-4.1',
          messages: [{ role: 'user', content: prompt }],
          stream: true
        })
      });

      if (!response.ok) {
        throw new Error(HolySheep API error: ${response.status});
      }

      const stream = new ReadableStream({
        async start(controller) {
          const reader = response.body!.getReader();
          const decoder = new TextDecoder();

          try {
            while (true) {
              const { done, value } = await reader.read();
              if (done) break;

              const chunk = decoder.decode(value, { stream: true });
              const lines = chunk.split('\n');

              for (const line of lines) {
                if (line.startsWith('data: ')) {
                  const data = line.slice(6);
                  if (data === '[DONE]') {
                    controller.close();
                    return;
                  }
                  try {
                    const json = JSON.parse(data);
                    const content = json.choices?.[0]?.delta?.content || '';
                    if (content) {
                      controller.enqueue(new TextEncoder().encode(content));
                      conn.messageCount++;
                      conn.totalLatency += Date.now() - startTime;
                    }
                  } catch (e) {
                    //途中の JSON はスキップ
                  }
                }
              }
            }
          } finally {
            conn.state = ConnectionState.IDLE;
            conn.lastActivity = new Date();
          }
        }
      });

      return stream;
    } catch (error) {
      conn.state = ConnectionState.ERROR;
      conn.errors.push(error as Error);
      this.emit('connection:error', { id: connId, error });
      throw error;
    }
  }

  // ヘルスチェック
  private startHealthCheck(): void {
    this.healthCheckInterval = setInterval(() => {
      const now = Date.now();
      let idleCount = 0;

      for (const [id, conn] of this.connections) {
        const idleTime = now - conn.lastActivity.getTime();
        
        if (conn.state === ConnectionState.IDLE && idleTime > this.config.idleTimeout) {
          this.connections.delete(id);
          idleCount++;
          this.emit('connection:idle-timeout', { id });
        }
      }

      if (idleCount > 0) {
        console.log([Pool] Cleaned up ${idleCount} idle connections);
      }
    }, 30000);
  }

  // プール統計取得
  getStats() {
    const states = Object.values(ConnectionState);
    const stateCounts = states.reduce((acc, s) => ({ ...acc, [s]: 0 }), {} as Record<ConnectionState, number>);

    for (const conn of this.connections.values()) {
      stateCounts[conn.state]++;
    }

    const avgLatency = Array.from(this.connections.values())
      .filter(c => c.messageCount > 0)
      .reduce((sum, c) => sum + c.totalLatency / c.messageCount, 0);

    return {
      totalConnections: this.connections.size,
      maxConnections: this.config.maxConnections,
      queueLength: this.connectionQueue.length,
      stateDistribution: stateCounts,
      averageLatencyMs: avgLatency.toFixed(2)
    };
  }

  // 全接続閉じる
  async destroy(): Promise<void> {
    if (this.healthCheckInterval) {
      clearInterval(this.healthCheckInterval);
    }
    this.connections.clear();
    this.connectionQueue.forEach(q => q.reject(new Error('Pool destroyed')));
    this.connectionQueue = [];
    this.emit('pool:destroyed');
  }
}

// 使用例
const pool = new HolySheepConnectionPool('YOUR_HOLYSHEEP_API_KEY', {
  maxConnections: 50,
  idleTimeout: 30000
});

// イベント監視
pool.on('connection:created', ({ id, poolSize }) => {
  console.log([Monitor] Connection ${id} created. Pool size: ${poolSize});
});

pool.on('connection:error', ({ id, error }) => {
  console.error([Monitor] Error on ${id}:, error.message);
});

// 統計出力
setInterval(() => {
  const stats = pool.getStats();
  console.log([Stats] ${JSON.stringify(stats, null, 2)});
}, 10000);

// リクエスト処理の例
async function processRequest(prompt: string) {
  const conn = await pool.acquire();
  try {
    const stream = await pool.sendStreamRequest(conn.id, prompt);
    let fullResponse = '';
    
    for await (const chunk of stream) {
      fullResponse += new TextDecoder().decode(chunk);
    }
    
    return fullResponse;
  } finally {
    pool.release(conn.id);
  }
}

export { HolySheepConnectionPool, ConnectionState, ConnectionInfo, PoolConfig };

接続状態の時系列可視化

# connection_timeline.py - 接続状態の時系列可視化

HolySheep AI API 呼び出しの遅延・接続状態監視

import asyncio import json import time from dataclasses import dataclass, field from datetime import datetime from typing import List, Optional, Dict, Any from enum import Enum import matplotlib.pyplot as plt import matplotlib.dates as mdates class ConnectionState(Enum): DISCONNECTED = "disconnected" CONNECTING = "connecting" CONNECTED = "connected" STREAMING = "streaming" COMPLETED = "completed" ERROR = "error" TIMEOUT = "timeout" RATE_LIMITED = "rate_limited" @dataclass class ConnectionEvent: timestamp: datetime state: ConnectionState latency_ms: float message_id: Optional[str] = None chunk_index: int = 0 error_message: Optional[str] = None metadata: Dict[str, Any] = field(default_factory=dict) class ConnectionTimeline: """接続状態の時系列記録・可視化クラス""" def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"): self.api_key = api_key self.base_url = base_url self.events: List[ConnectionEvent] = [] self.start_time: Optional[datetime] = None self.end_time: Optional[datetime] = None def record_event(self, state: ConnectionState, latency_ms: float = 0, message_id: Optional[str] = None, chunk_index: int = 0, error_message: Optional[str] = None, **metadata): """イベントを記録""" event = ConnectionEvent( timestamp=datetime.now(), state=state, latency_ms=latency_ms, message_id=message_id, chunk_index=chunk_index, error_message=error_message, metadata=metadata ) self.events.append(event) self._log_event(event) return event def _log_event(self, event: ConnectionEvent): """イベントをコンソール出力""" state_emoji = { ConnectionState.DISCONNECTED: "⚪", ConnectionState.CONNECTING: "🔵", ConnectionState.CONNECTED: "🟢", ConnectionState.STREAMING: "🟣", ConnectionState.COMPLETED: "✅", ConnectionState.ERROR: "❌", ConnectionState.TIMEOUT: "⏱️", ConnectionState.RATE_LIMITED: "🚫" } emoji = state_emoji.get(event.state, "⚪") timestamp_str = event.timestamp.strftime("%H:%M:%S.%f")[:-3] if event.state == ConnectionState.ERROR: print(f"{emoji} [{timestamp_str}] {event.state.value}: {event.error_message}") elif event.latency_ms > 0: print(f"{emoji} [{timestamp_str}] {event.state.value} | Latency: {event.latency_ms:.2f}ms") else: print(f"{emoji} [{timestamp_str}] {event.state.value}") async def stream_chat_completion(self, model: str, messages: List[Dict], timeout: float = 30.0) -> str: """HolySheep AI との SSE ストリーミング通信""" import aiohttp self.start_time = datetime.now() request_start = time.perf_counter() self.record_event(ConnectionState.CONNECTING) url = f"{self.base_url}/chat/completions" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } payload = { "model": model, "messages": messages, "stream": True } try: async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=timeout)) as response: connection_latency = (time.perf_counter() - request_start) * 1000 self.record_event(ConnectionState.CONNECTED, latency_ms=connection_latency) if response.status != 200: error_text = await response.text() self.record_event(ConnectionState.ERROR, error_message=f"HTTP {response.status}: {error_text}") raise Exception(f"API Error: {response.status}") self.record_event(ConnectionState.STREAMING) full_response = "" chunk_count = 0 chunk_start = time.perf_counter() async for line in response.content: line = line.decode('utf-8').strip() if not line.startswith('data: '): continue data = line[6:] # "data: " を除去 if data == '[DONE]': self.record_event(ConnectionState.COMPLETED, latency_ms=0, chunk_index=chunk_count) break try: json_data = json.loads(data) content = json_data.get('choices', [{}])[0].get('delta', {}).get('content', '') if content: full_response += content chunk_count += 1 chunk_latency = (time.perf_counter() - chunk_start) * 1000 self.record_event( ConnectionState.STREAMING, latency_ms=chunk_latency, chunk_index=chunk_count ) chunk_start = time.perf_counter() except json.JSONDecodeError: continue # 途中の JSON はスキップ self.end_time = datetime.now() return full_response except asyncio.TimeoutError: self.record_event(ConnectionState.TIMEOUT, error_message=f"Request timeout after {timeout}s") raise except aiohttp.ClientError as e: self.record_event(ConnectionState.ERROR, error_message=str(e)) raise def visualize_timeline(self, output_path: str = "connection_timeline.png"): """時系列グラフを生成""" if not self.events: print("No events to visualize") return fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8), sharex=True) # レイテンシ折れ線グラフ timestamps = [e.timestamp for e in self.events] latencies = [e.latency_ms for e in self.events] colors = ['#22c55e' if e.state == ConnectionState.COMPLETED else '#ef4444' if e.state in [ConnectionState.ERROR, ConnectionState.TIMEOUT] else '#3b82f6' for e in self.events] ax1.plot(timestamps, latencies, marker='o', linewidth=2, markersize=6) ax1.scatter(timestamps, latencies, c=colors, s=100, zorder=5) ax1.set_ylabel('Latency (ms)', fontsize=12) ax1.set_title('HolySheep AI WebSocket Connection Timeline', fontsize=14, fontweight='bold') ax1.grid(True, alpha=0.3) ax1.set_ylim(bottom=0) # 状態を示す水平バー state_colors = { ConnectionState.DISCONNECTED: '#6b7280', ConnectionState.CONNECTING: '#3b82f6', ConnectionState.CONNECTED: '#22c55e', ConnectionState.STREAMING: '#8b5cf6', ConnectionState.COMPLETED: '#10b981', ConnectionState.ERROR: '#ef4444', ConnectionState.TIMEOUT: '#f59e0b', ConnectionState.RATE_LIMITED: '#dc2626' } y_pos = 0.5 for event in self.events: ax2.barh(y_pos, 1, left=event.timestamp, height=0.3, color=state_colors.get(event.state, '#6b7280'), alpha=0.8) ax2.text(event.timestamp, y_pos + 0.2, event.state.value, fontsize=8, rotation=45) ax2.set_ylabel('Connection State', fontsize=12) ax2.set_yticks([]) ax2.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) ax2.xaxis.set_major_locator(mdates.SecondLocator(interval=1)) plt.xticks(rotation=45) # 統計サマリー total_duration = (self.end_time - self.start_time).total_seconds() if self.end_time else 0 total_chunks = sum(1 for e in self.events if e.state == ConnectionState.STREAMING) error_count = sum(1 for e in self.events if e.state in [ConnectionState.ERROR, ConnectionState.TIMEOUT]) avg_latency = sum(e.latency_ms for e in self.events) / len(self.events) if self.events else 0 stats_text = f"""Connection Statistics: • Total Duration: {total_duration:.2f}s • Total Chunks: {total_chunks} • Errors: {error_count} • Average Latency: {avg_latency:.2f}ms""" ax1.text(0.02, 0.98, stats_text, transform=ax1.transAxes, fontsize=10, verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) plt.tight_layout() plt.savefig(output_path, dpi=150, bbox_inches='tight') print(f"[Visualizer] Timeline saved to {output_path}") plt.close() def generate_report(self) -> Dict[str, Any]: """診断レポート生成""" total_duration = (self.end_time - self.start_time).total_seconds() if self.end_time else 0 total_events = len(self.events) error_events = [e for e in self.events if e.state == ConnectionState.ERROR] latency_values = [e.latency_ms for e in self.events if e.latency_ms > 0] return { "session": { "start": self.start_time.isoformat() if self.start_time else None, "end": self.end_time.isoformat() if self.end_time else None, "duration_seconds": total_duration }, "events": { "total": total_events, "errors": len(error_events), "error_rate": len(error_events) / total_events if total_events > 0 else 0 }, "latency": { "min_ms": min(latency_values) if latency_values else 0, "max_ms": max(latency_values) if latency_values else 0, "avg_ms