Skip to content

Support core dump analysis with pystack and gdb. - #39484

Open
tvalentyn wants to merge 10 commits into
apache:masterfrom
tvalentyn:pystack_coredump
Open

Support core dump analysis with pystack and gdb.#39484
tvalentyn wants to merge 10 commits into
apache:masterfrom
tvalentyn:pystack_coredump

Conversation

@tvalentyn

@tvalentyn tvalentyn commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Support analyzing core files with pystack when python SDK processes crash with segfaults.

Sample usages:

python my_pipeline.py --runner=DataflowRunner --profiler_agent=coredump --disk_size_gb=100

Stack traces from the crash diagnostics are saved into text files that are then synced to GCS. Excerpt of the analysis is published directly into logs: we attempt to find print the stacktrace for the thread that has the GIL and surface it; the complete analysis can be retrieved from GCS (--profle_location or gs://<temp_location>/profiles by default).

image

Arguments to pystack can be customized via the --profiler_extra_args pipeline options.
For more information, see: https://bloomberg.github.io/pystack/ and pystack --help .

Currently this will be supported with Dataflow from Beam 2.76.0 but it should be possible to extend support to other runners.

Requires either pystack, or optionally gdb be installed in the SDK container image.
Pystack might be able to provide most insight, but if that is not sufficient users can install gdb into the container image, then we also attempt to run various gdb analysis commands and save the output into a file.

To control how often core files are postprocessed and saved, use the --profile_upload_interval_sec and --profile_postprocess_interval_sec.

Core files are deleted after the analysis to save disk space. We don't dump cores on demand, but only perform a core analysis when when a crash happened.

The --profiler_stop_after_sec and --profiler_stop_after_crash options are also supported however it does not stop core files from being saved to disk, but stops the cleanup, which may be undesirable.

Sample analysis of a manufactured segfault (retrieved from GCS).

core.python.31-20260728181728.txt

core.python.31-20260728181728.gdb.txt


Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:

  • Mention the appropriate issue in your description (for example: addresses #123), if applicable. This will automatically add a link to the pull request in the issue. If you would like the issue to automatically close on merging the pull request, comment fixes #<ISSUE NUMBER> instead.
  • Update CHANGES.md with noteworthy changes.
  • If this contribution is large, please file an Apache Individual Contributor License Agreement.

See the Contributor Guide for more tips on how to make review process smoother.

To check the build health, please visit https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md

GitHub Actions Tests Status (on master branch)

Build python source distribution and wheels
Python tests
Java tests
Go tests

See CI.md for more information about GitHub Actions CI or the workflows README to see a list of phrases to trigger workflows.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions

Copy link
Copy Markdown
Contributor

Assigning reviewers:

R: @claudevdm for label python.

Note: If you would like to opt out of this review, comment assign to next reviewer.

Available commands:

  • stop reviewer notifications - opt out of the automated review tooling
  • remind me after tests pass - tag the comment author after tests pass
  • waiting on author - shift the attention set back to the author (any comment or push by the author will return the attention set to the reviewers)

The PR bot will only process comments in the main thread (not review comments).

@tvalentyn
tvalentyn marked this pull request as draft July 28, 2026 16:59
@tvalentyn
tvalentyn force-pushed the pystack_coredump branch 2 times, most recently from 62f50c9 to 2a68dfc Compare July 28, 2026 17:56
@tvalentyn
tvalentyn marked this pull request as ready for review July 28, 2026 18:06
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@tvalentyn tvalentyn changed the title [feat] Support core dump analysis with pystack. Support core dump analysis with pystack and gdb. Jul 29, 2026
Comment thread sdks/python/container/profiler.go Outdated
// options experiments (which is automatically set by the Python SDK). This ensures
// that core dumps are written in /tmp/ with a prefix matching "core.".
coreDir := "/tmp"
interval := 5 * time.Second

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is the default meant to be 5 seconds? It seems the pipeline option defaults to 60 sec

'--profile_postprocess_interval_sec',
type=int,
default=600,

So the 5 seconds will only survive if someone explicitly sets the pipeline option to 0, otherwise it will default to 600 sec?

Is 600 too long to catch segfault on startup?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah, this 5 second constant is useless, let's remove this altogether.

Re 600... not sure; probably won't eat a ton of CPU to do it more frequently, we can lower to 60sec. the ticker should not run concurrently, perhaps the issue might happen if the core cannot be processed or deleted, and keeps getting reprocessed again and again... hopefully that's not common

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

actually i'll keep it 600 sec since the same param is used by other profilers, which might be resource-intensive. Users can set the lower value manually but I suspect this will be good enough.

Comment thread sdks/python/container/profiler.go Outdated
return
}

prefix := "core."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is it a bit broad to match on any file starting with core.? Maybe we can add a bit more validation/regex?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good point, fortunately we control the pattern, so we can specify a more unique prefix. beam_py_coredump perhaps.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

changed the prefix.

logger.Warnf(ctx, "Python (worker %v) exited %v times: %v\nrestarting SDK process",
workerId, errorCount, err)
} else {
logger.Fatalf(ctx, "Python (worker %v) exited %v times: %v\nout of retries, failing container",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The last crash's core dump seems to depend on the ticker firing before Fatalf exits the container (default interval is 600s, and fresh cores <2s old are skipped).

Should we do one explicit core-dump sweep + GCS sync right before failing the container, so the crash that killed the worker is always analyzed regardless of timer timing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, the last crash is not super reliable... dataflow might also tear down the VM before processing finishes.

we could try to save core files into a semi-persist dir, which would survive container restarts, instead of /tmp/ . Let me check if that will work. I wary of increasing complexity of the boot.go.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a callback + explicit postprocessing before crashing the container.

case <-ticker.C:
processNewCoredumps(ctx, logger, pcfg, coreDir)
if isProfilerDisengaged(pcfg) {
return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this a race with the 2s file-age guard in processNewCoredumps? With --profiler_stop_after_crash.

Should we do a final synchronous sweep here that waits out the age guard (sleep >2s, re-run processNewCoredumps, sync results to GCS) before returning?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good catch, yes, it is a race and we should fix this corner case.

@tvalentyn tvalentyn Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Change the logic to wait until the age is older than 2 seconds.

Comment thread sdks/python/container/profiler.go Outdated
case <-ctx.Done():
return
case <-ticker.C:
processNewCoredumps(ctx, logger, pcfg, coreDir)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we syncProfilesToGCS here if the write was success?

@tvalentyn tvalentyn Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

currently profile postprocessing and upload are not coordinated (for this and other profilers). Since we write postprocessed content to a semi-persist directory, it should survive container restarts, so it should not be critical, and the logic is simpler. it is also possible to adjust the uploads to be more frequent.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants