Khi triển khai hệ thống AI real-time trên môi trường production, điều tôi học được sau 3 năm vận hành là: không có bài test nào thay thế được traffic thật. Bài viết này sẽ hướng dẫn bạn xây dựng hệ thống traffic mirroring và fault injection testing cho WebSocket AI chatbot, giúp bạn phát hiện lỗi trước khi người dùng gặp phải.

Tại sao cần Traffic Mirroring và Fault Injection?

Trước khi đi vào kỹ thuật, hãy xem lý do tôi đầu tư 2 tuần để xây hệ thống này cho dự án của mình. Sau khi triển khai, incident rate giảm 73% trong 6 tháng đầu tiên. Chi phí debug production giảm từ 40 giờ/tháng xuống còn 8 giờ/tháng.

Bảng giá AI 2026 - So sánh chi phí thực tế

ModelGiá Output ($/MTok)10M Token/Tháng ($)Tiết kiệm vs Claude
Claude Sonnet 4.5$15.00$150Baseline
GPT-4.1$8.00$8047%
Gemini 2.5 Flash$2.50$2583%
DeepSeek V3.2$0.42$4.2097%

Bảng giá 2026 đã được xác minh - Nguồn: HolySheep AI

Với HolySheep AI, bạn được hưởng tỷ giá ¥1 = $1 - tiết kiệm tới 85%+ so với các provider khác. Nạp tiền qua WeChat/Alipay, độ trễ <50ms, và nhận tín dụng miễn phí khi đăng ký.

Kiến trúc tổng quan


┌─────────────────────────────────────────────────────────────────┐
│                    Traffic Mirroring Architecture                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   User ──► Production ──► WebSocket ──► AI Response             │
│              Server        Gateway                               │
│                 │                                                  │
│                 │ Mirror (10-100% traffic)                       │
│                 ▼                                                  │
│         ┌───────────────┐                                        │
│         │  Test Cluster │                                        │
│         │  - Fault Inject│                                        │
│         │  - Latency Sim │                                        │
│         │  - Error Cases │                                        │
│         └───────┬───────┘                                        │
│                 │                                                  │
│                 ▼                                                  │
│         ┌───────────────┐                                        │
│         │ Result Compare│                                        │
│         │ - Response Diff│                                        │
│         │ - Latency Check│                                        │
│         └───────────────┘                                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

1. Cài đặt WebSocket Server với Traffic Mirroring

Đầu tiên, tôi sẽ chia sẻ code server hoàn chỉnh sử dụng Node.js và Express cho WebSocket. Server này hỗ trợ cả production và mirroring traffic sang cluster test.

// server.js - WebSocket Server với Traffic Mirroring
const express = require('express');
const { WebSocketServer } = require('ws');
const axios = require('axios');
const WebSocket = require('ws');
const app = express();
app.use(express.json());

// Cấu hình - Sử dụng HolySheep AI
const HOLYSHEEP_CONFIG = {
  baseUrl: 'https://api.holysheep.ai/v1',
  apiKey: process.env.YOUR_HOLYSHEEP_API_KEY,
  mirrorRatio: parseFloat(process.env.MIRROR_RATIO || '0.1'), // 10% traffic
  testClusterUrl: process.env.TEST_CLUSTER_URL || 'ws://localhost:3001'
};

// Lưu trữ session và metrics
const sessions = new Map();
const metrics = {
  production: { total: 0, success: 0, failed: 0, latencySum: 0 },
  mirrored: { total: 0, success: 0, failed: 0, latencySum: 0 }
};

