IPMI / BMC 系列約 6 分鐘

IPMI SEL 解析:電源事件紀錄怎麼看(電源工程師版)

IPMI SEL(System Event Log)逐位元組解析:16 Bytes 事件記錄的時間戳記、Sensor Type、Event Type、Assertion / Deassertion 與 Event Data,電源供應器事件(Presence、Failure、AC lost)與門檻事件怎麼解讀,如何用 SEL 重建故障時間線,附 Python 解析程式。

📘 參考規格書IPMI · Intelligent Platform Management Interface Specification v2.0原廠文件 ↗

這篇接在 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 ~ 102 00Record ID第 2 筆
202Record Type02h = 系統事件(C0h 以上為 OEM 記錄)
3 ~ 605 C6 E5 6ATimestamp6AE5C605h = 1,793,443,333 秒 → 2026-10-31 10:42:13 UTC
7 ~ 820 00Generator ID由 BMC(20h)產生
904EvM RevIPMI 1.5 / 2.0 事件格式
1008Sensor Type08h = Power Supply
1151Sensor Number51h,對照 SDR 得知是「PSU1 Status」
126FEvent Dir / Typeb7 = 0:Assertion(發生);b[6:0] = 6Fh:Sensor-specific 事件
1303Event Data 1b[3:0] = Offset 3 → 電源事件表的「input lost」
14 ~ 15FF FFEvent Data 2 / 3未使用

時間戳記

Timestamp 是從 1970-01-01 00:00 UTC 起算的秒數(和 Unix Time 相同;注意這和 FRU 的製造日期從 1996 年起算不一樣)。

Assertion 與 Deassertion

Byte 12 的 b7:

b7意思例子
0Assertion:狀態發生AC 斷電
1Deassertion:狀態解除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門檻型(溫度、電壓、功率等有數值的感測器)門檻事件表
6FhSensor-specific(依 Sensor Type 而定)Sensor Type 08h → 電源事件表
02h ~ 0Ch通用離散事件規格中的通用事件表

電源供應器事件(Sensor Type 08h)

Offset事件電源端常見的對應狀況
0Presence detected電源插入(PRESENT# 成立)
1Power Supply Failure detectedSTATUS_WORD 有故障位元、PWROK 消失
2Predictive Failure風扇轉速偏低、溫度警告等「快壞了」的徵兆
3Power Supply input lost (AC/DC)輸入斷電,例如電源線被拔、PDU 跳電
4Input lost or out-of-range輸入中斷或超出範圍
5Input out-of-range, but present有輸入但電壓超出範圍
6Configuration error設定錯誤,例如混插不同規格的電源(Event Data 3 記錄錯誤類型)

「電源端的對應狀況」是常見的做法。STATUS_WORD 的哪個位元會觸發哪個事件,由 BMC 韌體決定,各平台不同,可以和 BMC 團隊確認對應表。

門檻事件(Event Type 01h)

Offset事件
0 / 1Lower Non-critical going low / high
2 / 3Lower Critical going low / high
4 / 5Lower Non-recoverable going low / high
6 / 7Upper Non-critical going low / high
8 / 9Upper Critical going low / high
10 / 11Upper Non-recoverable going low / high

門檻事件的 Event Data 1 高 4 位元說明 Data 2、3 的用途。以第 4 筆為例:01 57 42 41

Byte值解讀
1201Assertion,門檻型事件
1357 = 0101 0111b[7:6] = 01:Data 2 是觸發時的讀值;b[5:4] = 01:Data 3 是觸發的門檻;b[3:0] = 7 → Upper Non-critical going high
1442觸發時的原始讀值
1541觸發的門檻原始值

原始值要用該感測器的 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 前先存檔。

延伸閱讀: