Files
rust_raidguardx/GC_DumpControllerLog.md
T

9.5 KiB

Dump Controller Log 實現分析

流程架構

┌─────────────────────────────────────────────────────────────┐
│  frmMain.jmDumpEventLog_actionPerformed()                  │
│  (根據型號選擇使用 dlgDumpLog 或 dlgDumpLog2)              │
└──────────────────────────┬──────────────────────────────────┘
                           │
           ┌───────────────┴───────────────┐
           ▼                               ▼
    ┌──────────────┐              ┌──────────────┐
    │ dlgDumpLog   │              │ dlgDumpLog2  │
    │ (舊型號)      │              │ (新型號)      │
    └──────────────┘              └──────────────┘
           │                               │
           └───────────┬───────────────────┘
                       ▼
        ┌──────────────────────────┐
        │  dumpEventLog()          │
        │  TCP 連線 Port 8922      │
        └───────────┬──────────────┘
                    ▼
        ┌──────────────────────────┐
        │  OpCode: 0x01            │
        │  SubOp: 0x03            │
        │  Page: 0x60 ~ 0x64       │
        └───────────┬──────────────┘
                    ▼
        ┌──────────────────────────┐
        │  寫入 ./Log 目錄         │
        │  壓縮為 ZIP 檔           │
        └──────────────────────────┘

通訊協定

Request

Head:  [0x01, 0x17, 0x00, 0x00, 0x00]  (23 bytes)
Data:  [0x02, 0x01, SN(16 bytes), CDB(5 bytes)]

CDB 結構

Offset 說明
0 0x01 OpCode - GET_PAGE
1 0x03 SubOp
2 0x60-0x64 Page Code (Log Type)
3 LSB Block Index
4 MSB Block Index

Log Types

Page 說明
0x60 System Log
0x61 Event Log
0x62 I/O Error Log
0x63 Dump Log
0x64 Reserved

實現細節

dlgDumpLog.java

// 1. 連線到控制器
theSocket.connect(new InetSocketAddress(PageDataDB.GuiSrvIpAddr, Port), 10000);

// 2. 發送请求 (OpCode 0x01, SubOp 0x03)
Cdb[0] = 0x01;  // GET_PAGE
Cdb[1] = 0x03;  // SubOp
Cdb[2] = 0x60;  // Log type (0x60-0x64)
Cdb[3] = BlockIndexLSB;
Cdb[4] = BlockIndexMSB;

// 3. 解析回應
arg[0] = reply[9];  // Status (0x50 = Success)
arg[1] = reply[12]; // Page code
arg[2] = startBlock;
arg[3] = totalBlocks;

// 4. 寫入檔案 (./Log 目錄)
File file = getFile((byte)arg[1]);
fileOut.write(reply, 15, 512);  // 寫入 512 bytes

// 5. 壓縮成 ZIP
compressLog();  // 打包成 Log_YYYYMMDD_HHMMSS.zip

迴圈邏輯

// 遍历 5 種 log 類型
for (byte i = 0x60; i <= 0x64; i++) {
    // 取得該類型的 log 總量
    arg1 = dumpEventLog(Cdb);
    
    // 遍历每個 block
    for (int j = arg1[2]; j < arg1[3]; j++) {
        Cdb[3] = (byte)(j & 0xFF);           // LSB
        Cdb[4] = (byte)((j >> 8) & 0xFF);    // MSB
        arg2 = dumpEventLog(Cdb);
        
        // 寫入單個 block
        fileOut.write(reply, 15, 512);
    }
}

輸出檔案

  • 目錄: ./Log/
  • 格式: Log_YYYYMMDD_HHMMSS.zip
  • 內容: 5 個 log 類型的原始資料 (各 512 bytes/block)

關鍵類

類別 檔案 功能
frmMain frmMain.java:4136 入口,選擇 dialog 版本
dlgDumpLog dlgDumpLog.java 舊型號使用 (單一控制器)
dlgDumpLog2 dlgDumpLog2.java 新型號使用 (支援多控制器)
SendCmd InBandAPI/SendCmd.java TCP 通訊底層

Code Flow

1. Menu 點擊

frmMain.jmDumpControllerLog (MenuItem)
  → frmMain.jmDumpEventLog_actionPerformed()
  → 檢查型號選擇 dlgDumpLog 或 dlgDumpLog2

2. Dump 執行

dlgDumpLog.run()
  → for (0x60 ~ 0x64) {
      dumpEventLog() 取得總量
      for (每個 block) {
        dumpEventLog() 取得資料
        寫入檔案
      }
    }
  → compressLog() 壓縮
  → dispose()

3. Socket 通訊

dumpEventLog(Cdb)
  → connect(Port 8922)
  → send(Head + SN + CDB)
  → read(reply)
  → parse status & data
  → close()

ZIP 壓縮處理細節

輸出檔案

dlgDumpLog.java (舊型號)

./Log/{SN}_ControllerLog_{YYYY}_{M}_{D}_{H}_{m}_{s}.zip

dlgDumpLog2.java (新型號)

