Skip to content

fix: Skip extra header columns in iostat parsing#4676

Open
InoMurko wants to merge 1 commit intoOffchainLabs:masterfrom
InoMurko:patch-1
Open

fix: Skip extra header columns in iostat parsing#4676
InoMurko wants to merge 1 commit intoOffchainLabs:masterfrom
InoMurko:patch-1

Conversation

@InoMurko
Copy link
Copy Markdown

Skip parsing when a stats row has fewer fields than the last Device header row.

It breaks when i >= len(data), i.e. when this data row has fewer whitespace-separated fields than the header has columns (or than we’ve consumed so far).

{
        "@timestamp": "2026-04-25 16:36:53.602",
        "@message": {
            "time": "2026-04-25T16:36:53.602931801Z",
            "stream": "stderr",
            "_p": "F",
            "log": "panic: runtime error: index out of range [7] with length 7",
            "kubernetes": {
                "pod_name": "sequencer-00-6bb98c996d-9npgf",
                "container_name": "nitro-sequencer-00"
            }
        },
        "kubernetes.pod_name": "sequencer-00-6bb98c996d-9npgf"
    },
    {
        "@timestamp": "2026-04-25 16:36:53.635",
        "@message": {
            "time": "2026-04-25T16:36:53.635989261Z",
            "stream": "stderr",
            "_p": "F",
            "log": "",
            "kubernetes": {
                "pod_name": "sequencer-00-6bb98c996d-9npgf",
                "container_name": "nitro-sequencer-00"
            }
        },
        "kubernetes.pod_name": "sequencer-00-6bb98c996d-9npgf"
    },
    {
        "@timestamp": "2026-04-25 16:36:53.636",
        "@message": {
            "time": "2026-04-25T16:36:53.636010651Z",
            "stream": "stderr",
            "_p": "F",
            "log": "goroutine 68 [running]:",
            "kubernetes": {
                "pod_name": "sequencer-00-6bb98c996d-9npgf",
                "container_name": "nitro-sequencer-00"
            }
        },
        "kubernetes.pod_name": "sequencer-00-6bb98c996d-9npgf"
    },
    {
        "@timestamp": "2026-04-25 16:36:53.695",
        "@message": {
            "time": "2026-04-25T16:36:53.695956669Z",
            "stream": "stderr",
            "_p": "F",
            "log": "github.com/offchainlabs/nitro/util/iostat.Run({0x373bce8, 0xc0005595e0}, 0x162e9a5?, 0xc0000a4f50)",
            "kubernetes": {
                "pod_name": "sequencer-00-6bb98c996d-9npgf",
                "container_name": "nitro-sequencer-00"
            }
        },
        "kubernetes.pod_name": "sequencer-00-6bb98c996d-9npgf"
    },
    {
        "@timestamp": "2026-04-25 16:36:53.696",
        "@message": {
            "time": "2026-04-25T16:36:53.696583445Z",
            "stream": "stderr",
            "_p": "F",
            "log": "\t/workspace/util/iostat/iostat.go:91 +0xa06",
            "kubernetes": {
                "pod_name": "sequencer-00-6bb98c996d-9npgf",
                "container_name": "nitro-sequencer-00"
            }
        },
        "kubernetes.pod_name": "sequencer-00-6bb98c996d-9npgf"
    },
    {
        "@timestamp": "2026-04-25 16:36:53.733",
        "@message": {
            "time": "2026-04-25T16:36:53.733730975Z",
            "stream": "stderr",
            "_p": "F",
            "log": "created by github.com/offchainlabs/nitro/util/iostat.RegisterAndPopulateMetrics in goroutine 59",
            "kubernetes": {
                "pod_name": "sequencer-00-6bb98c996d-9npgf",
                "container_name": "nitro-sequencer-00"
            }
        },
        "kubernetes.pod_name": "sequencer-00-6bb98c996d-9npgf"
    },
    {
        "@timestamp": "2026-04-25 16:36:53.733",
        "@message": {
            "time": "2026-04-25T16:36:53.733827792Z",
            "stream": "stderr",
            "_p": "F",
            "log": "\t/workspace/util/iostat/iostat.go:23 +0x12c",
            "kubernetes": {
                "pod_name": "sequencer-00-6bb98c996d-9npgf",
                "container_name": "nitro-sequencer-00"
            }
        },
        "kubernetes.pod_name": "sequencer-00-6bb98c996d-9npgf"
    },

Handle cases where header has extra columns in iostat.
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 27, 2026

CLA assistant check
All committers have signed the CLA.

@bragaigor bragaigor self-assigned this May 6, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

❌ Patch coverage is 0% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.09%. Comparing base (6dce8d1) to head (be655fb).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4676      +/-   ##
==========================================
+ Coverage   52.38%   59.09%   +6.70%     
==========================================
  Files         506      506              
  Lines       73739    60417   -13322     
==========================================
- Hits        38630    35703    -2927     
+ Misses      29951    19452   -10499     
- Partials     5158     5262     +104     

Copy link
Copy Markdown
Contributor

@bragaigor bragaigor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! Left a few comments to make it even more robust. Please let me know if you have any questions or need help with any of it. Ah you're also missing a changelog file :)

Comment thread util/iostat/iostat.go
Comment on lines +87 to +91
if i >= len(data) {
// Some iostat builds emit a header with more columns than a data row
// (e.g. trailing header tokens without values). Skip the rest of the header.
break
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we okay with "skipping" the missing field? The break plus partial send means any field whose column index >= len(data) gets published as 0.0 and overwrites the gauge. I think it would be overkill to track which fields are missing so maybe, a good medium, we could add a log.Warn so the failure is at least observable? Something like:

  if i >= len(data) {
      log.Warn("iostat row has fewer columns than header",
          "headerCols", len(fields), "dataCols", len(data),
          "header", fields, "row", data)
      break
  }

Comment thread util/iostat/iostat.go
stat := DeviceStats{}
var err error
for i, field := range fields {
if i >= len(data) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this fixes a production panic, it'd be great to add a regression test so a future refactor can't silently reintroduce the bug. The package has no _test.go file today so maybe we can introduce something like util/iostat/iostat_test.go. The current Run is awkward to unit-test because it shells out to iostat directly. One option is to extract the scanner/parse loop (roughly the body from var fields []string through the scanner.Err() check) into a helper:

func parseStream(r io.Reader, receiver chan<- DeviceStats)

Then Run becomes process-setup + parseStream(stdout, receiver) + teardown. A test can drive parseStream with strings.NewReader(...) against different inputs. A few cases worth covering:

  1. Well-formed row (baseline)
  2. The regression: header has more columns than the data row (the exact panic from the stack trace)
  3. A header that starts with Device: (older sysstat prints the trailing colon)

Happy to push a sketch if helpful.

Comment thread util/iostat/iostat.go
Comment on lines +88 to +89
// Some iostat builds emit a header with more columns than a data row
// (e.g. trailing header tokens without values). Skip the rest of the header.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: reword comment:

Suggested change
// Some iostat builds emit a header with more columns than a data row
// (e.g. trailing header tokens without values). Skip the rest of the header.
// Some iostat builds emit a header with more columns than a data row.
// Stop matching schema fields once the data row is exhausted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants