Skip to content

Commit c527f66

Browse files
Merge pull request #97 from amd/alex_dmesg_analysis
Bug fix: allow user to pass dmesg.log for analysis
2 parents 3b47144 + 729cd3f commit c527f66

File tree

4 files changed

+178
-3
lines changed

4 files changed

+178
-3
lines changed

nodescraper/interfaces/dataplugin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ def analyze(
252252
)
253253
return self.analysis_result
254254

255+
if data:
256+
self.data = data
257+
255258
if self.data is None:
256259
self.analysis_result = TaskResult(
257260
task=self.ANALYZER.__name__,
@@ -261,9 +264,6 @@ def analyze(
261264
)
262265
return self.analysis_result
263266

264-
if data:
265-
self.data = data
266-
267267
analyzer_task = self.ANALYZER(
268268
self.system_info,
269269
logger=self.logger,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
kern :info : 2026-01-07T10:00:00,123456-06:00 Linux version 5.15.0-91-generic (buildd@amd64-builder) (gcc version 11.4.0) #101-Ubuntu SMP
2+
kern :info : 2026-01-07T10:00:00,234567-06:00 Command line: BOOT_IMAGE=/boot/vmlinuz-5.15.0-91-generic root=UUID=a1b2c3d4 ro quiet splash vt.handoff=7
3+
kern :info : 2026-01-07T10:00:01,345678-06:00 KERNEL supported cpus:
4+
kern :info : 2026-01-07T10:00:01,456789-06:00 Intel GenuineIntel
5+
kern :info : 2026-01-07T10:00:01,567890-06:00 AMD AuthenticAMD
6+
kern :info : 2026-01-07T10:00:02,678901-06:00 x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
7+
kern :info : 2026-01-07T10:00:02,789012-06:00 x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
8+
kern :info : 2026-01-07T10:00:03,890123-06:00 Memory: 32823616K/33554432K available
9+
kern :warn : 2026-01-07T10:00:05,123456-06:00 pci 0000:00:01.0: BAR 0: failed to assign [mem size 0x01000000]
10+
kern :info : 2026-01-07T10:00:06,234567-06:00 PCI: Using ACPI for IRQ routing
11+
kern :info : 2026-01-07T10:00:07,345678-06:00 NetLabel: Initializing
12+
kern :info : 2026-01-07T10:00:08,456789-06:00 DMA: preallocated 4096 KiB GFP_KERNEL pool for atomic allocations
13+
kern :err : 2026-01-07T10:00:10,567890-06:00 WARNING: CPU: 0 PID: 1 at drivers/gpu/drm/amd/amdgpu/amdgpu_device.c:123 amdgpu_device_init+0x456/0x789
14+
kern :info : 2026-01-07T10:00:11,678901-06:00 Modules linked in: amdgpu drm_ttm_helper ttm drm_kms_helper
15+
kern :info : 2026-01-07T10:00:12,789012-06:00 CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.15.0-91-generic #101-Ubuntu
16+
kern :err : 2026-01-07T10:00:15,890123-06:00 AMD-Vi: Event logged [IO_PAGE_FAULT device=00:14.0 domain=0x0000 address=0xfffffffffffffef0 flags=0x0010]
17+
kern :info : 2026-01-07T10:00:16,123456-06:00 SCSI subsystem initialized
18+
kern :info : 2026-01-07T10:00:17,234567-06:00 libata version 3.00 loaded
19+
kern :info : 2026-01-07T10:00:18,345678-06:00 ACPI: Added _OSI(Module Device)
20+
kern :info : 2026-01-07T10:00:19,456789-06:00 ACPI: Added _OSI(Processor Device)
21+
kern :err : 2026-01-07T10:00:20,567890-06:00 ACPI Error: Method parse/execution failed \_SB.PCI0.GPP0.SWUS.SWDS.VGA.LCD._BCM, AE_NOT_FOUND
22+
kern :info : 2026-01-07T10:00:22,678901-06:00 [drm] amdgpu kernel modesetting enabled
23+
kern :info : 2026-01-07T10:00:23,789012-06:00 [drm] initializing kernel modesetting (NAVI21 0x1002:0x73BF)
24+
kern :info : 2026-01-07T10:00:25,890123-06:00 amdgpu 0000:03:00.0: amdgpu: Fetched VBIOS from VFCT
25+
kern :info : 2026-01-07T10:00:26,123456-06:00 amdgpu 0000:03:00.0: amdgpu: ATOM BIOS: 113-D4120100-O04
26+
kern :info : 2026-01-07T10:00:28,234567-06:00 [drm] GPU posting now...
27+
kern :warn : 2026-01-07T10:00:30,345678-06:00 [drm] *ERROR* Timeout waiting for DMCUB auto-load
28+
kern :info : 2026-01-07T10:00:32,456789-06:00 [drm] Display Core initialized with v3.2.149!
29+
kern :info : 2026-01-07T10:00:35,567890-06:00 [drm] VCN decode and encode initialized successfully
30+
kern :info : 2026-01-07T10:00:38,678901-06:00 [drm] fb0: amdgpudrmfb frame buffer device
31+
kern :info : 2026-01-07T10:00:40,789012-06:00 amdgpu 0000:03:00.0: amdgpu: ring gfx_0.0.0 uses VM inv eng 0 on hub 0
32+
kern :info : 2026-01-07T10:00:42,890123-06:00 [drm] Initialized amdgpu 3.42.0 20150101 for 0000:03:00.0 on minor 0
33+
kern :info : 2026-01-07T10:00:45,123456-06:00 EXT4-fs (nvme0n1p2): mounted filesystem with ordered data mode
34+
kern :info : 2026-01-07T10:00:48,234567-06:00 systemd[1]: systemd 249.11-0ubuntu3.6 running in system mode
35+
kern :info : 2026-01-07T10:00:50,345678-06:00 systemd[1]: Detected architecture x86-64
36+
kern :info : 2026-01-07T10:00:55,456789-06:00 audit: type=1400 audit(1704636055.456:2): apparmor="STATUS" operation="profile_load"
37+
kern :info : 2026-01-07T10:01:00,567890-06:00 Adding 33554428k swap on /swapfile
38+
kern :info : 2026-01-07T10:01:05,678901-06:00 IPv6: ADDRCONF(NETDEV_CHANGE): enp5s0: link becomes ready
39+
kern :info : 2026-01-07T10:01:10,789012-06:00 NFSD: Using UMH upcall client tracking operations
40+
kern :info : 2026-01-07T10:01:15,890123-06:00 NFSD: starting 90-second grace period (net f0000098)
41+
kern :info : 2026-01-07T10:01:20,123456-06:00 Bluetooth: BNEP (Ethernet Emulation) ver 1.3
42+
kern :info : 2026-01-07T10:01:25,234567-06:00 Bluetooth: BNEP filters: protocol multicast
43+
kern :info : 2026-01-07T10:01:30,345678-06:00 System operational - all services started successfully

test/functional/test_run_plugins.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
###############################################################################
2626
"""Functional tests for running individual plugins."""
2727

28+
import csv
29+
from pathlib import Path
30+
2831
import pytest
2932

3033
from nodescraper.pluginregistry import PluginRegistry
@@ -114,3 +117,58 @@ def test_run_comma_separated_plugins_with_invalid(run_cli_command):
114117
assert "Running plugin AmdSmiPlugin" in output
115118
# Verify it didn't crash
116119
assert "Data written to csv file" in output
120+
121+
122+
def test_run_plugin_with_data_file_no_collection(run_cli_command, tmp_path):
123+
"""Test running plugin with --data argument and --collection False."""
124+
fixtures_dir = Path(__file__).parent / "fixtures"
125+
dmesg_fixture = fixtures_dir / "dmesg_sample.log"
126+
127+
assert dmesg_fixture.exists(), f"Fixture file not found: {dmesg_fixture}"
128+
129+
analyze_log_path = str(tmp_path / "analyze_logs")
130+
result = run_cli_command(
131+
[
132+
"--log-path",
133+
analyze_log_path,
134+
"run-plugins",
135+
"DmesgPlugin",
136+
"--data",
137+
str(dmesg_fixture),
138+
"--collection",
139+
"False",
140+
],
141+
check=False,
142+
)
143+
144+
output = result.stdout + result.stderr
145+
assert (
146+
result.returncode == 1
147+
), f"Expected return code 1 (errors found), got: {result.returncode}"
148+
assert "Running data analyzer: DmesgAnalyzer" in output, "Analyzer should have run"
149+
assert "Data written to csv file" in output, "CSV file should be created"
150+
151+
if "Plugin tasks not ran" in output:
152+
pytest.fail(
153+
"Bug regression: Plugin reported 'tasks not ran' with --data file. "
154+
"Analysis should load data from --data parameter before checking if data is None."
155+
)
156+
157+
analyze_path = Path(analyze_log_path)
158+
csv_files = list(analyze_path.glob("*/nodescraper.csv"))
159+
assert len(csv_files) > 0, "CSV results file should exist"
160+
161+
csv_file = csv_files[0]
162+
with open(csv_file, "r", encoding="utf-8") as f:
163+
reader = csv.DictReader(f)
164+
rows = list(reader)
165+
166+
dmesg_rows = [row for row in rows if "DmesgPlugin" in row.get("plugin", "")]
167+
assert len(dmesg_rows) > 0, "DmesgPlugin should have results in CSV"
168+
169+
dmesg_row = dmesg_rows[0]
170+
status = dmesg_row.get("status", "")
171+
assert status != "NOT_RAN", (
172+
f"Bug regression: DmesgPlugin status is NOT_RAN with --data file. "
173+
f"Analysis should have run on provided data. Status: {status}"
174+
)

test/unit/framework/test_dataplugin.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,77 @@ def test_collect_preserve_connection(self, plugin_with_conn):
329329

330330
# Verify disconnect WAS called when preserve_connection=False
331331
mock_disconnect.assert_called_once()
332+
333+
def test_run_with_data_file_no_collection(self, plugin_with_conn, tmp_path):
334+
"""Test running plugin with data file and collection=False."""
335+
data_file = tmp_path / "test_data.json"
336+
data_file.write_text('{"value": "from_file"}')
337+
338+
with (
339+
patch.object(CoreDataPlugin, "collect") as mock_collect,
340+
patch.object(StandardAnalyzer, "analyze_data") as mock_analyze,
341+
):
342+
mock_analyze.return_value = TaskResult(status=ExecutionStatus.OK)
343+
344+
result = plugin_with_conn.run(collection=False, analysis=True, data=str(data_file))
345+
346+
mock_collect.assert_not_called()
347+
mock_analyze.assert_called_once()
348+
349+
call_args = mock_analyze.call_args
350+
analyzed_data = call_args[0][0]
351+
assert isinstance(analyzed_data, StandardDataModel)
352+
assert analyzed_data.value == "from_file"
353+
assert result.status == ExecutionStatus.OK
354+
assert plugin_with_conn.analysis_result.status == ExecutionStatus.OK
355+
356+
def test_run_with_data_dict_no_collection(self, plugin_with_conn):
357+
"""Test running plugin with data dict and collection=False."""
358+
data_dict = {"value": "from_dict"}
359+
360+
with (
361+
patch.object(CoreDataPlugin, "collect") as mock_collect,
362+
patch.object(StandardAnalyzer, "analyze_data") as mock_analyze,
363+
):
364+
mock_analyze.return_value = TaskResult(status=ExecutionStatus.OK)
365+
366+
result = plugin_with_conn.run(collection=False, analysis=True, data=data_dict)
367+
368+
mock_collect.assert_not_called()
369+
mock_analyze.assert_called_once()
370+
371+
call_args = mock_analyze.call_args
372+
analyzed_data = call_args[0][0]
373+
assert isinstance(analyzed_data, StandardDataModel)
374+
assert analyzed_data.value == "from_dict"
375+
assert result.status == ExecutionStatus.OK
376+
377+
def test_run_with_data_model_no_collection(self, plugin_with_conn):
378+
"""Test running plugin with data model instance and collection=False."""
379+
data_model = StandardDataModel(value="from_model")
380+
381+
with (
382+
patch.object(CoreDataPlugin, "collect") as mock_collect,
383+
patch.object(StandardAnalyzer, "analyze_data") as mock_analyze,
384+
):
385+
mock_analyze.return_value = TaskResult(status=ExecutionStatus.OK)
386+
387+
result = plugin_with_conn.run(collection=False, analysis=True, data=data_model)
388+
389+
mock_collect.assert_not_called()
390+
mock_analyze.assert_called_once()
391+
392+
call_args = mock_analyze.call_args
393+
analyzed_data = call_args[0][0]
394+
assert analyzed_data is data_model
395+
assert analyzed_data.value == "from_model"
396+
assert result.status == ExecutionStatus.OK
397+
398+
def test_analyze_no_data_available(self, plugin_with_conn):
399+
"""Test analyze returns NOT_RAN when no data is available."""
400+
plugin_with_conn._data = None
401+
402+
result = plugin_with_conn.analyze()
403+
404+
assert result.status == ExecutionStatus.NOT_RAN
405+
assert "No data available" in result.message

0 commit comments

Comments
 (0)