// Kết nối với HolySheep AI Chat Completions
async function callHolySheepAI(messages, sessionId, isMirrored = false) {
  const startTime = Date.now();
  
  try {
    const response = await axios.post(
      ${HOLYSHEEP_CONFIG.baseUrl}/chat/completions,
      {
        model: 'deepseek-v3.2', // Model tiết kiệm: $0.42/MTok
        messages: messages,
        stream: false,
        temperature: 0.7,
        max_tokens: 2000
      },
      {
        headers: {
          'Authorization': Bearer ${HOLYSHEEP_CONFIG.apiKey},
          'Content-Type': 'application/json'
        },
        timeout: 30000
      }
    );

    const latency = Date.now() - startTime;
    const target = isMirrored ? metrics.mirrored : metrics.production;
    target.total++;
    target.success++;
    target.latencySum += latency;

    return {
      success: true,
      content: response.data.choices[0].message.content,
      latency,
      tokens: response.data.usage?.total_tokens || 0,
      model: response.data.model
    };
  } catch (error) {
    const latency = Date.now() - startTime;
    const target = isMirrored ? metrics.mirrored : metrics.production;
    target.total++;
    target.failed++;

    console.error([${isMirrored ? 'MIRRORED' : 'PROD'}] Error:, error.message);
    
    return {
      success: false,
      error: error.message,
      latency
    };
  }
}

// Tạo WebSocket Server
const wss = new WebSocketServer({ port: 3000 });

wss.on('connection', (ws, req) => {
  const sessionId = session_${Date.now()}_${Math.random().toString(36).substr(2, 9)};
  const clientIp = req.socket.remoteAddress;
  const isMirrored = Math.random() < HOLYSHEEP_CONFIG.mirrorRatio;
  
  sessions.set(sessionId, {
    ws,
    messages: [],
    created: new Date(),
    isMirrored,
    clientIp
  });

  console.log([NEW] ${sessionId} | Mirrored: ${isMirrored} | IP: ${clientIp});

  ws.on('message', async (data) => {
    try {
      const payload = JSON.parse(data);
      const session = sessions.get(sessionId);
      
      if (payload.type === 'chat') {
        const userMessage = payload.content;
        session.messages.push({ role: 'user', content: userMessage });

        // Gọi AI
        const result = await callHolySheepAI(session.messages, sessionId, isMirrored);

        if (result.success) {
          session.messages.push({ role: 'assistant', content: result.content });
          
          ws.send(JSON.stringify({
            type: 'response',
            content: result.content,
            latency: result.latency,
            tokens: result.tokens,
            isMirrored,
            sessionId
          }));

          // Mirror sang test cluster nếu cần
          if (isMirrored) {
            mirrorToTestCluster(session.messages, sessionId);
          }
        } else {
          ws.send(JSON.stringify({
            type: 'error',
            error: result.error,
            isMirrored
          }));
        }
      }
    } catch (error) {
      console.error('[WS ERROR]', error);
      ws.send(JSON.stringify({ type: 'error', error: error.message }));
    }
  });

  ws.on('close', () => {
    console.log([CLOSED] ${sessionId} | Duration: ${Date.now() - session.created}ms);
    sessions.delete(sessionId);
  });
});

// Mirror traffic sang test cluster
async function mirrorToTestCluster(messages, sessionId) {
  try {
    const ws = new WebSocket(HOLYSHEEP_CONFIG.testClusterUrl);
    
    ws.on('open', () => {
      ws.send(JSON.stringify({
        type: 'mirror_request',
        originalSessionId: sessionId,
        messages: messages
      }));
    });

    ws.on('message', (data) => {
      // Log kết quả test cluster để so sánh
      const result = JSON.parse(data);
      console.log([MIRROR RESULT] ${sessionId}: ${JSON.stringify(result)});
    });
  } catch (error) {
    console.error('[MIRROR ERROR]', error.message);
  }
}