Primary: ./Log/Primary_{SN}_ControllerLog.zip
Secondary: ./Log/Secondary_{SN}_ControllerLog.zip
合併: ./Log/{PrimarySN}_{SecondarySN}_ControllerLog_{YYYY}_{M}_{D}_{H}_{m}_{s}.zip

壓縮前的暫存檔

Page Code 檔案名稱 說明
0x60 EventPage.bin Event Log
0x61 DebugPage.bin Debug Log
0x62 DiskMetadataPage.bin Disk Metadata
0x63 SpecialLogPage.bin Special Log
0x64 TelMesPages.log Telemetry Messages

compressLog() 流程 (dlgDumpLog.java)

public void compressLog() {
    File[] logs = new File[5];
    for(int i=0; i<=4; i++) {
        logs[i] = getFile((byte)(i+0x60));  // 取得 5 個暫存檔
    }
    
    // 建立 ZIP 檔案
    Calendar rightNow = Calendar.getInstance();
    String filename = "./Log/" + PageDataDB.sSN.trim() + "_ControllerLog_"
        + rightNow.get(Calendar.YEAR) + "_"
        + (rightNow.get(Calendar.MONTH)+1) + "_"
        + rightNow.get(Calendar.DAY_OF_MONTH) + "_"
        + rightNow.get(Calendar.HOUR) + "_"
        + rightNow.get(Calendar.MINUTE) + "_"
        + rightNow.get(Calendar.SECOND) + ".zip";
    
    FileOutputStream fout = new FileOutputStream(filename);
    ZipOutputStream zout = new ZipOutputStream(fout);
    zout.setLevel(5);  // 壓縮等級 0-9
    
    // 逐一壓縮每個檔案
    for (byte i = 0; i <= 4; i++) {
        if(logs[i].exists()) {
            ZipEntry ze = new ZipEntry(logs[i].getName());
            FileInputStream fin = new FileInputStream(logs[i]);
            zout.putNextEntry(ze);
            
            // 特殊處理 Page 0x64 (TelMesPages.log)
            if(i != 0x4) {
                // 一般檔案:直接複製
                for(int c = fin.read(); c != -1; c = fin.read()) {
                    zout.write(c);
                }
            } else {
                // TelMesPages.log: 每 40 bytes 為一筆記錄
                // 移除空字元 (0x00),只保留有效資料
                byte data;
                boolean skipNull = false;
                for(int j=0; j < logs[i].length()/40; j++) {
                    skipNull = false;
                    for(int k=0; k<40; k++) {
                        data = (byte)fin.read();
                        if(data == 0x0a || skipNull == true) {
                            skipNull = true;
                        } else {
                            zout.write(data);
                        }
                    }
                    zout.write(0x0a);  // 加上換行符
                }
            }
            
            zout.closeEntry();
            fin.close();
            logs[i].delete();  // 刪除暫存檔
        }
    }
    zout.close();
}

compressLog() 流程 (dlgDumpLog2.java)

// 單一控制器壓縮
public void compressLog(String sSN) {
    String prefix = sSN.equals(PageDataDB.sSN) ? "Primary" : "Secondary";
    String filename = "./Log/" + prefix + "_" + sSN.trim() + "_ControllerLog.zip";
    // ... 同上邏輯
}

// 雙控制器合併壓縮
public void compressLog2() {
    File[] logs = new File[2];
    logs[0] = new File(sMaterFileNamePatch);  // Primary
    logs[1] = new File(sSlaveFileNamePatch);   // Secondary
    
    // 合併成單一 ZIP,內含兩個 ZIP 檔
    String filename = "./Log/" + PageDataDB.sSN.trim() + "_" 
        + PageDataDB.sSlaveSN.trim() + "_ControllerLog_{timestamp}.zip";
    // ... 
}

ZIP 結構

Archive: {SN}_ControllerLog_2024_3_29_10_30_45.zip
├── EventPage.bin         (512 bytes/block)
├── DebugPage.bin         (512 bytes/block)
├── DiskMetadataPage.bin  (512 bytes/block)
├── SpecialLogPage.bin    (512 bytes/block)
└── TelMesPages.log      (40 bytes/record, 去除 null)

特殊處理:TelMesPages.log (Page 0x64)

// 原始資料: 40 bytes/record,包含 null (0x00) 字元
// 處理邏輯:
// 1. 讀取每 40 bytes
// 2. 跳過開頭的 null 字元
// 3. 遇到換行符 (0x0a) 後,後續全部視為有效資料
// 4. 輸出時每筆記錄加上換行符

// Example:
// Input:  [0x00, 0x00, 0x00, 0x31, 0x32, ..., 0x00, 0x00]
// Output: "12...data\n"

Java 類別使用

import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;
import java.util.Calendar;

// 關鍵類別
ZipOutputStream zout = new ZipOutputStream(fout);
zout.setLevel(5);              // 壓縮等級 (0=儲存, 9=最大壓縮)
ZipEntry ze = new ZipEntry(filename);
zout.putNextEntry(ze);
zout.write(byte[] data);
zout.closeEntry();
zout.close();