Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion cmd/memparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ func showProcessMemorySizeTables(tasks []internal.Task) error {
// Function to recursively traverse the process tree and populate the table rows
var traverseTree func(*crit.PsTree, string, *[][]string) error
traverseTree = func(root *crit.PsTree, checkpointOutputDir string, rows *[][]string) error {
taskState := crit.TaskState(root.Core.GetTc().GetTaskState())

// Ignore zombies and dead processes as they don't have memory pages.
if !taskState.IsAliveOrStopped() {
return nil
}

memReader, err := crit.NewMemoryReader(
filepath.Join(checkpointOutputDir, metadata.CheckpointDirectory),
root.PID, pageSize,
Expand Down Expand Up @@ -195,12 +202,19 @@ func printProcessMemoryPages(task internal.Task) error {
return fmt.Errorf("failed to get process tree: %w", err)
}

// Check if PID exist within the checkpoint
// If PID=0, show all PIDs; otherwise, parse the specified PID's memory.
if *pID != 0 {
// Check if PID exist within the checkpoint
ps := psTree.FindPs(*pID)
if ps == nil {
return fmt.Errorf("no process with PID %d (use `inspect --ps-tree` to view all PIDs)", *pID)
}

// Check if the specified process has memory pages
taskState := crit.TaskState(psTree.Core.GetTc().GetTaskState())
if !taskState.IsAliveOrStopped() {
return fmt.Errorf("process %d has no memory pages (task state is zombie or dead)", ps.PID)
}
}

memReader, err := crit.NewMemoryReader(
Expand Down Expand Up @@ -315,6 +329,12 @@ func printMemorySearchResultForPID(task internal.Task) error {
return fmt.Errorf("no process with PID %d (use `inspect --ps-tree` to view all PIDs)", *pID)
}

// Check if the process has memory pages
taskState := crit.TaskState(ps.Core.GetTc().GetTaskState())
if !taskState.IsAliveOrStopped() {
return fmt.Errorf("process %d has no memory pages (task state is zombie or dead)", ps.PID)
}

memReader, err := crit.NewMemoryReader(
filepath.Join(task.OutputDir, metadata.CheckpointDirectory),
*pID, pageSize,
Expand Down
21 changes: 14 additions & 7 deletions internal/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ type StatsNode struct {
}

type PsNode struct {
PID uint32 `json:"pid"`
Comm string `json:"command"`
Cmdline string `json:"cmdline,omitempty"`
EnvVars map[string]string `json:"environment_variables,omitempty"`
Children []PsNode `json:"children,omitempty"`
PID uint32 `json:"pid"`
Comm string `json:"command"`
Cmdline string `json:"cmdline,omitempty"`
TaskState string `json:"task_state,omitempty"`
EnvVars map[string]string `json:"environment_variables,omitempty"`
Children []PsNode `json:"children,omitempty"`
}

type FdNode struct {
Expand Down Expand Up @@ -254,9 +255,15 @@ func buildJSONPsTree(psTree *crit.PsTree, checkpointOutputDir string) (PsNode, e
}

func buildJSONPsNode(psTree *crit.PsTree, checkpointOutputDir string) (PsNode, error) {
taskState := crit.TaskState(psTree.Core.GetTc().GetTaskState())
node := PsNode{
PID: psTree.PID,
Comm: psTree.Comm,
PID: psTree.PID,
Comm: psTree.Comm,
TaskState: taskState.String(),
}

if !taskState.IsAliveOrStopped() {
return node, nil
}

if PsTreeCmd {
Expand Down
33 changes: 21 additions & 12 deletions internal/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/checkpoint-restore/go-criu/v8/crit"
criu_core "github.com/checkpoint-restore/go-criu/v8/crit/images/criu-core"
spec "github.com/opencontainers/runtime-spec/specs-go"
)

Expand Down Expand Up @@ -112,21 +113,25 @@ func TestBuildJSONMounts(t *testing.T) {
}

func TestBuildJSONPsTree(t *testing.T) {
aliveState := uint32(1) // COMPEL_TASK_ALIVE
coreEntry := &criu_core.CoreEntry{Tc: &criu_core.TaskCoreEntry{TaskState: &aliveState}}
psTree := &crit.PsTree{
PID: 1,
Comm: "root",
Core: coreEntry,
Children: []*crit.PsTree{
{PID: 2, Comm: "child1"},
{PID: 3, Comm: "child2"},
{PID: 2, Comm: "child1", Core: coreEntry},
{PID: 3, Comm: "child2", Core: coreEntry},
},
}

expectedPsTree := PsNode{
PID: 1,
Comm: "root",
PID: 1,
Comm: "root",
TaskState: "Alive",
Children: []PsNode{
{PID: 2, Comm: "child1"},
{PID: 3, Comm: "child2"},
{PID: 2, Comm: "child1", TaskState: "Alive"},
{PID: 3, Comm: "child2", TaskState: "Alive"},
},
}

Expand All @@ -141,21 +146,25 @@ func TestBuildJSONPsTree(t *testing.T) {
}

func TestBuildJSONPsNode(t *testing.T) {
aliveState := uint32(1) // COMPEL_TASK_ALIVE
coreEntry := &criu_core.CoreEntry{Tc: &criu_core.TaskCoreEntry{TaskState: &aliveState}}
psTree := &crit.PsTree{
PID: 1,
Comm: "root",
Core: coreEntry,
Children: []*crit.PsTree{
{PID: 2, Comm: "child1"},
{PID: 3, Comm: "child2"},
{PID: 2, Comm: "child1", Core: coreEntry},
{PID: 3, Comm: "child2", Core: coreEntry},
},
}

expectedPsNode := PsNode{
PID: 1,
Comm: "root",
PID: 1,
Comm: "root",
TaskState: "Alive",
Children: []PsNode{
{PID: 2, Comm: "child1"},
{PID: 3, Comm: "child2"},
{PID: 2, Comm: "child1", TaskState: "Alive"},
{PID: 3, Comm: "child2", TaskState: "Alive"},
},
}

Expand Down
30 changes: 29 additions & 1 deletion internal/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"path/filepath"
"sort"
"strings"
"time"

Expand Down Expand Up @@ -175,7 +176,28 @@ func addPsTreeToTree(
// processes as child nodes of the branch.
var processNodes func(treeprint.Tree, *crit.PsTree) error
processNodes = func(tree treeprint.Tree, root *crit.PsTree) error {
node := tree.AddMetaBranch(root.PID, root.Comm)
var metaBranchTag string

taskState := crit.TaskState(root.Core.GetTc().GetTaskState())
if taskState.IsAlive() {
metaBranchTag = fmt.Sprintf("%d", root.PID)
} else {
metaBranchTag = fmt.Sprintf("%d (%s)", root.PID, taskState.String())
}

node := tree.AddMetaBranch(metaBranchTag, root.Comm)

// Skip dead or zombie processes as they do not have other state, and
// their children are inherited by init or the nearest subreaper process.
if !taskState.IsAliveOrStopped() {
return nil
}

// Sort children by PID for consistent output
sort.Slice(root.Children, func(i, j int) bool {
return root.Children[i].PID < root.Children[j].PID
})

// attach environment variables to process
if PsTreeEnv {
envVars, err := getPsEnvVars(checkpointOutputDir, root.PID)
Expand Down Expand Up @@ -286,13 +308,19 @@ func showSockets(sks []*crit.Sk, node treeprint.Tree, root *crit.PsTree) error {
// Recursively updates the Comm field of the psTree struct with the command line arguments
// from process memory pages
func updatePsTreeCommToCmdline(checkpointOutputDir string, psTree *crit.PsTree) error {
taskState := crit.TaskState(psTree.Core.GetTc().GetTaskState())
if !taskState.IsAliveOrStopped() {
return nil
}

cmdline, err := getCmdline(checkpointOutputDir, psTree.PID)
if err != nil {
return err
}
if cmdline != "" {
psTree.Comm = cmdline
}

for _, child := range psTree.Children {
if err := updatePsTreeCommToCmdline(checkpointOutputDir, child); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test-junit: test-imgs
bats -F junit checkpointctl.bats > junit.xml

test-imgs: piggie/piggie
$(eval PID := $(shell export TEST_ENV=BAR TEST_ENV_EMPTY=; piggie/piggie --tcp-socket))
$(eval PID := $(shell export TEST_ENV=BAR TEST_ENV_EMPTY=; piggie/piggie --tcp-socket --zombie))
mkdir -p $@
$(CRIU) dump --tcp-established -v4 -o dump.log -D $@ -t $(PID) || cat $@/dump.log

Expand Down
8 changes: 7 additions & 1 deletion test/checkpointctl.bats
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,15 @@ function teardown() {
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . )
checkpointctl inspect "$TEST_TMP_DIR2"/test.tar --files
[ "$status" -eq 0 ]

[[ ${lines[11]} == *"[REG 0]"* ]]
[[ ${lines[25]} == *"[cwd]"* ]]
[[ ${lines[26]} == *"[root]"* ]]

[[ ${lines[27]} == *"[5 (Dead)] piggie-zombie"* ]]
[[ ${lines[28]} == *"[6 (Stopped)] stopped-child"* ]]
[[ ${lines[29]} == *"Open files"* ]]
[[ ${lines[33]} == *"[7] alive-child"* ]]
}

@test "Run checkpointctl inspect with tar file and --files and missing files.img" {
Expand Down Expand Up @@ -681,7 +687,7 @@ function teardown() {
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . )
checkpointctl memparse --search=PATH --context=10 "$TEST_TMP_DIR2"/test.tar --pid=1
[ "$status" -eq 0 ]
[[ ${lines[3]} == *"PATH"* ]]
[[ "$(printf '%s\n' "${lines[@]}")" == *"PATH"* ]]
}

@test "Run checkpointctl memparse with --search-regex='HOME=([^?]+)' " {
Expand Down
Loading
Loading