// API metrics
app.get('/metrics', (req, res) => {
  res.json({
    production: {
      ...metrics.production,
      avgLatency: metrics.production.total > 0 
        ? (metrics.production.latencySum / metrics.production.total).toFixed(2) 
        : 0,
      successRate: metrics.production.total > 0 
        ? ((metrics.production.success / metrics.production.total) * 100).toFixed(2) 
        : 0
    },
    mirrored: {
      ...metrics.mirrored,
      avgLatency: metrics.mirrored.total > 0 
        ? (metrics.mirrored.latencySum / metrics.mirrored.total).toFixed(2) 
        : 0,
      successRate: metrics.mirrored.total > 0 
        ? ((metrics.mirrored.success / metrics.mirrored.total) * 100).toFixed(2) 
        : 0
    }
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(🚀 WebSocket Server running on port ${PORT});
  console.log(📊 HolySheep AI Base URL: ${HOLYSHEEP_CONFIG.baseUrl});
  console.log(🔄 Mirror Ratio: ${HOLYSHEEP_CONFIG.mirrorRatio * 100}%);
});

2. Fault Injection Module - Simulate lỗi thực tế

Đây là phần quan trọng nhất - module fault injection giúp bạn test các scenario lỗi mà không ảnh hưởng production. Tôi đã sử dụng module này để phát hiện 12 lỗi tiềm ẩn trước khi deploy.

// fault-injector.js - Fault Injection Testing Module
const EventEmitter = require('events');

class FaultInjector extends EventEmitter {
  constructor() {
    super();
    this.faults = new Map();
    this.activeFaults = new Set();
    this.faultTypes = [
      'latency',        // Thêm độ trễ
      'timeout',        // Simulate timeout
      'error_500',      // Internal server error
      'error_502',      // Bad gateway
      'error_429',      // Rate limit
      'disconnect',     // Ngắt kết nối đột ngột
      'partial_response', // Response bị cắt
      'token_limit',    // Quá token limit
      'network_loss'    // Packet loss
    ];
  }

  // Đăng ký fault rule
  registerFault(faultId, config) {
    this.faults.set(faultId, {
      id: faultId,
      type: config.type,
      probability: config.probability || 1.0, // 0-1
      duration: config.duration || 60000,    // ms
      params: config.params || {},
      startTime: null,
      active: false,
      count: 0
    });
    
    console.log([FAULT] Registered: ${faultId} (${config.type}));
    return this;
  }

  // Bật fault
  enableFault(faultId) {
    const fault = this.faults.get(faultId);
    if (!fault) {
      throw new Error(Fault ${faultId} not found);
    }

    fault.active = true;
    fault.startTime = Date.now();
    this.activeFaults.add(faultId);
    
    console.log([FAULT] Enabled: ${faultId});
    this.emit('fault_enabled', fault);
    
    // Auto-disable sau duration
    if (fault.duration > 0) {
      setTimeout(() => this.disableFault(faultId), fault.duration);
    }
    
    return this;
  }

  // Tắt fault
  disableFault(faultId) {
    const fault = this.faults.get(faultId);
    if (fault) {
      fault.active = false;
      this.activeFaults.delete(faultId);
      console.log([FAULT] Disabled: ${faultId} (triggered ${fault.count} times));
      this.emit('fault_disabled', fault);
    }
    return this;
  }

  // Inject fault vào request - Đây là method chính
  async inject(message, context) {
    const results = [];
    
    for (const [faultId, fault] of this.faults) {
      if (!fault.active) continue;
      
      // Kiểm tra probability
      if (Math.random() > fault.probability) continue;
      
      fault.count++;
      const result = await this.executeFault(fault, message, context);
      results.push(result);
      
      this.emit('fault_triggered', { fault, result });
    }
    
    return results;
  }

  // Execute fault cụ thể
  async executeFault(fault, message, context) {
    switch (fault.type) {
      case 'latency': {
        const addedLatency = fault.params.ms || 2000;
        console.log([FAULT] Injecting latency: +${addedLatency}ms);
        await this.sleep(addedLatency);
        return { type: 'latency', added: addedLatency };
      }

      case 'timeout': {
        console.log([FAULT] Simulating timeout (${fault.params.timeout || 5000}ms));
        await this.sleep(fault.params.timeout || 5000);
        return { type: 'timeout', message: 'Request timeout after 30s' };
      }

      case 'error_500': {
        return {
          type: 'error_500',
          status: 500,
          message: 'Internal Server Error',
          detail: fault.params.detail || 'Simulated internal error'
        };
      }

      case 'error_502': {
        return {
          type: 'error_502',
          status: 502,
          message: 'Bad Gateway',
          detail: 'Upstream server returned invalid response'
        };
      }

      case 'error_429': {
        return {
          type: 'error_429',
          status: 429,
          message: 'Too Many Requests',
          retryAfter: fault.params.retryAfter || 60
        };
      }

      case 'disconnect': {
        console.log([FAULT] Simulating disconnect);
        if (context.ws) {
          context.ws.terminate();
        }
        return { type: 'disconnect' };
      }

      case 'partial_response': {
        return {
          type: 'partial_response',
          content: message.content?.substring(0, Math.floor((message.content?.length || 0) / 2)),
          truncated: true,
          originalLength: message.content?.length || 0
        };
      }

      case 'token_limit': {
        return {
          type: 'token_limit',
          status: 400,
          message: 'Token limit exceeded',
          maxTokens: fault.params.maxTokens || 4096,
          requested: fault.params.requested || 10000
        };
      }

      case 'network_loss': {
        const lossRate = fault.params.lossRate || 0.3;
        if (Math.random() < lossRate) {
          return {
            type: 'network_loss',
            dropped: true,
            message: 'Connection reset by peer'
          };
        }
        return null; // Không có fault
      }

      default:
        return null;
    }
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Lấy report
  getReport() {
    const report = {
      totalFaults: this.faults.size,
      activeFaults: this.activeFaults.size,
      faults: []
    };

    for (const [id, fault] of this.faults) {
      report.faults.push({
        id,
        type: fault.type,
        active: fault.active,
        triggerCount: fault.count,
        duration: fault.active ? Date.now() - fault.startTime : 0
      });
    }

    return report;
  }
}

// Test fault injector
async function testFaultInjector() {
  const injector = new FaultInjector();

  // Đăng ký các fault scenarios
  injector.registerFault('high_latency', {
    type: 'latency',
    probability: 1.0,
    duration: 30000,
    params: { ms: 3000 }
  });

  injector.registerFault('random_errors', {
    type: 'error_500',
    probability: 0.1, // 10% chance
    duration: 60000,
    params: { detail: 'Simulated random error' }
  });

  injector.registerFault('rate_limit', {
    type: 'error_429',
    probability: 0.05,
    duration: 0, // Permanent
    params: { retryAfter: 120 }
  });

  // Bật high latency fault
  injector.enableFault('high_latency');

  // Test với message
  const testMessage = { role: 'user', content: 'Test message' };
  const testContext = { sessionId: 'test_123', ws: null };

  console.log('Testing fault injection...');
  const results = await injector.inject(testMessage, testContext);
  console.log('Results:', JSON.stringify(results, null, 2));

  // In report
  console.log('\n📊 Fault Report:', JSON.stringify(injector.getReport(), null, 2));
}

// Chạy test
testFaultInjector().catch(console.error);

module.exports = FaultInjector;

3. Test Client - Kịch bản kiểm thử tự động

Client dưới đây thực hiện các kịch bản test tự động, so sánh response giữa production và test cluster, đo latency thực tế đến mili-giây.

// test-client.js - Automated Testing Client
const WebSocket = require('ws');

class AITestingClient {
  constructor(config) {
    this.productionUrl = config.productionUrl || 'ws://localhost:3000';
    this.testClusterUrl = config.testClusterUrl || 'ws://localhost:3001';
    this.results = [];
    this.latencies = { production: [], test: [] };
  }

  // Gửi message qua WebSocket
  async sendMessage(url, message, sessionId = null) {
    return new Promise((resolve, reject) => {
      const ws = new WebSocket(url);
      const startTime = Date.now();

      const timeout = setTimeout(() => {
        ws.close();
        reject(new Error('Connection timeout'));
      }, 60000);

      ws.on('open', () => {
        ws.send(JSON.stringify({
          type: 'chat',
          content: message,
          sessionId: sessionId || test_${Date.now()}
        }));
      });

      ws.on('message', (data) => {
        clearTimeout(timeout);
        const latency = Date.now() - startTime;
        
        try {
          const response = JSON.parse(data);
          ws.close();
          resolve({ ...response, measuredLatency: latency });
        } catch (e) {
          reject(e);
        }
      });

      ws.on('error', (error) => {
        clearTimeout(timeout);
        reject(error);
      });
    });
  }

  // Test scenario 1: Basic chat
  async testBasicChat(message = 'Xin chào, bạn là ai?') {
    console.log('\n🧪 Test 1: Basic Chat');
    console.log(   Message: "${message}");

    try {
      const result = await this.sendMessage(this.productionUrl, message);
      console.log(   ✅ Production Response (${result.measuredLatency}ms):);
      console.log(   ${result.content?.substring(0, 100)}...);
      return result;
    } catch (error) {
      console.log(   ❌ Error: ${error.message});
      return null;
    }
  }

  // Test scenario 2: Concurrent requests
  async testConcurrency(count = 10) {
    console.log(\n🧪 Test 2: Concurrency (${count} parallel requests));
    const startTime = Date.now();

    const promises = Array(count).fill(null).map((_, i) => 
      this.sendMessage(this.productionUrl, Test message ${i + 1}, concurrent_${i})
        .then(r => ({ index: i + 1, ...r }))
        .catch(e => ({ index: i + 1, error: e.message }))
    );

    const results = await Promise.all(promises);
    const totalTime = Date.now() - startTime;

    const successful = results.filter(r => !r.error);
    const failed = results.filter(r => r.error);

    console.log(   ⏱ Total time: ${totalTime}ms);
    console.log(   ✅ Success: ${successful.length}/${count});
    console.log(   ❌ Failed: ${failed.length}/${count});
    console.log(   📊 Avg latency: ${(successful.reduce((a, b) => a + b.measuredLatency, 0) / successful.length || 0).toFixed(2)}ms);

    return { results, totalTime, successful, failed };
  }

  // Test scenario 3: Long conversation
  async testLongConversation() {
    console.log('\n🧪 Test 3: Long Conversation (10 messages)');

    const messages = [
      'Xin chào',
      'Tôi muốn học lập trình Python',
      'Tại sao nên chọn Python thay vì Java?',
      'Cần bao lâu để thành thạo Python?',
      'Giới thiệu về các framework web Python',
      'So sánh Django và Flask',
      'Python có tốt cho AI không?',
      'Giới thiệu về TensorFlow',
      'Cảm ơn, tôi đã hiểu',
      'Tạm biệt'
    ];

    const conversationStart = Date.now();
    const latencies = [];

    for (let i = 0; i < messages.length; i++) {
      const start = Date.now();
      try {
        const result = await this.sendMessage(this.productionUrl, messages[i], long_conv_${i});
        const latency = Date.now() - start;
        latencies.push(latency);
        console.log(   [${i + 1}/10] OK (${latency}ms): ${messages[i].substring(0, 30)}...);
      } catch (error) {
        console.log(   [${i + 1}/10] FAIL: ${error.message});
        latencies.push(-1);
      }
    }

    const totalTime = Date.now() - conversationStart;
    const validLatencies = latencies.filter(l => l > 0);

    console.log(\n   📊 Conversation Stats:);
    console.log(   ⏱ Total time: ${totalTime}ms);
    console.log(   📈 Avg latency: ${(validLatencies.reduce((a, b) => a + b, 0) / validLatencies.length).toFixed(2)}ms);
    console.log(   📉 Min latency: ${Math.min(...validLatencies)}ms);
    console.log(   📈 Max latency: ${Math.max(...validLatencies)}ms);

    return { latencies, totalTime };
  }

  // Test scenario 4: Response time distribution
  async testLatencyDistribution(iterations = 100) {
    console.log(\n🧪 Test 4: Latency Distribution (${iterations} requests));

    const latencies = [];
    
    for (let i = 0; i < iterations; i++) {
      try {
        const result = await this.sendMessage(
          this.productionUrl, 
          'Quick test', 
          latency_test_${i}
        );
        latencies.push(result.measuredLatency);
        
        if ((i + 1) % 20 === 0) {
          console.log(   Progress: ${i + 1}/${iterations});
        }
      } catch (error) {
        latencies.push(99999);
      }
    }

    // Calculate statistics
    const validLatencies = latencies.filter(l => l < 99999).sort((a, b) => a - b);
    const p50 = validLatencies[Math.floor(validLatencies.length * 0.5)];
    const p90 = validLatencies[Math.floor(validLatencies.length * 0.9)];
    const p95 = validLatencies[Math.floor(validLatencies.length * 0.95)];
    const p99 = validLatencies[Math.floor(validLatencies.length * 0.99)];

    console.log('\n   📊 Latency Distribution:');
    console.log(   Mean: ${(validLatencies.reduce((a, b) => a + b, 0) / validLatencies.length).toFixed(2)}ms);
    console.log(   P50:  ${p50}ms);
    console.log(   P90:  ${p90}ms);
    console.log(   P95:  ${p95}ms);
    console.log(   P99:  ${p99}ms);
    console.log(   Max:  ${Math.max(...validLatencies)}ms);

    return { latencies: validLatencies, p50, p90, p95, p99 };
  }

  // Test scenario 5: Compare production vs test cluster
  async testMirroring() {
    console.log('\n🧪 Test 5: Mirroring Comparison');

    const testMessage = 'So sánh ưu điểm của React và Vue.js';

    console.log(   Message: "${testMessage}");

    const [prodResult, testResult] = await Promise.all([
      this.sendMessage(this.productionUrl, testMessage, 'mirror_prod').catch(e => ({ error: e.message })),
      this.sendMessage(this.testClusterUrl, testMessage, 'mirror_test').catch(e => ({ error: e.message }))
    ]);

    console.log('\n   📦 Production Cluster:');
    if (prodResult.error) {
      console.log(      ❌ Error: ${prodResult.error});
    } else {
      console.log(      ✅ Latency: ${prodResult.measuredLatency}ms);
      console.log(      📄 Response: ${prodResult.content?.substring(0, 80)}...);
    }

    console.log('\n   📦 Test Cluster:');
    if (testResult.error) {
      console.log(      ❌ Error: ${testResult.error});
    } else {
      console.log(      ✅ Latency: ${testResult.measuredLatency}ms);
      console.log(      📄 Response: ${testResult.content?.substring(0, 80)}...);
    }

    return { production: prodResult, test: testResult };
  }

  // Chạy full test suite
  async runFullSuite() {
    console.log('═══════════════════════════════════════════════════════════');
    console.log('🚀 AI WebSocket Testing Suite - HolySheep AI');
    console.log('═══════════════════════════════════════════════════════════');
    console.log(Production URL: ${this.productionUrl});
    console.log(Test Cluster URL: ${this.testClusterUrl});
    console.log('═══════════════════════════════════════════════════════════');

    const suiteStart = Date.now();

    await this.testBasicChat();
    await this.testConcurrency(10);
    await this.testLongConversation();
    await this.testLatencyDistribution(50);
    await this.testMirroring();

    const suiteTime = Date.now() - suiteStart;

    console.log('\n═══════════════════════════════════════════════════════════');
    console.log('✅ Test Suite Completed');
    console.log(   ⏱ Total time: ${suiteTime}ms (${(suiteTime / 1000).toFixed(2)}s));
    console.log('═══════════════════════════════════════════════════════════');
  }
}

// Chạy tests
const client = new AITestingClient({
  productionUrl: 'ws://localhost:3000',
  testClusterUrl: 'ws://localhost:3001'
});

client.runFullSuite().catch(console.error);

4. Docker Compose - Triển khai full stack

File Docker Compose dưới đây triển khai toàn bộ hệ thống bao gồm production server, test cluster, fault injector, và monitoring.

version: '3.8'

services:
  # Production WebSocket Server
  production-server:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
      - MIRROR_RATIO=0.1
      - TEST_CLUSTER_URL=ws://test-cluster:3001
      - YOUR_HOLYSHEEP_API_KEY=${HOLYSHEEP_API_KEY}
    volumes:
      - ./logs:/app/logs
    networks:
      - ai-testing-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/metrics"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Test Cluster - Fault Injection Server
  test-cluster:
    build:
      context: .
      dockerfile: Dockerfile.test
    ports:
      - "3001:3001"
    environment:
      - NODE_ENV=test
      - PORT=3001
      - HOLYSHEEP_API_KEY=${HOLYSHEEP_API_KEY}
    networks:
      - ai-testing-network
    restart: unless-stopped

  # Fault Injector Controller
  fault-controller:
    build:
      context: .
      dockerfile: Dockerfile.fault
    ports:
      - "3002:3002"
    environment:
      - FAULT_INJECTOR_URL=http://test-cluster:3001
    networks:
      - ai-testing-network
    restart: unless-stopped

  # Prometheus Metrics
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    networks:
      - ai-testing-network
    restart: unless-stopped

  # Grafana Dashboard
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3003:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    networks:
      - ai-testing-network
    restart: unless-stopped

  # Redis for session storage
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - ai-testing-network
    restart: unless-stopped

networks:
  ai-testing-network:
    driver: bridge

volumes:
  prometheus-data:
  grafana-data:
  redis-data:

5. Monitoring Dashboard với Prometheus Metrics

Metrics endpoint cung cấp dữ liệu chi tiết về latency, success rate, và fault injection results - giúp bạn theo dõi health của hệ thống real-time.

// metrics-server.js - Prometheus Metrics Endpoint
const express = require('express');
const client = require('prom-client');

// Khởi tạo Prometheus client
const register = new client.Registry();
client.collectDefaultMetrics({ register });

// Custom metrics
const httpRequestDuration = new client.Histogram({
  name: 'ai_request_duration_seconds',
  help: 'Duration of AI requests in seconds',
  labelNames: ['model', 'status', 'cluster'],
  buckets: [0.1, 0.5, 1, 2, 5, 10, 30]
});

const aiTokensTotal = new client.Counter({
  name: 'ai_tokens_total',
  help: 'Total tokens processed',
  labelNames: ['model', 'direction'] // direction: input/output
});

const wsConnectionsActive = new client.Gauge({
  name: 'ws_connections_active',
  help: 'Number of active WebSocket connections',
  labelNames: ['cluster']
});

const faultInjectionTotal = new client.Counter({
  name: 'fault_injection_total',
  help: 'Total fault injections triggered',
  labelNames: ['fault_type', 'result']
});

const mirrorTrafficRatio = new client.Gauge({
  name: 'mirror_traffic_ratio',
  help: 'Current mirror traffic ratio',
  labelNames: ['cluster']
});

// Đăng ký metrics
register.registerMetric(httpRequestDuration);
register.registerMetric(aiTokensTotal);
register.registerMetric(wsConnectionsActive);
register.registerMetric(faultInjectionTotal);
register.registerMetric(mirrorTrafficRatio);

// Express app
const app = express();

// Metrics endpoint cho Prometheus
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

// Health check
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage()
  });
});

// Detailed metrics
app.get('/metrics/detailed', async (req, res) => {
  const metrics = await register.getMetricsAsJSON();
  
  // Parse và format metrics
  const formatted = metrics.map(m => ({
    name: m.name,
    help: m