# 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 排程 ```java // 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秒) ```java 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秒) ```java 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秒) ```java class wkEventPolling extends java.util.TimerTask { public void run() { if (PageDataDB.Polling_Flag) { fMainTmp.GetEvent(); // 取得事件 } } } ``` **行為:** - 每 18 秒執行一次 - 呼叫 GetEvent() 取得事件日誌 - 更新 UI 事件列表 ### wkEventPollingForSNMP (22秒) ```java class wkEventPollingForSNMP extends java.util.TimerTask { public void run() { if (fMainTmp.jtHostList.getSelectedRow() >= 0) { fMainTmp.EventPollingForSNMP(); // 取得事件並發送 SNMP } } } ``` **行為:** - 每 22 秒執行一次 - 需要有選中的控制器 - 呼叫 EventPollingForSNMP() 取得事件並發送 SNMP Trap ## Polling() 方法 ```java 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() 方法 ```java 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() 方法 ```java 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 開關 ```java // 選單功能 - 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 (如果啟用) ``` ## 關鍵變數 ```java // 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 實作建議 ```rust use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::time::{interval, Duration}; pub struct PollingManager { polling_flag: Arc, // 其他狀態... } 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) { // 取得控制器狀態 // 刷新頁面資料 } } ```