IPMI SEL 解析:電源事件紀錄怎麼看(電源工程師版)
IPMI SEL(System Event Log)逐位元組解析:16 Bytes 事件記錄的時間戳記、Sensor Type、Event Type、Assertion / Deassertion 與 Event Data,電源供應器事件(Presence、Failure、AC lost)與門檻事件怎麼解讀,如何用 SEL 重建故障時間線,附 Python 解析程式。
這篇接在 BMC 如何監控電源 與 IPMI SDR 解析 之後,會用到 SDR 的「感測器編號」與「換算公式」。
SEL 的特性
| 特性 | 說明 |
|---|---|
| 存在 BMC 的非揮發記憶體 | 主機關機、重開機都會保留 |
| 每筆固定 16 Bytes | 格式由 IPMI 規格定義 |
| 有時間戳記 | 以 BMC 的時鐘為準 |
| 容量有限 | 常見幾百到幾千筆,滿了之後新事件可能被丟棄(依平台設定,也可能覆蓋舊紀錄) |
| 只增不改 | 只能新增或整個清除,不能修改單筆 |
先看實際的事件紀錄
以下是一顆電源在一天內的 4 筆 SEL:
01 00 02 95 3D E5 6A 20 00 04 08 51 6F 00 FF FF
02 00 02 05 C6 E5 6A 20 00 04 08 51 6F 03 FF FF
03 00 02 2C C9 E5 6A 20 00 04 08 51 EF 03 FF FF
04 00 02 98 DA E5 6A 20 00 04 01 52 01 57 42 41
解析後:
1 | 2026-10-31 01:00:05 UTC | Power Supply PSU1 Status | Presence detected | Asserted
2 | 2026-10-31 10:42:13 UTC | Power Supply PSU1 Status | Power Supply input lost (AC/DC) | Asserted
3 | 2026-10-31 10:55:40 UTC | Power Supply PSU1 Status | Power Supply input lost (AC/DC) | Deasserted
4 | 2026-10-31 12:10:00 UTC | Temperature PSU1 Temp | Upper Non-critical going high | Asserted | 讀值 42h,門檻 41h(用 SDR 換算)
翻成白話:PSU1 在 01:00 插入;10:42 輸入斷電,10:55 恢復(斷了約 13 分鐘);12:10 溫度超過警告門檻。
SEL 記錄的結構(16 Bytes)
以第 2 筆為例:02 00 02 05 C6 E5 6A 20 00 04 08 51 6F 03 FF FF
| Byte | 值 | 欄位 | 解讀 |
|---|---|---|---|
| 0 ~ 1 | 02 00 | Record ID | 第 2 筆 |
| 2 | 02 | Record Type | 02h = 系統事件(C0h 以上為 OEM 記錄) |
| 3 ~ 6 | 05 C6 E5 6A | Timestamp | 6AE5C605h = 1,793,443,333 秒 → 2026-10-31 10:42:13 UTC |
| 7 ~ 8 | 20 00 | Generator ID | 由 BMC(20h)產生 |
| 9 | 04 | EvM Rev | IPMI 1.5 / 2.0 事件格式 |
| 10 | 08 | Sensor Type | 08h = Power Supply |
| 11 | 51 | Sensor Number | 51h,對照 SDR 得知是「PSU1 Status」 |
| 12 | 6F | Event Dir / Type | b7 = 0:Assertion(發生);b[6:0] = 6Fh:Sensor-specific 事件 |
| 13 | 03 | Event Data 1 | b[3:0] = Offset 3 → 電源事件表的「input lost」 |
| 14 ~ 15 | FF FF | Event Data 2 / 3 | 未使用 |
時間戳記
Timestamp 是從 1970-01-01 00:00 UTC 起算的秒數(和 Unix Time 相同;注意這和 FRU 的製造日期從 1996 年起算不一樣)。
Assertion 與 Deassertion
Byte 12 的 b7:
| b7 | 意思 | 例子 |
|---|---|---|
| 0 | Assertion:狀態發生 | AC 斷電 |
| 1 | Deassertion:狀態解除 | AC 恢復 |
第 3 筆的 Byte 12 是 EFh(1110 1111):b7 = 1(解除)、事件類型仍是 6Fh。同一個事件的發生與解除兩筆相減,就是持續時間:10:55:40 − 10:42:13 ≈ 13 分鐘。
Event Type:門檻型 vs 專用型
Byte 12 的 b[6:0] 決定 Event Data 1 的 Offset 要查哪一張表:
| Event Type | 意義 | Offset 查哪張表 |
|---|---|---|
01h | 門檻型(溫度、電壓、功率等有數值的感測器) | 門檻事件表 |
6Fh | Sensor-specific(依 Sensor Type 而定) | Sensor Type 08h → 電源事件表 |
02h ~ 0Ch | 通用離散事件 | 規格中的通用事件表 |
電源供應器事件(Sensor Type 08h)
| Offset | 事件 | 電源端常見的對應狀況 |
|---|---|---|
| 0 | Presence detected | 電源插入(PRESENT# 成立) |
| 1 | Power Supply Failure detected | STATUS_WORD 有故障位元、PWROK 消失 |
| 2 | Predictive Failure | 風扇轉速偏低、溫度警告等「快壞了」的徵兆 |
| 3 | Power Supply input lost (AC/DC) | 輸入斷電,例如電源線被拔、PDU 跳電 |
| 4 | Input lost or out-of-range | 輸入中斷或超出範圍 |
| 5 | Input out-of-range, but present | 有輸入但電壓超出範圍 |
| 6 | Configuration error | 設定錯誤,例如混插不同規格的電源(Event Data 3 記錄錯誤類型) |
「電源端的對應狀況」是常見的做法。STATUS_WORD 的哪個位元會觸發哪個事件,由 BMC 韌體決定,各平台不同,可以和 BMC 團隊確認對應表。
門檻事件(Event Type 01h)
| Offset | 事件 |
|---|---|
| 0 / 1 | Lower Non-critical going low / high |
| 2 / 3 | Lower Critical going low / high |
| 4 / 5 | Lower Non-recoverable going low / high |
| 6 / 7 | Upper Non-critical going low / high |
| 8 / 9 | Upper Critical going low / high |
| 10 / 11 | Upper Non-recoverable going low / high |
門檻事件的 Event Data 1 高 4 位元說明 Data 2、3 的用途。以第 4 筆為例:01 57 42 41
| Byte | 值 | 解讀 |
|---|---|---|
| 12 | 01 | Assertion,門檻型事件 |
| 13 | 57 = 0101 0111 | b[7:6] = 01:Data 2 是觸發時的讀值;b[5:4] = 01:Data 3 是觸發的門檻;b[3:0] = 7 → Upper Non-critical going high |
| 14 | 42 | 觸發時的原始讀值 |
| 15 | 41 | 觸發的門檻原始值 |
原始值要用該感測器的 SDR 公式換算。「PSU1 Temp」的 SDR 是 M = 1,所以 42h = 66 °C、41h = 65 °C:溫度 66 °C 越過了 65 °C 的警告門檻。
用 ipmitool 操作 SEL
ipmitool sel info # 筆數、容量、是否已滿、最後新增時間
ipmitool sel elist # 列出所有事件(會查 SDR,顯示感測器名稱)
ipmitool sel get 4 # 第 4 筆的完整內容,包含原始 Event Data
ipmitool sel clear # 清除全部紀錄(無法復原)
用 Python 解析 SEL
以下程式解析 SEL 原始資料(每 16 Bytes 一筆),處理時間戳記、Assertion / Deassertion、電源事件與門檻事件:
#!/usr/bin/env python3
"""sel_decode.py:解析 IPMI SEL 記錄(每筆 16 Bytes)"""
import sys
from datetime import datetime, timezone
SENSOR_TYPES = {0x01: "Temperature", 0x02: "Voltage", 0x03: "Current", 0x04: "Fan",
0x08: "Power Supply"}
# 感測器編號 → 名稱:實際對應關係來自 BMC 的 SDR,這裡只是範例
SENSOR_NAMES = {0x51: "PSU1 Status", 0x52: "PSU1 Temp"}
THRESHOLD_EVENTS = [
"Lower Non-critical going low", "Lower Non-critical going high",
"Lower Critical going low", "Lower Critical going high",
"Lower Non-recoverable going low", "Lower Non-recoverable going high",
"Upper Non-critical going low", "Upper Non-critical going high",
"Upper Critical going low", "Upper Critical going high",
"Upper Non-recoverable going low", "Upper Non-recoverable going high",
]
POWER_SUPPLY_EVENTS = [
"Presence detected", "Power Supply Failure detected", "Predictive Failure",
"Power Supply input lost (AC/DC)", "Power Supply input lost or out-of-range",
"Power Supply input out-of-range, but present", "Configuration error",
]
def decode_event(sensor_type, event_type, offset):
if event_type == 0x01: # 門檻型感測器
return THRESHOLD_EVENTS[offset] if offset < len(THRESHOLD_EVENTS) else f"offset {offset}"
if event_type == 0x6F and sensor_type == 0x08: # 電源供應器專用事件
return POWER_SUPPLY_EVENTS[offset] if offset < len(POWER_SUPPLY_EVENTS) else f"offset {offset}"
return f"event type {event_type:02X}h, offset {offset}"
def decode_record(r):
record_id = r[0] | r[1] << 8
if r[2] != 0x02:
return f"{record_id:4d} | OEM 記錄(type {r[2]:02X}h): {r[3:].hex(' ').upper()}"
seconds = r[3] | r[4] << 8 | r[5] << 16 | r[6] << 24
if seconds <= 0x20000000:
when = f"開機後 {seconds} 秒(BMC 時間尚未設定)"
else:
when = datetime.fromtimestamp(seconds, timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
sensor_type, sensor_num = r[10], r[11]
deassert = r[12] & 0x80
event_type = r[12] & 0x7F
data1, data2, data3 = r[13], r[14], r[15]
text = decode_event(sensor_type, event_type, data1 & 0x0F)
name = SENSOR_NAMES.get(sensor_num, f"Sensor #{sensor_num:02X}h")
line = (f"{record_id:4d} | {when} | {SENSOR_TYPES.get(sensor_type, f'{sensor_type:02X}h')} "
f"{name} | {text} | {'Deasserted' if deassert else 'Asserted'}")
if event_type == 0x01 and data1 >> 6 == 0b01: # Event Data 2 = 觸發時的讀值
line += f" | 讀值 {data2:02X}h,門檻 {data3:02X}h(用 SDR 換算)"
return line
if __name__ == "__main__":
with open(sys.argv[1], "rb") as f:
data = f.read()
for i in range(0, len(data) - 15, 16):
print(decode_record(data[i:i + 16]))
執行 python3 sel_decode.py psu_sel.bin 的結果,就是本文開頭「解析後」的那 4 行。
電源工程師要知道的事
| 現象 | 可能原因與建議 |
|---|---|
| SEL 被同一個電源事件洗版 | 電源的 STATUS 位元或 PWROK 反覆跳動,BMC 每次都記一筆。檢查電源端的去彈跳與狀態判斷 |
| SEL 滿了,新事件沒記錄 | 洗版或長期未清理;先存檔再清除,並找出洗版的來源 |
| 只有 Assertion,沒有 Deassertion | 狀態沒有恢復,或電源端在恢復時沒有清除對應的 STATUS 位元 |
| AC 斷電後沒有 input lost 事件 | 待機電源在 BMC 讀到狀態前就消失;檢查電源在 Hold-up 期間的狀態更新時序 |
| 時間線對不上 | BMC 時鐘未同步、Pre-Init 時間戳記,或 UTC 與本地時間混用 |
結論
- SEL 是 BMC 的事件黑盒子,每筆 16 Bytes,關機也會保留。
- 關鍵欄位:Timestamp(1970 年起算秒數)、Sensor Type / Number、Event Dir / Type、Event Data 1 ~ 3。
- b7 = 0 發生、b7 = 1 解除,兩者相減就是持續時間。
- 電源事件(Sensor Type
08h):Presence、Failure、Predictive Failure、Input lost 等。 - 門檻事件的 Event Data 2 / 3 記錄觸發讀值與門檻,要用 SDR 換算。
- 清除 SEL 前先存檔。
延伸閱讀: