Files
rust_raidguardx/GC_Polling.md
T
2026-03-29 12:12:35 +08:00

14 KiB

Polling 架構分析

概述

Polling 是 RAIDGuard X 用來定期檢測 RAID 控制器狀態的核心機制,負責:

  • 取得控制器狀態
  • 取得事件日誌
  • 檢測連線狀態
  • 發送 SNMP Trap

架構流程

┌─────────────────────────────────────────────────────────────────────┐
│                        Polling 架構                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │              java.util.Timer (tmPolling)                   │   │
│  │  ┌─────────────┬─────────────┬─────────────┬─────────────┐ │   │
│  │  │ wkPolling1  │ wkPolling2  │ wkEventPolling │wkEventPoll │ │   │
│  │  │   (12秒)    │   (30秒)    │   (18秒)    │ forSNMP    │ │   │
│  │  │             │             │             │  (22秒)    │ │   │
│  │  └─────────────┴─────────────┴─────────────┴─────────────┘ │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                              │                                      │
│         ┌────────────────────┼────────────────────┐                │
│         ▼                    ▼                    ▼                │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐          │
│  │   Polling()  │    │   GetEvent() │    │EventPollFor  │          │
│  │  狀態輪詢     │    │  事件取得     │    │   SNMP()     │          │
│  └──────┬───────┘    └──────┬───────┘    └──────┬───────┘          │
│         │                   │                   │                   │
│         ▼                   ▼                   ▼                   │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐          │
│  │ PageRefresh() │    │IssueCmdAgent2│    │IssueCmdAgent2│          │
│  │  取得頁面資料  │    │  發送命令     │    │  發送命令     │          │
│  └──────┬───────┘    └──────┬───────┘    └──────┬───────┘          │
│         │                   │                   │                   │
│         ▼                   ▼                   ▼                   │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │              In-Band API (Port 8922)                        │  │
│  │  - 0x01 GET_PAGE                                            │  │
│  │  - 0xFF/0x02 Get InBand Event                               │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                              │                                      │
│                              ▼                                      │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │              RAID Controller                                 │  │
│  └──────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

Polling 任務

Timer 排程

// frmMain.java - 1460-1477
java.util.Timer tmPolling = new java.util.Timer();

// 主動式輪詢 (線上狀態)
tmPolling.schedule(new wkPolling1(this), 5000, 12001);      // 每 12 秒
tmPolling.schedule(new wkPolling2(this), 5000, 3001);       // 每 3 秒

// 被動式輪詢 (離線狀態 - 檢查是否重新上線)
tmPolling.schedule(new wkStatusRefresh(this), 5000, 1004);   // 每 1 秒

// 事件輪詢
tmPolling.schedule(new wkEventPolling(this), 5000, 18003);  // 每 18 秒
tmPolling.schedule(new wkEventPollingForSNMP(this), 5000, 22002); // 每 22 秒
任務 週期 說明
wkPolling1 12 秒 取得控制器頁面資料
wkPolling2 3 秒 取得控制器頁面資料 + GC
wkStatusRefresh 1 秒 狀態刷新
wkEventPolling 18 秒 取得事件日誌
wkEventPollingForSNMP 22 秒 取得事件並發送 SNMP Trap

Polling 類別

wkPolling1 (12秒)

class wkPolling1 extends java.util.TimerTask {
    public void run() {
        if (PageDataDB.Polling_Flag) {
            fMainTmp.Polling(0x01);  // iCareOffline = 0x01 (關心離線)
        }
    }
}

行為:

  • 每 12 秒執行一次
  • 只在 Polling_Flag = true 時執行
  • 關心離線狀態 (iCareOffline = 0x01)
  • 呼叫 Polling(0x01)

wkPolling2 (3秒)

class wkPolling2 extends java.util.TimerTask {
    public void run() {
        if (PageDataDB.Polling_Flag) {
            fMainTmp.Polling(0x00);  // iCareOffline = 0x00 (不關心離線)
        }
        Runtime.getRuntime().gc();  // 垃圾回收
    }
}

行為:

  • 每 3 秒執行一次
  • 不關心離線狀態 (iCareOffline = 0x00)
  • 呼叫 Polling(0x00)
  • 執行 GC 垃圾回收

