diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go index ec2ac7ce65..42cff5ebea 100644 --- a/cmd/podman/system/events.go +++ b/cmd/podman/system/events.go @@ -54,6 +54,8 @@ type Event struct { // containerExitCode is for storing the exit code of a container which can // be used for "internal" event notification ContainerExitCode *int `json:",omitempty"` + // OOMKilled indicates whether the container was killed by an OOM condition + OOMKilled *bool `json:",omitempty"` // ID can be for the container, image, volume, etc ID string `json:",omitempty"` // Image used where applicable @@ -81,6 +83,7 @@ type Event struct { func newEventFromLibpodEvent(e *events.Event) Event { return Event{ ContainerExitCode: e.ContainerExitCode, + OOMKilled: e.OOMKilled, ID: e.ID, Image: e.Image, Name: e.Name, diff --git a/docs/source/markdown/podman-events.1.md b/docs/source/markdown/podman-events.1.md index 6658e67483..681e43d3a0 100644 --- a/docs/source/markdown/podman-events.1.md +++ b/docs/source/markdown/podman-events.1.md @@ -127,6 +127,7 @@ Format the output to JSON Lines or using the given Go template. | .Image | Name of image being run (string) | | .Name | Container name (string) | | .Network | Name of network being used (string) | +| .OOMKilled | Whether container was killed due to OOM (bool) | | .PodID | ID of pod associated with container, if any | | .Status | Event status (e.g., create, start, died, ...) | | .Time | Event timestamp (string) | diff --git a/docs/source/markdown/podman-inspect.1.md.in b/docs/source/markdown/podman-inspect.1.md.in index c88a1dba54..fb24a0332a 100644 --- a/docs/source/markdown/podman-inspect.1.md.in +++ b/docs/source/markdown/podman-inspect.1.md.in @@ -147,6 +147,12 @@ podman container inspect --latest --format {{.EffectiveCaps}} [CAP_CHOWN CAP_DAC_OVERRIDE CAP_FSETID CAP_FOWNER CAP_SETGID CAP_SETUID CAP_SETFCAP CAP_SETPCAP CAP_NET_BIND_SERVICE CAP_KILL] ``` +Inspect the specified container for the `OOMKilled` format specifier +``` +podman container inspect myContainer --format {{.State.OOMKilled}} +false +``` + Inspect the specified pod for the `Name` format specifier: ``` # podman inspect myPod --type pod --format "{{.Name}}" diff --git a/libpod/events.go b/libpod/events.go index 984d71f031..dc69d4abdf 100644 --- a/libpod/events.go +++ b/libpod/events.go @@ -105,9 +105,11 @@ func (c *Container) newContainerExitedEvent(exitCode int32) { intExitCode := int(exitCode) e.ContainerExitCode = &intExitCode - e.Details = events.Details{ - Attributes: c.Labels(), + attrs := c.Labels() + if c.state.OOMKilled { + e.OOMKilled = &c.state.OOMKilled } + e.Details = events.Details{Attributes: attrs} if err := c.runtime.eventer.Write(e); err != nil { logrus.Errorf("Unable to write container exited event: %q", err) diff --git a/libpod/events/config.go b/libpod/events/config.go index 053e7e3c37..f73854dd32 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -24,6 +24,8 @@ type Event struct { // ContainerExitCode is for storing the exit code of a container which can // be used for "internal" event notification ContainerExitCode *int `json:",omitempty"` + // OOMKilled indicates whether the container was killed by an OOM condition + OOMKilled *bool `json:",omitempty"` // ID can be for the container, image, volume, etc ID string `json:",omitempty"` // Image used where applicable diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go index 5497f2a422..21a77b9020 100644 --- a/libpod/events/journal_linux.go +++ b/libpod/events/journal_linux.go @@ -50,6 +50,9 @@ func (e EventJournalD) Write(ee Event) error { if ee.ContainerExitCode != nil { m["PODMAN_EXIT_CODE"] = strconv.Itoa(*ee.ContainerExitCode) } + if ee.OOMKilled != nil { + m["PODMAN_OOM_KILLED"] = strconv.FormatBool(*ee.OOMKilled) + } if ee.PodID != "" { m["PODMAN_POD_ID"] = ee.PodID } @@ -250,6 +253,14 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { newEvent.ContainerExitCode = &intCode } } + if val, ok := entry.Fields["PODMAN_OOM_KILLED"]; ok { + oomKilled, err := strconv.ParseBool(val) + if err != nil { + logrus.Errorf("Parsing event oom killed value %s", val) + } else { + newEvent.OOMKilled = &oomKilled + } + } if err := getLabelsFromJournal(entry, &newEvent); err != nil { return nil, err } diff --git a/pkg/domain/entities/events.go b/pkg/domain/entities/events.go index 236f248193..4ec362eedc 100644 --- a/pkg/domain/entities/events.go +++ b/pkg/domain/entities/events.go @@ -29,6 +29,16 @@ func ConvertToLibpodEvent(e Event) *libpodEvents.Event { if err != nil { return nil } + var ( + oomKilled bool + hasOOM bool + ) + if raw, ok := e.Actor.Attributes["oomKilled"]; ok { + if parsed, err := strconv.ParseBool(raw); err == nil { + oomKilled = parsed + hasOOM = true + } + } image := e.Actor.Attributes["image"] name := e.Actor.Attributes["name"] network := e.Actor.Attributes["network"] @@ -41,7 +51,8 @@ func ConvertToLibpodEvent(e Event) *libpodEvents.Event { delete(details, "podId") delete(details, "error") delete(details, "containerExitCode") - return &libpodEvents.Event{ + delete(details, "oomKilled") + newEvent := &libpodEvents.Event{ ContainerExitCode: &exitCode, ID: e.Actor.ID, Image: image, @@ -57,6 +68,10 @@ func ConvertToLibpodEvent(e Event) *libpodEvents.Event { Attributes: details, }, } + if hasOOM { + newEvent.OOMKilled = &oomKilled + } + return newEvent } // ConvertToEntitiesEvent converts a libpod event to an entities one. @@ -70,6 +85,9 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *types.Event { if e.ContainerExitCode != nil { attributes["containerExitCode"] = strconv.Itoa(*e.ContainerExitCode) } + if e.OOMKilled != nil { + attributes["oomKilled"] = strconv.FormatBool(*e.OOMKilled) + } attributes["podId"] = e.PodID if e.Network != "" { attributes["network"] = e.Network diff --git a/test/system/090-events.bats b/test/system/090-events.bats index e617608668..adc12ce92d 100644 --- a/test/system/090-events.bats +++ b/test/system/090-events.bats @@ -338,6 +338,24 @@ EOF assert "$output" = "$lvalue" "podman-events output includes container label" } +@test "events - died event contains OOMKilled attribute" { + local cname=c-$(safename) + + run_podman run -d --rm \ + --name $cname \ + --memory 20m \ + --cgroup-conf=memory.oom.group=1 \ + $IMAGE sh -c "x=a; while :; do x=\$x\$x\$x\$x; done" + run_podman wait $cname + run_podman events \ + --filter container=$cname \ + --filter event=died \ + --stream=false \ + --format "{{.OOMKilled}}" + + assert "$output" = "true" "OOMKilled attribute should be present and set to true" +} + # bats test_tags=ci:parallel @test "events - backend none should error" { skip_if_remote "remote does not support --events-backend"