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
36 changes: 17 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,25 @@ To display an overview of a checkpoint archive you can just use
```console
$ checkpointctl show /tmp/dump.tar

+-----------------+------------------------------------------+--------------+---------+----------------------+--------+------------+-------------------+
| CONTAINER | IMAGE | ID | RUNTIME | CREATED | ENGINE | CHKPT SIZE | ROOT FS DIFF SIZE |
+-----------------+------------------------------------------+--------------+---------+----------------------+--------+------------+-------------------+
| magical_murdock | quay.io/adrianreber/wildfly-hello:latest | f11d11844af0 | crun | 2023-02-28T09:43:52Z | Podman | 338.2 MiB | 177.0 KiB |
+-----------------+------------------------------------------+--------------+---------+----------------------+--------+------------+-------------------+
Displaying container checkpoint data from /root/dump.tar

CONTAINER IMAGE ID RUNTIME CREATED ENGINE CHKPT SIZE ROOT FS DIFF SIZE
--------- ----- -- ------- ------- ------ ---------- -----------------
looper docker.io/library/busybox:latest 8b5c2ca15082 crun 2021-09-28T10:03:56Z Podman 130.8 KiB 204 B
```

For a checkpoint archive created by Kubernetes with *CRI-O* the output would
look like this:

```console
$ checkpointctl show /var/lib/kubelet/checkpoints/checkpoint-counters_default-counter-2023-02-13T16\:20\:09Z.tar
$ checkpointctl show /var/lib/kubelet/checkpoints/checkpoint-counters_default-counter-2025-05-22T14\:31\:35Z.tar

Displaying container checkpoint data from /var/lib/kubelet/checkpoints/checkpoint-counters_default-counter-2025-05-22T14:31:35Z.tar

CONTAINER IMAGE ID RUNTIME CREATED ENGINE IP CHKPT SIZE ROOT FS DIFF SIZE
--------- ----- -- ------- ------- ------ -- ---------- -----------------
counter quay.io/adrianreber/counter:latest 29ed106ef467 runc 2025-05-22T14:31:24.818422898Z CRI-O 10.0.0.70 9.2 MiB 2.0 KiB

+-----------+------------------------------------+--------------+---------+--------------------------------+--------+------------+------------+
| CONTAINER | IMAGE | ID | RUNTIME | CREATED | ENGINE | IP | CHKPT SIZE |
+-----------+------------------------------------+--------------+---------+--------------------------------+--------+------------+------------+
| counter | quay.io/adrianreber/counter:latest | 7eb9680287f1 | runc | 2023-02-13T16:12:25.843774934Z | CRI-O | 10.88.0.24 | 8.5 MiB |
+-----------+------------------------------------+--------------+---------+--------------------------------+--------+------------+------------+
```

### `inspect` sub-command
Expand Down Expand Up @@ -88,7 +89,7 @@ $ checkpointctl memparse /tmp/jira.tar.gz --pid=1 | less

Displaying memory pages content for Process ID 1 from checkpoint: /tmp/jira.tar.gz

Address Hexadecimal ASCII
ADDRESS HEXADECIMAL ASCII
-------------------------------------------------------------------------------------
00005633bb080000 f3 0f 1e fa 48 83 ec 08 48 8b 05 d1 4f 00 00 48 |....H...H...O..H|
00005633bb080010 85 c0 74 02 ff d0 48 83 c4 08 c3 00 00 00 00 00 |..t...H.........|
Expand Down Expand Up @@ -145,13 +146,10 @@ $ sudo checkpointctl memparse /tmp/jira.tar.gz

Displaying processes memory sizes from /tmp/jira.tar.gz

