diff --git a/CLAUDE.md b/CLAUDE.md index 82cf5d9..f02e433 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,199 +1,124 @@ # LOGPile - Инструкции для Claude Code -## Описание проекта +## Что это за проект -Приложение для анализа диагностической информации с BMC серверов (IPMI). -Представляет собой standalone Go-бинарник со встроенным веб-интерфейсом. +LOGPile - standalone Go-приложение для анализа BMC/IPMI диагностических архивов с веб-интерфейсом. +Приложение запускает локальный HTTP-сервер, парсит архив, автоматически выбирает подходящий parser по vendor и показывает результат в UI + экспортирует данные. -### Функциональность +## Актуальная архитектура -**Входные данные:** -- Архив (tar.gz/zip) с диагностическими данными IPMI сервера +- Язык: Go 1.22+ +- HTTP: стандартный `net/http` + `http.ServeMux` +- UI: embedded (`//go:embed`) HTML/CSS/Vanilla JS +- Бинарник: один executable, без внешних зависимостей на runtime +- Порт по умолчанию: `8082` (а не 8080) -**Обработка:** -- Парсинг System Event Log (SEL) - журнал событий IPMI -- Парсинг FRU (Field Replaceable Unit) - серийные номера компонентов -- Парсинг конфигурации сервера (CPU, RAM, диски, и т.д.) - -**Выходные данные:** -- Веб-интерфейс с человекочитаемой информацией -- Экспорт логов в TXT/JSON -- Экспорт конфигурации в JSON -- Экспорт серийных номеров в CSV - -## Архитектура - -- **Тип:** Standalone бинарник с embedded веб-сервером -- **Язык:** Go -- **UI:** Embedded HTML + CSS + Vanilla JS -- **Порт:** localhost:8080 (по умолчанию) - -## Структура проекта +## Реальная структура репозитория ``` logpile/ -├── cmd/logpile/main.go # Точка входа +├── cmd/logpile/main.go ├── internal/ -│ ├── parser/ # Парсинг архивов и IPMI данных -│ ├── models/ # Модели данных -│ ├── analyzer/ # Логика анализа -│ ├── exporter/ # Экспорт данных -│ └── server/ # HTTP сервер и handlers -├── web/ # Embedded веб-интерфейс -│ ├── static/ # CSS, JS, изображения -│ └── templates/ # HTML шаблоны -├── testdata/ # Примеры архивов для тестов -├── go.mod +│ ├── analyzer/ +│ ├── exporter/ +│ ├── models/ +│ ├── parser/ +│ │ └── vendors/ +│ │ ├── generic/ +│ │ ├── inspur/ +│ │ ├── nvidia/ +│ │ ├── nvidia_bug_report/ +│ │ └── supermicro/ +│ └── server/ +├── web/ +│ ├── static/ +│ └── templates/ ├── Makefile -└── README.md +└── go.mod ``` -## Технический стек - -### Backend -- Go 1.22+ -- Стандартная библиотека (net/http, archive/tar, compress/gzip) -- embed для встраивания веб-ресурсов - -### Frontend -- Vanilla JavaScript -- CSS (встроенный в бинарник) -- Без сборщиков - всё embedded в бинарник - -### Парсинг IPMI -- SEL формат: текстовый вывод `ipmitool sel list` или бинарный -- FRU формат: вывод `ipmitool fru print` -- Конфигурация: различные текстовые файлы из архива - -## Реализованные функции - -### 1. Базовая структура -- [x] Создана структура директорий -- [x] go.mod инициализирован -- [x] Makefile создан - -### 2. Парсер архивов -- [x] Распаковка tar.gz -- [x] Распаковка zip -- [x] Определение типов файлов внутри архива - -### 3. Парсеры IPMI данных -- [x] SEL parser (System Event Log) -- [x] FRU parser (серийные номера) -- [x] Config parser (конфигурация сервера) -- [x] Поддержка нескольких производителей (Supermicro, Inspur, Nvidia, etc.) - -### 4. Модели данных -- [x] Event (события из SEL) -- [x] Hardware (конфигурация) -- [x] SerialNumber (серийники компонентов) - -### 5. Веб-сервер -- [x] HTTP сервер с embedded файлами -- [x] Upload handler для архивов -- [x] API endpoints для получения данных -- [x] Handlers для экспорта - -### 6. Веб-интерфейс -- [x] Главная страница с upload формой -- [x] Отображение событий (timeline/таблица) -- [x] Отображение конфигурации -- [x] Таблица серийных номеров -- [x] Кнопки экспорта - -### 7. Экспортеры -- [x] CSV экспорт (серийники) -- [x] JSON экспорт (конфиг, события) -- [x] TXT отчет (логи) - -### 8. Тестирование и сборка -- [x] Unit тесты для парсеров -- [x] Интеграционные тесты -- [x] Cross-platform сборка (Linux, Windows, Mac) - -## Примеры использования +## CLI и запуск (актуально) ```bash # Сборка make build -# Запуск веб-сервера -./bin/logpile serve +# Запуск (авто-открытие браузера включено) +./bin/logpile -# Открыть в браузере -open http://localhost:8080 +# Явный порт +./bin/logpile --port 8082 -# С указанием порта -./bin/logpile serve --port 9000 +# Не открывать браузер автоматически +./bin/logpile --no-browser -# С предзагрузкой файла -./bin/logpile serve --file /path/to/bmc-archive.tar.gz +# Версия +./bin/logpile --version ``` -## Формат данных IPMI +Важно: сейчас **нет** subcommand `serve`, запуск идёт напрямую через флаги. -### SEL (System Event Log) -``` -SEL Record ID : 0001 - Record Type : 02 - Timestamp : 01/15/2025 14:23:45 - Generator ID : 0020 - EvM Revision : 04 - Sensor Type : Temperature - Sensor Number : 01 - Event Type : Threshold - Event Direction : Assertion Event - Event Data : 010000 - Description : Upper Critical - going high -``` +## Основной runtime-flow -### FRU (Field Replaceable Unit) -``` -FRU Device Description : Builtin FRU Device (ID 0) - Board Mfg Date : Mon Jan 1 00:00:00 1996 - Board Mfg : Supermicro - Board Product : X11DPH-T - Board Serial : WM194S001234 - Board Part Number : X11DPH-TQ -``` +1. `main.go` регистрирует embedded web FS и запускает сервер. +2. `POST /api/upload` принимает архив и передаёт его в `parser.BMCParser`. +3. `DetectFormat()` выбирает parser с максимальным confidence. +4. Результат сохраняется в памяти (`Server.result`) и отдаётся через API. +5. UI строит вкладки: конфигурация, прошивки, сенсоры, серийники, события. -## API Endpoints +## Поддерживаемые parser modules + +- `supermicro` - Supermicro parser +- `inspur` - Inspur/Kaytus parser +- `nvidia` - NVIDIA Field Diagnostics parser +- `nvidia_bug_report` - parser для `nvidia-bug-report.sh` +- `generic` - fallback parser + +Реестр parser-ов: `internal/parser/registry.go`, подключение модулей: `internal/parser/vendors/vendors.go`. + +## API (фактически в коде) ``` -POST /api/upload # Загрузить архив -GET /api/status # Получить статус парсинга -GET /api/parsers # Получить список доступных парсеров -GET /api/events # Получить список событий -GET /api/sensors # Получить показания сенсоров -GET /api/config # Получить конфигурацию -GET /api/serials # Получить серийные номера -GET /api/firmware # Получить версии прошивок -GET /api/export/csv # Экспорт в CSV -GET /api/export/json # Экспорт в JSON -GET /api/export/txt # Экспорт текстового отчета -DELETE /api/clear # Очистить загруженные данные -POST /api/shutdown # Завершить работу приложения +POST /api/upload +GET /api/status +GET /api/parsers +GET /api/events +GET /api/sensors +GET /api/config +GET /api/serials +GET /api/firmware +GET /api/export/csv +GET /api/export/json +GET /api/export/txt +DELETE /api/clear +POST /api/shutdown ``` -## Поддерживаемые производители +## Форматы данных и экспорт -- Supermicro -- Inspur/Kaytus -- Nvidia -- Generic (fallback) +- `AnalysisResult` агрегирует: events, sensors, FRU, hardware. +- Экспорт реализован в `internal/exporter/exporter.go`: + - CSV: серийные номера компонентов + - JSON: полный `AnalysisResult` + - TXT: человекочитаемый отчёт -## Следующие шаги +## Важные текущие ограничения (чтобы не ошибаться в задачах) -1. Добавление поддержки других производителей BMC (Dell iDRAC, HP iLO, Lenovo XCC) -2. Расширение функционала экспорта данных -3. Добавление фильтрации и сортировки данных в UI -4. Улучшение производительности парсинга больших архивов -5. Добавление поддержки других форматов IPMI данных +- Upload через `/api/upload` использует `ParseFromReader()`, где сейчас поддержаны `.tar`, `.tar.gz`, `.tgz`. +- Код распаковки `.zip` есть, но в текущем upload-пути `zip` не обрабатывается. +- Флаг `--file` присутствует в CLI-конфиге, но preload в `Server.Run()` сейчас не выполняется. +- Данные хранятся только в памяти процесса; перезапуск очищает состояние. -## Примечания +## Практические рекомендации для доработок -- Все файлы веб-интерфейса должны быть embedded в бинарник через `//go:embed` -- Приоритет на простоту и минимум зависимостей -- Безопасность: валидация загружаемых архивов (размер, типы файлов) -- UI должен быть простым и функциональным, не перегруженным -- Поддержка русского языка в интерфейсе \ No newline at end of file +- Если меняется parser-логика, обновляй `Version()` соответствующего parser-модуля. +- Новые vendor-парсеры регистрируй через import в `internal/parser/vendors/vendors.go`. +- Для API/контрактов проверяй согласованность `handlers.go` и `web/static/js/app.js`. +- Для UI-изменений не забывай, что ассеты embedded через `web/embed.go`. + +## Приоритетные следующие шаги + +1. Довести поддержку `zip` в upload path (`ParseFromReader`). +2. Реализовать preload из `--file`. +3. Добавить/актуализировать автотесты для parser и HTTP handlers. +4. Расширить vendor coverage (Dell/HPE/Lenovo) по реальным дампам. diff --git a/logpile b/logpile index 226d509..51ba1be 100755 Binary files a/logpile and b/logpile differ diff --git a/quick_test.go b/quick_test.go new file mode 100644 index 0000000..802515c --- /dev/null +++ b/quick_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "log" + + "git.mchus.pro/mchus/logpile/internal/parser" + _ "git.mchus.pro/mchus/logpile/internal/parser/vendors" +) + +func main() { + p := parser.NewBMCParser() + + fmt.Println("Testing archive parsing...") + if err := p.ParseArchive("example/A514359X5A07900_logs-20260122-074208.tar"); err != nil { + log.Fatalf("ERROR: %v", err) + } + + fmt.Println("✓ Archive parsed successfully!") + fmt.Printf("✓ Detected vendor: %s\n", p.DetectedVendor()) + + result := p.Result() + fmt.Printf("✓ GPUs found: %d\n", len(result.Hardware.GPUs)) + fmt.Printf("✓ Events found: %d\n", len(result.Events)) + fmt.Printf("✓ PCIe Devices found: %d\n", len(result.Hardware.PCIeDevices)) + + fmt.Println("\nBoard Info:") + fmt.Printf(" Manufacturer: %s\n", result.Hardware.BoardInfo.Manufacturer) + fmt.Printf(" Product Name: %s\n", result.Hardware.BoardInfo.ProductName) + fmt.Printf(" Serial Number: %s\n", result.Hardware.BoardInfo.SerialNumber) + fmt.Printf(" Part Number: %s\n", result.Hardware.BoardInfo.PartNumber) +} diff --git a/test_nvidia_full b/test_nvidia_full new file mode 100755 index 0000000..09ddf49 Binary files /dev/null and b/test_nvidia_full differ diff --git a/test_nvidia_full.go b/test_nvidia_full.go new file mode 100644 index 0000000..6cf3174 --- /dev/null +++ b/test_nvidia_full.go @@ -0,0 +1,96 @@ +package main + +import ( + "fmt" + "log" + + "git.mchus.pro/mchus/logpile/internal/parser" + _ "git.mchus.pro/mchus/logpile/internal/parser/vendors" +) + +func main() { + p := parser.NewBMCParser() + + fmt.Println("Testing NVIDIA Bug Report parser (full)...") + if err := p.ParseArchive("/Users/mchusavitin/Downloads/nvidia-bug-report-2KD501412.log.gz"); err != nil { + log.Fatalf("ERROR: %v", err) + } + + fmt.Println("✓ Archive parsed successfully!") + fmt.Printf("✓ Detected vendor: %s\n", p.DetectedVendor()) + + result := p.Result() + fmt.Printf("✓ CPUs: %d\n", len(result.Hardware.CPUs)) + fmt.Printf("✓ Memory: %d modules\n", len(result.Hardware.Memory)) + fmt.Printf("✓ Power Supplies: %d\n", len(result.Hardware.PowerSupply)) + fmt.Printf("✓ GPUs: %d\n", len(result.Hardware.GPUs)) + fmt.Printf("✓ Network Adapters: %d\n", len(result.Hardware.NetworkAdapters)) + + fmt.Println("\nSystem Information:") + if result.Hardware.BoardInfo.SerialNumber != "" { + fmt.Printf(" Serial Number: %s\n", result.Hardware.BoardInfo.SerialNumber) + } + if result.Hardware.BoardInfo.UUID != "" { + fmt.Printf(" UUID: %s\n", result.Hardware.BoardInfo.UUID) + } + if result.Hardware.BoardInfo.Manufacturer != "" { + fmt.Printf(" Manufacturer: %s\n", result.Hardware.BoardInfo.Manufacturer) + } + if result.Hardware.BoardInfo.ProductName != "" { + fmt.Printf(" Product: %s\n", result.Hardware.BoardInfo.ProductName) + } + if result.Hardware.BoardInfo.Version != "" { + fmt.Printf(" Version: %s\n", result.Hardware.BoardInfo.Version) + } + + fmt.Println("\nCPU Information:") + for _, cpu := range result.Hardware.CPUs { + fmt.Printf(" Socket %d: %s\n", cpu.Socket, cpu.Model) + fmt.Printf(" S/N: %s, Cores: %d, Threads: %d\n", cpu.SerialNumber, cpu.Cores, cpu.Threads) + } + + fmt.Println("\nPower Supplies:") + for _, psu := range result.Hardware.PowerSupply { + fmt.Printf(" %s: %s (%s)\n", psu.Slot, psu.Model, psu.Vendor) + fmt.Printf(" S/N: %s\n", psu.SerialNumber) + fmt.Printf(" Power: %d W, Revision: %s\n", psu.WattageW, psu.Firmware) + fmt.Printf(" Status: %s\n", psu.Status) + } + + totalMemGB := 0 + for _, mem := range result.Hardware.Memory { + totalMemGB += mem.SizeMB / 1024 + } + fmt.Printf("\nMemory: %d modules, %d GB total\n", len(result.Hardware.Memory), totalMemGB) + + fmt.Printf("\nNetwork Adapters: %d devices\n", len(result.Hardware.NetworkAdapters)) + for _, nic := range result.Hardware.NetworkAdapters { + fmt.Printf(" %s: %s\n", nic.Location, nic.Model) + if nic.Slot != "" { + fmt.Printf(" Slot: %s\n", nic.Slot) + } + if nic.PartNumber != "" { + fmt.Printf(" P/N: %s\n", nic.PartNumber) + } + if nic.SerialNumber != "" { + fmt.Printf(" S/N: %s\n", nic.SerialNumber) + } + if nic.PortCount > 0 { + fmt.Printf(" Ports: %d x %s\n", nic.PortCount, nic.PortType) + } + } + + fmt.Printf("\nGPUs: %d devices\n", len(result.Hardware.GPUs)) + for _, gpu := range result.Hardware.GPUs { + fmt.Printf(" %s: %s\n", gpu.BDF, gpu.Model) + if gpu.UUID != "" { + fmt.Printf(" UUID: %s\n", gpu.UUID) + } + if gpu.VideoBIOS != "" { + fmt.Printf(" Video BIOS: %s\n", gpu.VideoBIOS) + } + if gpu.IRQ > 0 { + fmt.Printf(" IRQ: %d\n", gpu.IRQ) + } + } +}