wkEventPolling (18秒)

class wkEventPolling extends java.util.TimerTask {
    public void run() {
        if (PageDataDB.Polling_Flag) {
            fMainTmp.GetEvent();  // 取得事件
        }
    }
}

行為:

  • 每 18 秒執行一次
  • 呼叫 GetEvent() 取得事件日誌
  • 更新 UI 事件列表

wkEventPollingForSNMP (22秒)

class wkEventPollingForSNMP extends java.util.TimerTask {
    public void run() {
        if (fMainTmp.jtHostList.getSelectedRow() >= 0) {
            fMainTmp.EventPollingForSNMP();  // 取得事件並發送 SNMP
        }
    }
}

行為:

  • 每 22 秒執行一次
  • 需要有選中的控制器
  • 呼叫 EventPollingForSNMP() 取得事件並發送 SNMP Trap

Polling() 方法

public void Polling(int iCareOffline) {
    // 遍歷所有已註冊的控制器
    for (int i = 0; i < jtHostList.getRowCount(); i++) {
        sHostName = jtHostList.getValueAt(i, iHostList_Host).toString();
        sIpAddr = jtHostList.getValueAt(i, iHostList_IP).toString();
        sSN = jtHostList.getValueAt(i, iHostList_SN).toString();
        
        if (!sSN.equals("")) {
            // 檢查連線失敗次數
            if (GetHostCmdFailCnt(sSN) > iMaxCommandFail) {
                // 設定為離線狀態
                SetHostStatus(sSN, 0x00);
                jtHostList.setValueAt(imgRed, i, iHostList_Net);
                jtHostList.setValueAt("Disconnected", i, iHostList_Status);
                ClearEventLog();  // 清除事件日誌
            }
            
            // 根據參數決定行為
            if (iCareOffline == 0x01) {
                // 只關心線上的控制器
                if (GetHostStatus(sSN) == 0x01) {
                    PageRefresh(sIpAddr, sSN, new Point(0, 0));
                    // 如果是 MAX Storage,刷新備援控制器
                    if (Check_MAX_Storage(sSN)) {
                        PageRefresh(sIpAddr, GetSlaveSerialNumber(sSN), new Point(0, 0));
                    }
                }
            } else {
                // 刷新所有控制器
                PageRefresh(sIpAddr, sSN, new Point(0, 0));
                if (Check_MAX_Storage(sSN)) {
                    PageRefresh(sIpAddr, GetSlaveSerialNumber(sSN), new Point(0, 0));
                }
            }
        }
    }
}

iCareOffline 參數

行為
0x01 只刷新線上 (GetHostStatus == 0x01) 的控制器
0x00 刷新所有控制器 (不管離線與否)

GetEvent() 方法

public void GetEvent() {
    // 取得目前選中的控制器事件
    GetEvent(sIpAddr, sSN);
}

// PageDataDB.GetEvent()
public static void GetEvent(String sIP, String sSN) {
    // 避免重複執行
    if (bIsGetEvent) return;
    bIsGetEvent = true;
    
    // 建立命令
    CmdQueue cmdQueue = new CmdQueue();
    cmdQueue.NewCommand(sIP, sSN, 1, 1, 3);
    
    // 設定命令內容
    bCmds[0] = 0xFF;  // Phase Code
    bCmds[1] = 0x00;  // Request Code
    bCmds[2] = 0x00;  // Controller No
    bCmds[3] = 0x02;  // InBand Event Cmd Code: Get InBand Event
    
    // 發送命令
    issueCmdAgent2 ICA = new issueCmdAgent2();
    ICA.IssusCmd(sIP, sSN, cmdQueue);
    
    // 處理回應
    ReplyTask(cmdQueue, iEventPollingCheckType);
    
    bIsGetEvent = false;
}

EventPollingForSNMP() 方法

