diff --git a/checkpointctl.go b/checkpointctl.go index e1f65980..ebde10a0 100644 --- a/checkpointctl.go +++ b/checkpointctl.go @@ -40,6 +40,9 @@ func main() { inspectCommand := setupInspect() rootCommand.AddCommand(inspectCommand) + memparseCommand := setupMemParse() + rootCommand.AddCommand(memparseCommand) + rootCommand.Version = version if err := rootCommand.Execute(); err != nil { @@ -263,3 +266,33 @@ func cleanupTasks(tasks []task) { } } } + +func setupMemParse() *cobra.Command { + cmd := &cobra.Command{ + Use: "memparse", + Short: "Analyze container checkpoint memory", + RunE: memparse, + Args: cobra.MinimumNArgs(1), + } + + return cmd +} + +func memparse(cmd *cobra.Command, args []string) error { + requiredFiles := []string{ + metadata.SpecDumpFile, metadata.ConfigDumpFile, + filepath.Join(metadata.CheckpointDirectory, "pstree.img"), + filepath.Join(metadata.CheckpointDirectory, "core-"), + filepath.Join(metadata.CheckpointDirectory, "pagemap-"), + filepath.Join(metadata.CheckpointDirectory, "pages-"), + filepath.Join(metadata.CheckpointDirectory, "mm-"), + } + + tasks, err := createTasks(args, requiredFiles) + if err != nil { + return err + } + defer cleanupTasks(tasks) + + return showProcessMemorySizeTables(tasks) +} diff --git a/memparse.go b/memparse.go new file mode 100644 index 00000000..1de3caf0 --- /dev/null +++ b/memparse.go @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 + +// This file is used to handle memory pages analysis of container checkpoints + +package main + +import ( + "fmt" + "os" + "path/filepath" + + metadata "github.com/checkpoint-restore/checkpointctl/lib" + "github.com/checkpoint-restore/go-criu/v6/crit" + "github.com/olekukonko/tablewriter" +) + +// Display processes memory sizes within the given container checkpoints. +func showProcessMemorySizeTables(tasks []task) error { + // Initialize the table + table := tablewriter.NewWriter(os.Stdout) + header := []string{ + "PID", + "Process name", + "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 { + memReader, err := crit.NewMemoryReader( + filepath.Join(checkpointOutputDir, metadata.CheckpointDirectory), + root.PID, pageSize, + ) + if err != nil { + return err + } + + pagemapEntries := memReader.GetPagemapEntries() + + var memSize int64 + + for _, entry := range pagemapEntries { + memSize += int64(*entry.NrPages) * int64(pageSize) + } + + table.Append([]string{ + fmt.Sprintf("%d", root.PID), + root.Comm, + metadata.ByteToString(memSize), + }) + + for _, child := range root.Children { + if err := traverseTree(child, checkpointOutputDir); err != nil { + return err + } + } + return nil + } + + for _, task := range tasks { + // Clear the table before processing each checkpoint task + table.ClearRows() + + c := crit.New(nil, nil, filepath.Join(task.outputDir, "checkpoint"), false, false) + psTree, err := c.ExplorePs() + if err != nil { + return fmt.Errorf("failed to get process tree: %w", err) + } + + // Populate the table rows + if err := traverseTree(psTree, task.outputDir); err != nil { + return err + } + + fmt.Printf("\nDisplaying processes memory sizes from %s\n\n", task.checkpointFilePath) + table.Render() + } + + return nil +}