+-----+--------------+-------------+
| PID | PROCESS NAME | MEMORY SIZE |
+-----+--------------+-------------+
| 1 | tini | 100.0 KiB |
+-----+--------------+-------------+
| 2 | java | 553.5 MiB |
+-----+--------------+-------------+
PID PROCESS NAME MEMORY SIZE SHARED MEMORY SIZE
--- ------------ ----------- ------------------
1 tini 100.0 KiB 0 B
2 java 553.5 MiB 0 B
```

In this example, given the large size of the java process, it is better to write its output to a file.
Expand Down
14 changes: 7 additions & 7 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"time"

"github.com/checkpoint-restore/checkpointctl/internal"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
)

Expand All @@ -38,7 +37,7 @@ func list(cmd *cobra.Command, args []string) error {
}()
showTable := false

table := tablewriter.NewWriter(os.Stdout)
w := internal.GetNewTabWriter(os.Stdout)
header := []string{
"Namespace",
"Pod",
Expand All @@ -48,9 +47,7 @@ func list(cmd *cobra.Command, args []string) error {
"Checkpoint Name",
}

table.SetHeader(header)
table.SetAutoMergeCells(false)
table.SetRowLine(true)
var rows [][]string

for _, checkpointPath := range allPaths {
files, err := filepath.Glob(filepath.Join(checkpointPath, "checkpoint-*"))
Expand Down Expand Up @@ -81,7 +78,7 @@ func list(cmd *cobra.Command, args []string) error {
filepath.Base(file),
}

table.Append(row)
rows = append(rows, row)
}
}

Expand All @@ -90,6 +87,9 @@ func list(cmd *cobra.Command, args []string) error {
return nil
}

table.Render()
internal.WriteTableHeader(w, header)
internal.WriteTableRows(w, rows)

w.Flush()
return nil
}
50 changes: 26 additions & 24 deletions cmd/memparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/checkpoint-restore/checkpointctl/internal"
metadata "github.com/checkpoint-restore/checkpointctl/lib"
"github.com/checkpoint-restore/go-criu/v7/crit"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -116,21 +115,16 @@ func memparse(cmd *cobra.Command, args []string) error {

// Display processes memory sizes within the given container checkpoints.
func showProcessMemorySizeTables(tasks []internal.Task) error {
// Initialize the table
table := tablewriter.NewWriter(os.Stdout)
header := []string{
"PID",
"Process name",
"Memory size",
"Shared memory size",
}
table.SetHeader(header)
table.SetAutoMergeCells(false)
table.SetRowLine(true)

// Function to recursively traverse the process tree and populate the table rows
var traverseTree func(*crit.PsTree, string) error
traverseTree = func(root *crit.PsTree, checkpointOutputDir string) error {
var traverseTree func(*crit.PsTree, string, *[][]string) error
traverseTree = func(root *crit.PsTree, checkpointOutputDir string, rows *[][]string) error {
memReader, err := crit.NewMemoryReader(
filepath.Join(checkpointOutputDir, metadata.CheckpointDirectory),
root.PID, pageSize,
Expand All @@ -152,24 +146,25 @@ func showProcessMemorySizeTables(tasks []internal.Task) error {
return err
}

table.Append([]string{
row := []string{
fmt.Sprintf("%d", root.PID),
root.Comm,
metadata.ByteToString(memSize),
metadata.ByteToString(shmemSize),
})
}
*rows = append(*rows, row)

for _, child := range root.Children {
if err := traverseTree(child, checkpointOutputDir); err != nil {
if err := traverseTree(child, checkpointOutputDir, rows); err != nil {
return err
}
}
return nil
}

for _, task := range tasks {
// Clear the table before processing each checkpoint task
table.ClearRows()
w := internal.GetNewTabWriter(os.Stdout)
var rows [][]string

c := crit.New(nil, nil, filepath.Join(task.OutputDir, "checkpoint"), false, false)
psTree, err := c.ExplorePs()
Expand All @@ -178,12 +173,16 @@ func showProcessMemorySizeTables(tasks []internal.Task) error {
}

// Populate the table rows
if err := traverseTree(psTree, task.OutputDir); err != nil {
if err := traverseTree(psTree, task.OutputDir, &rows); err != nil {
return err
}

fmt.Printf("\nDisplaying processes memory sizes from %s\n\n", task.CheckpointFilePath)
table.Render()

internal.WriteTableHeader(w, header)
internal.WriteTableRows(w, rows)

w.Flush()
}

return nil
Expand Down Expand Up @@ -348,21 +347,24 @@ func printMemorySearchResultForPID(task internal.Task) error {
return nil
}

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Address", "Match", "Instance"})
table.SetAutoMergeCells(false)
table.SetRowLine(true)
w := internal.GetNewTabWriter(os.Stdout)
header := []string{"Address", "Match", "Instance"}

internal.WriteTableHeader(w, header)

// Build rows
var rows [][]string
for i, result := range results {
table.Append([]string{
fmt.Sprintf(
"%016x", result.Vaddr),
row := []string{
fmt.Sprintf("%016x", result.Vaddr),
result.Match,
fmt.Sprintf("%d", i+1),
})
}
rows = append(rows, row)
}

table.Render()
internal.WriteTableRows(w, rows)

w.Flush()
return nil
}
3 changes: 0 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ toolchain go1.24.2
require (
github.com/checkpoint-restore/go-criu/v7 v7.2.0
github.com/containers/storage v1.58.0
github.com/olekukonko/tablewriter v0.0.5
github.com/opencontainers/runtime-spec v1.2.1
github.com/spf13/cobra v1.9.1
github.com/xlab/treeprint v1.2.0
Expand All @@ -18,11 +17,9 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/moby/sys/capability v0.4.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
Expand Down
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,16 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
Expand Down
15 changes: 8 additions & 7 deletions internal/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
metadata "github.com/checkpoint-restore/checkpointctl/lib"
"github.com/checkpoint-restore/go-criu/v7/crit"
"github.com/containers/storage/pkg/archive"
"github.com/olekukonko/tablewriter"
spec "github.com/opencontainers/runtime-spec/specs-go"
)

Expand Down Expand Up @@ -107,7 +106,8 @@ func getCheckpointInfo(task Task) (*checkpointInfo, error) {
}

func ShowContainerCheckpoints(tasks []Task) error {
table := tablewriter.NewWriter(os.Stdout)
w := GetNewTabWriter(os.Stdout)

header := []string{
"Container",
"Image",
Expand All @@ -121,6 +121,8 @@ func ShowContainerCheckpoints(tasks []Task) error {
header = append(header, "IP", "MAC", "CHKPT Size", "Root Fs Diff Size")
}

var rows [][]string

for _, task := range tasks {
info, err := getCheckpointInfo(task)
if err != nil {
Expand Down Expand Up @@ -167,14 +169,13 @@ func ShowContainerCheckpoints(tasks []Task) error {
row = append(row, metadata.ByteToString(info.archiveSizes.rootFsDiffTarSize))
}

table.Append(row)
rows = append(rows, row)
}

table.SetHeader(header)
table.SetAutoMergeCells(false)
table.SetRowLine(true)
table.Render()
WriteTableHeader(w, header)
WriteTableRows(w, rows)

w.Flush()
return nil
}

Expand Down
51 changes: 51 additions & 0 deletions internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package internal

import (
"fmt"
"io"
"os"
"strings"
"text/tabwriter"
"time"

metadata "github.com/checkpoint-restore/checkpointctl/lib"
Expand Down Expand Up @@ -82,3 +84,52 @@ func CleanupTasks(tasks []Task) {
}
}
}

// Constant values are based on kubectl's tabwriter settings:
// https://github.com/kubernetes/cli-runtime/blob/master/pkg/printers/tabwriter.go
const (
tabwriterMinWidth = 6
tabwriterWidth = 4
tabwriterPadding = 3
tabwriterPadChar = ' '
tabwriterFlags = 0
)

// GetNewTabWriter returns a tabwriter that translates tabbed columns in input into properly aligned text.
func GetNewTabWriter(output io.Writer) *tabwriter.Writer {
return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags)
}

// WriteTableHeader writes the header row and separator line for a table
func WriteTableHeader(w *tabwriter.Writer, header []string) {
// Print header
for i, h := range header {
if i > 0 {
fmt.Fprint(w, "\t")
}
fmt.Fprint(w, strings.ToUpper(h))
}
fmt.Fprintln(w)

// Print separator line
for i := range header {
if i > 0 {
fmt.Fprint(w, "\t")
}
fmt.Fprint(w, strings.Repeat("-", len(header[i])))
}
fmt.Fprintln(w)
}

// WriteTableRows writes the data rows for a table
func WriteTableRows(w *tabwriter.Writer, rows [][]string) {
for _, row := range rows {
for i, cell := range row {
if i > 0 {
fmt.Fprint(w, "\t")
}
fmt.Fprint(w, cell)
}
fmt.Fprintln(w)
}
}
Loading
Loading