導入 — 購読者へ向けた結論
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