Files
rust_raidguardx/GC_DumpControllerLog.md
T
2026-03-29 01:17:00 +08:00

15 KiB
Raw Blame History

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();

.bin 檔案解析

原始資料格式

控制器回傳的資料包含 Header + Data

┌─────────────────────────────────────────────┐
│           Controller Response               │
├─────────┬───────────────────────────────────┤
│ Offset  │ Description                      │
├─────────┼───────────────────────────────────┤
│ 0-2     │ Total Length (2 bytes)           │
│ 3       │ (continuation?)                  │
│ 4-8     │ Unknown                          │
│ 9       │ Status (0x50 = Success)         │
│ 10-11   │ Unknown                          │
│ 12      │ Page Code (0x60-0x64)            │
│ 13-14   │ 0xFF 0xFF (flag)                │
│ 15-18   │ Log Count (4 bytes)             │
│ 19-22   │ Start Block (4 bytes)           │
│ 23-26   │ Total Blocks (4 bytes)          │
├─────────┴───────────────────────────────────┤
│ 15-511  │ Actual Data (496 bytes)         │
└─────────┴───────────────────────────────────┘

寫入 .bin 檔案的程式碼

// 從 reply[15] 開始,寫入 512 bytes
fileOut.write(reply, 15, 512);

所以 .bin 檔案內容是:

  • 每筆 512 bytes
  • 包含 header 資訊

解析方式

1. 解析 Header

def parse_bin_header(block):
    # block 是 512 bytes
    status = block[9]           # 0x50 = Success
    page_code = block[12]       # 0x60-0x64
    log_count = int.from_bytes(block[15:19], 'little')
    start_block = int.from_bytes(block[19:23], 'little')
    total_blocks = int.from_bytes(block[23:27], 'little')
    data = block[15:]           # 實際資料
    
    return {
        'status': status,
        'page_code': page_code,
        'log_count': log_count,
        'start_block': start_block,
        'total_blocks': total_blocks,
        'data': data
    }

2. 解析各類型 Log

Page Code 類型 資料格式
0x60 EventPage.bin 32 bytes/record
0x61 DebugPage.bin 32 bytes/record
0x62 DiskMetadataPage.bin 32 bytes/record
0x63 SpecialLogPage.bin 48 bytes/record
0x64 TelMesPages.log 40 bytes/record (特殊處理)

3. Python 解析範例

import struct
import datetime

def parse_event_log(data):
    """解析 Event Page (0x60)"""
    records = []
    for i in range(0, len(data), 32):
        if i + 32 > len(data):
            break
        record = data[i:i+32]
        
        # 解析 timestamp (假設格式)
        timestamp = struct.unpack('<I', record[0:4])[0]
        event_id = struct.unpack('<H', record[4:6])[0]
        message = record[6:].decode('ascii', errors='replace').rstrip('\x00')
        
        records.append({
            'timestamp': timestamp,
            'event_id': event_id,
            'message': message
        })
    
    return records

# 使用
with open('EventPage.bin', 'rb') as f:
    data = f.read()
    events = parse_event_log(data)
    for e in events:
        print(f"{e['event_id']}: {e['message']}")

4. 用 Hexdump 查看

# 查看前 512 bytes
xxd -l 512 EventPage.bin

# 輸出範例:
00000000: 5001 0000 0000 0000 0000 5001 ff ff 0000 0000  P...........P....
00000010: 0000 0001 0000 0001 0000 0000 0000 0000  ................
00000020: 3031 3233 3435 3637 3839 3031 3233 3435  0123456789012345
00000030: 3637 3839 0000 0000 0000 0000 0000 0000  6789............
...

TelMesPages.log 特殊處理

Page 0x64 有特殊處理:

  • 原始:40 bytes/record
  • 輸出:去除 null 字元 (0x00)
// 寫入時的處理
for(int j=0; j < logs[i].length()/40; j++) {
    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);  // 加上換行
}

Rust 解析範例

use std::fs::File;
use std::io::Read;

#[derive(Debug)]
struct LogHeader {
    status: u8,
    page_code: u8,
    log_count: u32,
    start_block: u32,
    total_blocks: u32,
}

fn parse_bin_header(block: &[u8; 512]) -> LogHeader {
    LogHeader {
        status: block[9],
        page_code: block[12],
        log_count: u32::from_le_bytes([block[15], block[16], block[17], block[18]]),
        start_block: u32::from_le_bytes([block[19], block[20], block[21], block[22]]),
        total_blocks: u32::from_le_bytes([block[23], block[24], block[25], block[26]]),
    }
}

fn main() {
    let mut file = File::open("EventPage.bin").unwrap();
    let mut buffer = [0u8; 512];
    
    let mut block_num = 0;
    while file.read(&mut buffer).unwrap() > 0 {
        let header = parse_bin_header(&buffer);
        println!("Block {}: status={:02x}, page={:02x}", 
                 block_num, header.status, header.page_code);
        block_num += 1;
    }
}