public void EventPollingForSNMP() {
    // 遍歷所有控制器
    for (int iHost = 0; iHost < iMaxHosts; iHost++) {
        // 取得控制器資訊
        sTmpHostName = jtHostList.getValueAt(iHost, iHostList_Host);
        sTmpIP = jtHostList.getValueAt(iHost, iHostList_IP);
        sTmpSN = jtHostList.getValueAt(iHost, iHostList_SN);
        
        // 發送事件查詢命令 (OpCode 0xFF/0x02)
        CmdQueue cmdQueue = new CmdQueue();
        cmdQueue.NewCommand(sTmpIP, sTmpSN, 1, 70, 3);
        
        bCmds[3] = 0x02;  // Get InBand Event
        
        // 發送並處理回應
        issueCmdAgent2 ICA = new issueCmdAgent2();
        ICA.IssusCmd(sTmpIP, sTmpSN, cmdQueue);
        
        // 處理 SNMP 回應
        ReplyTaskForSNMP(sTmpHostName, sTmpIP, sTmpSN, cmdQueue);
    }
}

通訊協定

取得事件 (Get InBand Event)

Request:
  Phase Code:  0xFF
  Request Code: 0x00
  Controller:  0x00
  OpCode:       0x02 (Get InBand Event)

Response:
  - 事件資料 (最多 70 bytes)
  - 包含事件時間、事件 ID、事件描述

Polling 開關

// 選單功能 - Polling On/Off
public void jmPollingSwitch_actionPerformed(ActionEvent e) {
    if (PageDataDB.Polling_Flag) {
        PageDataDB.Polling_Flag = false;
        jmPollingSwitch.setText("Polling Off");
    } else {
        PageDataDB.Polling_Flag = true;
        jmPollingSwitch.setText("Polling On");
    }
}

Polling 狀態判斷

主機狀態 (Host Status):
  0x00 - 離線 (Offline)
  0x01 - 線上 (Online)
  0x10 - 更新中 (Updating)

連線失敗計數:
  - GetHostCmdFailCnt(sSN) > iMaxCommandFail → 判定離線
  - 每次命令失敗計數 +1
  - 每次成功計數歸零

Code Flow

完整 Polling 流程

1. Timer 觸發
   │
   ▼
2. 檢查 Polling_Flag
   │  true → 繼續
   │  false → 停止
   ▼
3. 選擇 Polling 任務
   │
   ├── wkPolling1 ──► Polling(0x01) ──► PageRefresh() ──► In-Band API
   │
   ├── wkPolling2 ──► Polling(0x00) ──► PageRefresh() ──► In-Band API
   │
   ├── wkEventPolling ──► GetEvent() ──► In-Band API
   │
   └── wkEventPollingForSNMP ──► EventPollingForSNMP() ──► In-Band API ──► SNMP Trap

事件處理流程

控制器事件發生
    │
    ▼
Polling 偵測到 (Get InBand Event)
    │
    ▼
解析事件內容
    │
    ├── 更新 UI Event List
    │
    ├── 發送 SNMP Trap (EventPollingForSNMP)
    │
    └── 發送 Email Alert (如果啟用)

關鍵變數

// PageDataDB.java
public static boolean Polling_Flag = true;     // Polling 開關
public static boolean bIsGetEvent = false;    // 防止重複取得事件
public static int iMaxCommandFail = 3;         // 最大允許連線失敗次數

// frmMain.java
java.util.Timer tmPolling;                    // Polling 計時器

效能優化

  1. 分層輪詢: 不同任務使用不同週期
  2. 離線加速: 離線後改用較短週期 (3秒 vs 12秒)
  3. 事件鎖: bIsGetEvent 防止重複取得
  4. GC 排程: wkPolling2 執行垃圾回收

Rust 實作建議

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::time::{interval, Duration};

pub struct PollingManager {
    polling_flag: Arc<AtomicBool>,
    // 其他狀態...
}

impl PollingManager {
    pub fn new() -> Self {
        Self {
            polling_flag: Arc::new(AtomicBool::new(true)),
        }
    }
    
    pub fn start(&self) {
        let flag = self.polling_flag.clone();
        
        tokio::spawn(async move {
            let mut timer = interval(Duration::from_secs(12));
            loop {
                timer.tick().await;
                if flag.load(Ordering::SeqCst) {
                    self.polling().await;
                }
            }
        });
    }
    
    pub fn stop(&self) {
        self.polling_flag.store(false, Ordering::SeqCst);
    }
    
    async fn polling(&self) {
        // 取得控制器狀態
        // 刷新頁面資料
    }
}