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

503 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```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
```
### 迴圈邏輯
```java
// 遍历 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)
```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)
```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)
```java
// 原始資料: 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 類別使用
```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 檔案的程式碼
```java
// 從 reply[15] 開始,寫入 512 bytes
fileOut.write(reply, 15, 512);
```
所以 .bin 檔案內容是:
- **每筆 512 bytes**
- **包含 header 資訊**
### 解析方式
#### 1. 解析 Header
```python
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 解析範例
```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 查看
```bash
# 查看前 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)
```java
// 寫入時的處理
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 解析範例
```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;
}
}
```