mirror of
https://github.com/containers/podman.git
synced 2025-06-20 17:13:43 +08:00
@ -206,7 +206,7 @@ func ps(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := tmpl.Execute(w, responses); err != nil {
|
if err := tmpl.Execute(w, responses); err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
if err := w.Flush(); err != nil {
|
if err := w.Flush(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
244
cmd/podman/containers/stats.go
Normal file
244
cmd/podman/containers/stats.go
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
tm "github.com/buger/goterm"
|
||||||
|
"github.com/containers/libpod/cmd/podman/registry"
|
||||||
|
"github.com/containers/libpod/libpod/define"
|
||||||
|
"github.com/containers/libpod/pkg/cgroups"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
|
"github.com/containers/libpod/utils"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers."
|
||||||
|
statsCommand = &cobra.Command{
|
||||||
|
Use: "stats [flags] [CONTAINER...]",
|
||||||
|
Short: "Display a live stream of container resource usage statistics",
|
||||||
|
Long: statsDescription,
|
||||||
|
RunE: stats,
|
||||||
|
Args: checkStatOptions,
|
||||||
|
Example: `podman stats --all --no-stream
|
||||||
|
podman stats ctrID
|
||||||
|
podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`,
|
||||||
|
}
|
||||||
|
|
||||||
|
containerStatsCommand = &cobra.Command{
|
||||||
|
Use: statsCommand.Use,
|
||||||
|
Short: statsCommand.Short,
|
||||||
|
Long: statsCommand.Long,
|
||||||
|
RunE: statsCommand.RunE,
|
||||||
|
Args: checkStatOptions,
|
||||||
|
Example: `podman container stats --all --no-stream
|
||||||
|
podman container stats ctrID
|
||||||
|
podman container stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
statsOptions entities.ContainerStatsOptions
|
||||||
|
defaultStatsRow = "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n"
|
||||||
|
defaultStatsHeader = "ID\tNAME\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET IO\tBLOCK IO\tPIDS\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
func statFlags(flags *pflag.FlagSet) {
|
||||||
|
flags.BoolVarP(&statsOptions.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false")
|
||||||
|
flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template")
|
||||||
|
flags.BoolVarP(&statsOptions.Latest, "latest", "l", false, "Act on the latest container Podman is aware of")
|
||||||
|
flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen between intervals")
|
||||||
|
flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false")
|
||||||
|
if registry.IsRemote() {
|
||||||
|
_ = flags.MarkHidden("latest")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||||
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||||
|
Command: statsCommand,
|
||||||
|
})
|
||||||
|
flags := statsCommand.Flags()
|
||||||
|
statFlags(flags)
|
||||||
|
|
||||||
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||||
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||||
|
Command: containerStatsCommand,
|
||||||
|
Parent: containerCmd,
|
||||||
|
})
|
||||||
|
|
||||||
|
containerStatsFlags := containerStatsCommand.Flags()
|
||||||
|
statFlags(containerStatsFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stats is different in that it will assume running containers if
|
||||||
|
// no input is given, so we need to validate differently
|
||||||
|
func checkStatOptions(cmd *cobra.Command, args []string) error {
|
||||||
|
opts := 0
|
||||||
|
if statsOptions.All {
|
||||||
|
opts += 1
|
||||||
|
}
|
||||||
|
if statsOptions.Latest {
|
||||||
|
opts += 1
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
opts += 1
|
||||||
|
}
|
||||||
|
if opts > 1 {
|
||||||
|
return errors.Errorf("--all, --latest and containers cannot be used together")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stats(cmd *cobra.Command, args []string) error {
|
||||||
|
if rootless.IsRootless() {
|
||||||
|
unified, err := cgroups.IsCgroup2UnifiedMode()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !unified {
|
||||||
|
return errors.New("stats is not supported in rootless mode without cgroups v2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statsOptions.StatChan = make(chan []*define.ContainerStats, 1)
|
||||||
|
go func() {
|
||||||
|
for reports := range statsOptions.StatChan {
|
||||||
|
if err := outputStats(reports); err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return registry.ContainerEngine().ContainerStats(registry.Context(), args, statsOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputStats(reports []*define.ContainerStats) error {
|
||||||
|
if len(statsOptions.Format) < 1 && !statsOptions.NoReset {
|
||||||
|
tm.Clear()
|
||||||
|
tm.MoveCursor(1, 1)
|
||||||
|
tm.Flush()
|
||||||
|
}
|
||||||
|
var stats []*containerStats
|
||||||
|
for _, r := range reports {
|
||||||
|
stats = append(stats, &containerStats{r})
|
||||||
|
}
|
||||||
|
if statsOptions.Format == "json" {
|
||||||
|
return outputJSON(stats)
|
||||||
|
}
|
||||||
|
format := defaultStatsRow
|
||||||
|
if len(statsOptions.Format) > 0 {
|
||||||
|
format = statsOptions.Format
|
||||||
|
if !strings.HasSuffix(format, "\n") {
|
||||||
|
format += "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format = "{{range . }}" + format + "{{end}}"
|
||||||
|
if len(statsOptions.Format) < 1 {
|
||||||
|
format = defaultStatsHeader + format
|
||||||
|
}
|
||||||
|
tmpl, err := template.New("stats").Parse(format)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
|
||||||
|
if err := tmpl.Execute(w, stats); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type containerStats struct {
|
||||||
|
*define.ContainerStats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *containerStats) ID() string {
|
||||||
|
return s.ContainerID[0:12]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *containerStats) CPUPerc() string {
|
||||||
|
return floatToPercentString(s.CPU)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *containerStats) MemPerc() string {
|
||||||
|
return floatToPercentString(s.ContainerStats.MemPerc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *containerStats) NetIO() string {
|
||||||
|
return combineHumanValues(s.NetInput, s.NetOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *containerStats) BlockIO() string {
|
||||||
|
return combineHumanValues(s.BlockInput, s.BlockOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *containerStats) PIDS() string {
|
||||||
|
if s.PIDs == 0 {
|
||||||
|
// If things go bazinga, return a safe value
|
||||||
|
return "--"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d", s.PIDs)
|
||||||
|
}
|
||||||
|
func (s *containerStats) MemUsage() string {
|
||||||
|
return combineHumanValues(s.ContainerStats.MemUsage, s.ContainerStats.MemLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func floatToPercentString(f float64) string {
|
||||||
|
strippedFloat, err := utils.RemoveScientificNotationFromFloat(f)
|
||||||
|
if err != nil || strippedFloat == 0 {
|
||||||
|
// If things go bazinga, return a safe value
|
||||||
|
return "--"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.2f", strippedFloat) + "%"
|
||||||
|
}
|
||||||
|
|
||||||
|
func combineHumanValues(a, b uint64) string {
|
||||||
|
if a == 0 && b == 0 {
|
||||||
|
return "-- / --"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputJSON(stats []*containerStats) error {
|
||||||
|
type jstat struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CpuPercent string `json:"cpu_percent"`
|
||||||
|
MemUsage string `json:"mem_usage"`
|
||||||
|
MemPerc string `json:"mem_percent"`
|
||||||
|
NetIO string `json:"net_io"`
|
||||||
|
BlockIO string `json:"block_io"`
|
||||||
|
Pids string `json:"pids"`
|
||||||
|
}
|
||||||
|
var jstats []jstat
|
||||||
|
for _, j := range stats {
|
||||||
|
jstats = append(jstats, jstat{
|
||||||
|
Id: j.ID(),
|
||||||
|
Name: j.Name,
|
||||||
|
CpuPercent: j.CPUPerc(),
|
||||||
|
MemUsage: j.MemPerc(),
|
||||||
|
MemPerc: j.MemUsage(),
|
||||||
|
NetIO: j.NetIO(),
|
||||||
|
BlockIO: j.BlockIO(),
|
||||||
|
Pids: j.PIDS(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.MarshalIndent(jstats, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(string(b))
|
||||||
|
return nil
|
||||||
|
}
|
@ -112,3 +112,22 @@ func (s ContainerExecStatus) String() string {
|
|||||||
return "bad state"
|
return "bad state"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerStats contains the statistics information for a running container
|
||||||
|
type ContainerStats struct {
|
||||||
|
ContainerID string
|
||||||
|
Name string
|
||||||
|
PerCPU []uint64
|
||||||
|
CPU float64
|
||||||
|
CPUNano uint64
|
||||||
|
CPUSystemNano uint64
|
||||||
|
SystemNano uint64
|
||||||
|
MemUsage uint64
|
||||||
|
MemLimit uint64
|
||||||
|
MemPerc float64
|
||||||
|
NetInput uint64
|
||||||
|
NetOutput uint64
|
||||||
|
BlockInput uint64
|
||||||
|
BlockOutput uint64
|
||||||
|
PIDs uint64
|
||||||
|
}
|
||||||
|
@ -247,14 +247,14 @@ func (p *Pod) InfraContainerID() (string, error) {
|
|||||||
// PodContainerStats is an organization struct for pods and their containers
|
// PodContainerStats is an organization struct for pods and their containers
|
||||||
type PodContainerStats struct {
|
type PodContainerStats struct {
|
||||||
Pod *Pod
|
Pod *Pod
|
||||||
ContainerStats map[string]*ContainerStats
|
ContainerStats map[string]*define.ContainerStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPodStats returns the stats for each of its containers
|
// GetPodStats returns the stats for each of its containers
|
||||||
func (p *Pod) GetPodStats(previousContainerStats map[string]*ContainerStats) (map[string]*ContainerStats, error) {
|
func (p *Pod) GetPodStats(previousContainerStats map[string]*define.ContainerStats) (map[string]*define.ContainerStats, error) {
|
||||||
var (
|
var (
|
||||||
ok bool
|
ok bool
|
||||||
prevStat *ContainerStats
|
prevStat *define.ContainerStats
|
||||||
)
|
)
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
@ -266,10 +266,10 @@ func (p *Pod) GetPodStats(previousContainerStats map[string]*ContainerStats) (ma
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newContainerStats := make(map[string]*ContainerStats)
|
newContainerStats := make(map[string]*define.ContainerStats)
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
if prevStat, ok = previousContainerStats[c.ID()]; !ok {
|
if prevStat, ok = previousContainerStats[c.ID()]; !ok {
|
||||||
prevStat = &ContainerStats{}
|
prevStat = &define.ContainerStats{}
|
||||||
}
|
}
|
||||||
newStats, err := c.GetContainerStats(prevStat)
|
newStats, err := c.GetContainerStats(prevStat)
|
||||||
// If the container wasn't running, don't include it
|
// If the container wasn't running, don't include it
|
||||||
|
@ -13,8 +13,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetContainerStats gets the running stats for a given container
|
// GetContainerStats gets the running stats for a given container
|
||||||
func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) {
|
func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) {
|
||||||
stats := new(ContainerStats)
|
stats := new(define.ContainerStats)
|
||||||
stats.ContainerID = c.ID()
|
stats.ContainerID = c.ID()
|
||||||
stats.Name = c.Name()
|
stats.Name = c.Name()
|
||||||
|
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package libpod
|
|
||||||
|
|
||||||
// ContainerStats contains the statistics information for a running container
|
|
||||||
type ContainerStats struct {
|
|
||||||
ContainerID string
|
|
||||||
Name string
|
|
||||||
PerCPU []uint64
|
|
||||||
CPU float64
|
|
||||||
CPUNano uint64
|
|
||||||
CPUSystemNano uint64
|
|
||||||
SystemNano uint64
|
|
||||||
MemUsage uint64
|
|
||||||
MemLimit uint64
|
|
||||||
MemPerc float64
|
|
||||||
NetInput uint64
|
|
||||||
NetOutput uint64
|
|
||||||
BlockInput uint64
|
|
||||||
BlockOutput uint64
|
|
||||||
PIDs uint64
|
|
||||||
}
|
|
@ -5,6 +5,6 @@ package libpod
|
|||||||
import "github.com/containers/libpod/libpod/define"
|
import "github.com/containers/libpod/libpod/define"
|
||||||
|
|
||||||
// GetContainerStats gets the running stats for a given container
|
// GetContainerStats gets the running stats for a given container
|
||||||
func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) {
|
func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) {
|
||||||
return nil, define.ErrOSNotSupported
|
return nil, define.ErrOSNotSupported
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,10 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
"github.com/containers/libpod/utils"
|
"github.com/containers/libpod/utils"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
@ -36,24 +34,6 @@ func FuncTimer(funcName string) {
|
|||||||
fmt.Printf("%s executed in %d ms\n", funcName, elapsed)
|
fmt.Printf("%s executed in %d ms\n", funcName, elapsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveScientificNotationFromFloat returns a float without any
|
|
||||||
// scientific notation if the number has any.
|
|
||||||
// golang does not handle conversion of float64s that have scientific
|
|
||||||
// notation in them and otherwise stinks. please replace this if you have
|
|
||||||
// a better implementation.
|
|
||||||
func RemoveScientificNotationFromFloat(x float64) (float64, error) {
|
|
||||||
bigNum := strconv.FormatFloat(x, 'g', -1, 64)
|
|
||||||
breakPoint := strings.IndexAny(bigNum, "Ee")
|
|
||||||
if breakPoint > 0 {
|
|
||||||
bigNum = bigNum[:breakPoint]
|
|
||||||
}
|
|
||||||
result, err := strconv.ParseFloat(bigNum, 64)
|
|
||||||
if err != nil {
|
|
||||||
return x, errors.Wrapf(err, "unable to remove scientific number from calculations")
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MountExists returns true if dest exists in the list of mounts
|
// MountExists returns true if dest exists in the list of mounts
|
||||||
func MountExists(specMounts []spec.Mount, dest string) bool {
|
func MountExists(specMounts []spec.Mount, dest string) bool {
|
||||||
for _, m := range specMounts {
|
for _, m := range specMounts {
|
||||||
|
@ -3,6 +3,7 @@ package libpod
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/utils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ func TestRemoveScientificNotationFromFloat(t *testing.T) {
|
|||||||
numbers := []float64{0.0, .5, 1.99999932, 1.04e+10}
|
numbers := []float64{0.0, .5, 1.99999932, 1.04e+10}
|
||||||
results := []float64{0.0, .5, 1.99999932, 1.04}
|
results := []float64{0.0, .5, 1.99999932, 1.04}
|
||||||
for i, x := range numbers {
|
for i, x := range numbers {
|
||||||
result, err := RemoveScientificNotationFromFloat(x)
|
result, err := utils.RemoveScientificNotationFromFloat(x)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, result, results[i])
|
assert.Equal(t, result, results[i])
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{})
|
stats, err := ctnr.GetContainerStats(&define.ContainerStats{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name))
|
utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name))
|
||||||
return
|
return
|
||||||
|
@ -367,3 +367,14 @@ type ContainerCpOptions struct {
|
|||||||
// ContainerCpReport describes the output from a cp operation
|
// ContainerCpReport describes the output from a cp operation
|
||||||
type ContainerCpReport struct {
|
type ContainerCpReport struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerStatsOptions describes input options for getting
|
||||||
|
// stats on containers
|
||||||
|
type ContainerStatsOptions struct {
|
||||||
|
All bool
|
||||||
|
Format string
|
||||||
|
Latest bool
|
||||||
|
NoReset bool
|
||||||
|
NoStream bool
|
||||||
|
StatChan chan []*define.ContainerStats
|
||||||
|
}
|
||||||
|
@ -35,6 +35,7 @@ type ContainerEngine interface {
|
|||||||
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
|
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
|
||||||
ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error)
|
ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error)
|
||||||
ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error)
|
ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error)
|
||||||
|
ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error
|
||||||
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error)
|
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error)
|
||||||
ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error)
|
ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error)
|
||||||
ContainerUnmount(ctx context.Context, nameOrIds []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error)
|
ContainerUnmount(ctx context.Context, nameOrIds []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error)
|
||||||
|
@ -8,8 +8,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
lpfilters "github.com/containers/libpod/libpod/filters"
|
|
||||||
|
|
||||||
"github.com/containers/buildah"
|
"github.com/containers/buildah"
|
||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
@ -17,8 +16,10 @@ import (
|
|||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
"github.com/containers/libpod/libpod/events"
|
"github.com/containers/libpod/libpod/events"
|
||||||
|
lpfilters "github.com/containers/libpod/libpod/filters"
|
||||||
"github.com/containers/libpod/libpod/image"
|
"github.com/containers/libpod/libpod/image"
|
||||||
"github.com/containers/libpod/libpod/logs"
|
"github.com/containers/libpod/libpod/logs"
|
||||||
|
"github.com/containers/libpod/pkg/cgroups"
|
||||||
"github.com/containers/libpod/pkg/checkpoint"
|
"github.com/containers/libpod/pkg/checkpoint"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/containers/libpod/pkg/domain/infra/abi/terminal"
|
"github.com/containers/libpod/pkg/domain/infra/abi/terminal"
|
||||||
@ -1003,3 +1004,76 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) {
|
|||||||
_ = ic.Libpod.Shutdown(false)
|
_ = ic.Libpod.Shutdown(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error {
|
||||||
|
containerFunc := ic.Libpod.GetRunningContainers
|
||||||
|
switch {
|
||||||
|
case len(namesOrIds) > 0:
|
||||||
|
containerFunc = func() ([]*libpod.Container, error) { return ic.Libpod.GetContainersByList(namesOrIds) }
|
||||||
|
case options.Latest:
|
||||||
|
containerFunc = func() ([]*libpod.Container, error) {
|
||||||
|
lastCtr, err := ic.Libpod.GetLatestContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []*libpod.Container{lastCtr}, nil
|
||||||
|
}
|
||||||
|
case options.All:
|
||||||
|
containerFunc = ic.Libpod.GetAllContainers
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrs, err := containerFunc()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to get list of containers")
|
||||||
|
}
|
||||||
|
containerStats := map[string]*define.ContainerStats{}
|
||||||
|
for _, ctr := range ctrs {
|
||||||
|
initialStats, err := ctr.GetContainerStats(&define.ContainerStats{})
|
||||||
|
if err != nil {
|
||||||
|
// when doing "all", don't worry about containers that are not running
|
||||||
|
cause := errors.Cause(err)
|
||||||
|
if options.All && (cause == define.ErrCtrRemoved || cause == define.ErrNoSuchCtr || cause == define.ErrCtrStateInvalid) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cause == cgroups.ErrCgroupV1Rootless {
|
||||||
|
err = cause
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
containerStats[ctr.ID()] = initialStats
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
reportStats := []*define.ContainerStats{}
|
||||||
|
for _, ctr := range ctrs {
|
||||||
|
id := ctr.ID()
|
||||||
|
if _, ok := containerStats[ctr.ID()]; !ok {
|
||||||
|
initialStats, err := ctr.GetContainerStats(&define.ContainerStats{})
|
||||||
|
if errors.Cause(err) == define.ErrCtrRemoved || errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrStateInvalid {
|
||||||
|
// skip dealing with a container that is gone
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
containerStats[id] = initialStats
|
||||||
|
}
|
||||||
|
stats, err := ctr.GetContainerStats(containerStats[id])
|
||||||
|
if err != nil && errors.Cause(err) != define.ErrNoSuchCtr {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// replace the previous measurement with the current one
|
||||||
|
containerStats[id] = stats
|
||||||
|
reportStats = append(reportStats, stats)
|
||||||
|
}
|
||||||
|
ctrs, err = containerFunc()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
options.StatChan <- reportStats
|
||||||
|
if options.NoStream {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/containers/libpod/pkg/cgroups"
|
"github.com/containers/libpod/pkg/cgroups"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/containers/libpod/pkg/rootless"
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
|
"github.com/containers/libpod/utils"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -68,7 +69,7 @@ func combineHumanValues(a, b uint64) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func floatToPercentString(f float64) string {
|
func floatToPercentString(f float64) string {
|
||||||
strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f)
|
strippedFloat, err := utils.RemoveScientificNotationFromFloat(f)
|
||||||
if err != nil || strippedFloat == 0 {
|
if err != nil || strippedFloat == 0 {
|
||||||
// If things go bazinga, return a safe value
|
// If things go bazinga, return a safe value
|
||||||
return "--"
|
return "--"
|
||||||
|
@ -387,3 +387,7 @@ func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string,
|
|||||||
// Shutdown Libpod engine
|
// Shutdown Libpod engine
|
||||||
func (ic *ContainerEngine) Shutdown(_ context.Context) {
|
func (ic *ContainerEngine) Shutdown(_ context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
@ -331,7 +331,7 @@ func (i *VarlinkAPI) GetContainerStats(call iopodman.VarlinkCall, name string) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return call.ReplyContainerNotFound(name, err.Error())
|
return call.ReplyContainerNotFound(name, err.Error())
|
||||||
}
|
}
|
||||||
containerStats, err := ctr.GetContainerStats(&libpod.ContainerStats{})
|
containerStats, err := ctr.GetContainerStats(&define.ContainerStats{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Cause(err) == define.ErrCtrStateInvalid {
|
if errors.Cause(err) == define.ErrCtrStateInvalid {
|
||||||
return call.ReplyNoContainerRunning()
|
return call.ReplyNoContainerRunning()
|
||||||
|
@ -8,12 +8,12 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/libpod"
|
||||||
|
"github.com/containers/libpod/libpod/define"
|
||||||
|
iopodman "github.com/containers/libpod/pkg/varlink"
|
||||||
"github.com/cri-o/ocicni/pkg/ocicni"
|
"github.com/cri-o/ocicni/pkg/ocicni"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod"
|
|
||||||
iopodman "github.com/containers/libpod/pkg/varlink"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreatePod ...
|
// CreatePod ...
|
||||||
@ -263,7 +263,7 @@ func (i *VarlinkAPI) GetPodStats(call iopodman.VarlinkCall, name string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return call.ReplyPodNotFound(name, err.Error())
|
return call.ReplyPodNotFound(name, err.Error())
|
||||||
}
|
}
|
||||||
prevStats := make(map[string]*libpod.ContainerStats)
|
prevStats := make(map[string]*define.ContainerStats)
|
||||||
podStats, err := pod.GetPodStats(prevStats)
|
podStats, err := pod.GetPodStats(prevStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return call.ReplyErrorOccurred(err.Error())
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
package varlinkapi
|
package varlinkapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod/define"
|
||||||
iopodman "github.com/containers/libpod/pkg/varlink"
|
iopodman "github.com/containers/libpod/pkg/varlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod
|
// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod
|
||||||
// container stats
|
// container stats
|
||||||
func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats {
|
func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) define.ContainerStats {
|
||||||
cstats := libpod.ContainerStats{
|
cstats := define.ContainerStats{
|
||||||
ContainerID: stats.Id,
|
ContainerID: stats.Id,
|
||||||
Name: stats.Name,
|
Name: stats.Name,
|
||||||
CPU: stats.Cpu,
|
CPU: stats.Cpu,
|
||||||
|
@ -21,7 +21,6 @@ var _ = Describe("Podman stats", func() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
Skip(v2fail)
|
|
||||||
cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
|
cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
@ -125,3 +126,21 @@ func Tar(source string) (io.ReadCloser, error) {
|
|||||||
logrus.Debugf("creating tarball of %s", source)
|
logrus.Debugf("creating tarball of %s", source)
|
||||||
return archive.Tar(source, archive.Uncompressed)
|
return archive.Tar(source, archive.Uncompressed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveScientificNotationFromFloat returns a float without any
|
||||||
|
// scientific notation if the number has any.
|
||||||
|
// golang does not handle conversion of float64s that have scientific
|
||||||
|
// notation in them and otherwise stinks. please replace this if you have
|
||||||
|
// a better implementation.
|
||||||
|
func RemoveScientificNotationFromFloat(x float64) (float64, error) {
|
||||||
|
bigNum := strconv.FormatFloat(x, 'g', -1, 64)
|
||||||
|
breakPoint := strings.IndexAny(bigNum, "Ee")
|
||||||
|
if breakPoint > 0 {
|
||||||
|
bigNum = bigNum[:breakPoint]
|
||||||
|
}
|
||||||
|
result, err := strconv.ParseFloat(bigNum, 64)
|
||||||
|
if err != nil {
|
||||||
|
return x, errors.Wrapf(err, "unable to remove scientific number from calculations")
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user