mirror of
https://github.com/opencontainers/runc.git
synced 2026-11-06 05:17:05 +00:00
Network interface statistics were only collected for "veth" networks,
but the veth strategy was removed back in 2018 by commit fe3d5c4c
("Remove unused veth setup code"). Since then, a container with a veth
network can not even be started ("unknown strategy type"), so this code
is never reached, and the network interface stats are always empty.
Remove the dead code, and deprecate libcontainer.Stats.Interfaces and
types.Stats.NetworkInterfaces, which are always empty.
Network statistics are better obtained by whoever sets up the container
network (which runc does not do).
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
218 lines
6.2 KiB
Go
218 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/opencontainers/cgroups"
|
|
"github.com/opencontainers/runc/libcontainer"
|
|
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
|
"github.com/opencontainers/runc/types"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/urfave/cli/v3"
|
|
)
|
|
|
|
var eventsCommand = &cli.Command{
|
|
Name: "events",
|
|
Usage: "display container events such as OOM notifications, cpu, memory, and IO usage statistics",
|
|
ArgsUsage: `<container-id>
|
|
|
|
Where "<container-id>" is the name for the instance of the container.`,
|
|
Description: `The events command displays information about the container. By default the
|
|
information is displayed once every 5 seconds.`,
|
|
// Disable comma as separator for slice flags.
|
|
DisableSliceFlagSeparator: true,
|
|
Flags: []cli.Flag{
|
|
&cli.DurationFlag{Name: "interval", Value: 5 * time.Second, Usage: "set the stats collection interval"},
|
|
&cli.BoolFlag{Name: "stats", Usage: "display the container's stats then exit"},
|
|
},
|
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
|
return err
|
|
}
|
|
container, err := getContainer(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
duration := cmd.Duration("interval")
|
|
if duration <= 0 {
|
|
return errors.New("duration interval must be greater than 0")
|
|
}
|
|
status, err := container.Status()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if status == libcontainer.Stopped {
|
|
return fmt.Errorf("container with id %s is not running", container.ID())
|
|
}
|
|
var (
|
|
stats = make(chan *libcontainer.Stats, 1)
|
|
events = make(chan *types.Event, 1024)
|
|
group = &sync.WaitGroup{}
|
|
)
|
|
group.Go(func() {
|
|
enc := json.NewEncoder(os.Stdout)
|
|
for e := range events {
|
|
if err := enc.Encode(e); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}
|
|
})
|
|
if cmd.Bool("stats") {
|
|
s, err := container.Stats()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
events <- &types.Event{Type: "stats", ID: container.ID(), Data: convertLibcontainerStats(s)}
|
|
close(events)
|
|
group.Wait()
|
|
return nil
|
|
}
|
|
go func() {
|
|
for range time.Tick(cmd.Duration("interval")) {
|
|
s, err := container.Stats()
|
|
if err != nil {
|
|
logrus.Error(err)
|
|
continue
|
|
}
|
|
stats <- s
|
|
}
|
|
}()
|
|
n, err := container.NotifyOOM()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for {
|
|
select {
|
|
case _, ok := <-n:
|
|
if ok {
|
|
// this means an oom event was received, if it is !ok then
|
|
// the channel was closed because the container stopped and
|
|
// the cgroups no longer exist.
|
|
events <- &types.Event{Type: "oom", ID: container.ID()}
|
|
} else {
|
|
n = nil
|
|
}
|
|
case s := <-stats:
|
|
events <- &types.Event{Type: "stats", ID: container.ID(), Data: convertLibcontainerStats(s)}
|
|
}
|
|
if n == nil {
|
|
close(events)
|
|
break
|
|
}
|
|
}
|
|
group.Wait()
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func convertLibcontainerStats(ls *libcontainer.Stats) *types.Stats {
|
|
cg := ls.CgroupStats
|
|
if cg == nil {
|
|
return nil
|
|
}
|
|
var s types.Stats
|
|
s.Pids.Current = cg.PidsStats.Current
|
|
s.Pids.Limit = cg.PidsStats.Limit
|
|
|
|
s.CPU.Usage.Kernel = cg.CpuStats.CpuUsage.UsageInKernelmode
|
|
s.CPU.Usage.User = cg.CpuStats.CpuUsage.UsageInUsermode
|
|
s.CPU.Usage.Total = cg.CpuStats.CpuUsage.TotalUsage
|
|
s.CPU.Usage.Percpu = cg.CpuStats.CpuUsage.PercpuUsage
|
|
s.CPU.Usage.PercpuKernel = cg.CpuStats.CpuUsage.PercpuUsageInKernelmode
|
|
s.CPU.Usage.PercpuUser = cg.CpuStats.CpuUsage.PercpuUsageInUsermode
|
|
s.CPU.Throttling.Periods = cg.CpuStats.ThrottlingData.Periods
|
|
s.CPU.Throttling.ThrottledPeriods = cg.CpuStats.ThrottlingData.ThrottledPeriods
|
|
s.CPU.Throttling.ThrottledTime = cg.CpuStats.ThrottlingData.ThrottledTime
|
|
s.CPU.PSI = cg.CpuStats.PSI
|
|
|
|
s.CPUSet = types.CPUSet(cg.CPUSetStats)
|
|
|
|
s.Memory.Cache = cg.MemoryStats.Cache
|
|
s.Memory.Kernel = convertMemoryEntry(cg.MemoryStats.KernelUsage)
|
|
s.Memory.KernelTCP = convertMemoryEntry(cg.MemoryStats.KernelTCPUsage)
|
|
s.Memory.Swap = convertMemoryEntry(cg.MemoryStats.SwapUsage)
|
|
s.Memory.Usage = convertMemoryEntry(cg.MemoryStats.Usage)
|
|
s.Memory.Raw = cg.MemoryStats.Stats
|
|
s.Memory.PSI = cg.MemoryStats.PSI
|
|
|
|
s.Blkio.IoServiceBytesRecursive = convertBlkioEntry(cg.BlkioStats.IoServiceBytesRecursive)
|
|
s.Blkio.IoServicedRecursive = convertBlkioEntry(cg.BlkioStats.IoServicedRecursive)
|
|
s.Blkio.IoQueuedRecursive = convertBlkioEntry(cg.BlkioStats.IoQueuedRecursive)
|
|
s.Blkio.IoServiceTimeRecursive = convertBlkioEntry(cg.BlkioStats.IoServiceTimeRecursive)
|
|
s.Blkio.IoWaitTimeRecursive = convertBlkioEntry(cg.BlkioStats.IoWaitTimeRecursive)
|
|
s.Blkio.IoMergedRecursive = convertBlkioEntry(cg.BlkioStats.IoMergedRecursive)
|
|
s.Blkio.IoTimeRecursive = convertBlkioEntry(cg.BlkioStats.IoTimeRecursive)
|
|
s.Blkio.SectorsRecursive = convertBlkioEntry(cg.BlkioStats.SectorsRecursive)
|
|
s.Blkio.PSI = cg.BlkioStats.PSI
|
|
|
|
s.Hugetlb = make(map[string]types.Hugetlb)
|
|
for k, v := range cg.HugetlbStats {
|
|
s.Hugetlb[k] = convertHugtlb(v)
|
|
}
|
|
|
|
if is := ls.IntelRdtStats; is != nil {
|
|
if intelrdt.IsCATEnabled() {
|
|
s.IntelRdt.L3CacheInfo = convertL3CacheInfo(is.L3CacheInfo)
|
|
s.IntelRdt.L3CacheSchemaRoot = is.L3CacheSchemaRoot
|
|
s.IntelRdt.L3CacheSchema = is.L3CacheSchema
|
|
}
|
|
if intelrdt.IsMBAEnabled() {
|
|
s.IntelRdt.MemBwInfo = convertMemBwInfo(is.MemBwInfo)
|
|
s.IntelRdt.MemBwSchemaRoot = is.MemBwSchemaRoot
|
|
s.IntelRdt.MemBwSchema = is.MemBwSchema
|
|
}
|
|
if intelrdt.IsMBMEnabled() {
|
|
s.IntelRdt.MBMStats = is.MBMStats
|
|
}
|
|
if intelrdt.IsCMTEnabled() {
|
|
s.IntelRdt.CMTStats = is.CMTStats
|
|
}
|
|
|
|
s.IntelRdt.Schemata = is.Schemata
|
|
}
|
|
|
|
return &s
|
|
}
|
|
|
|
func convertHugtlb(c cgroups.HugetlbStats) types.Hugetlb {
|
|
return types.Hugetlb{
|
|
Usage: c.Usage,
|
|
Max: c.MaxUsage,
|
|
Failcnt: c.Failcnt,
|
|
}
|
|
}
|
|
|
|
func convertMemoryEntry(c cgroups.MemoryData) types.MemoryEntry {
|
|
return types.MemoryEntry{
|
|
Limit: c.Limit,
|
|
Usage: c.Usage,
|
|
Max: c.MaxUsage,
|
|
Failcnt: c.Failcnt,
|
|
}
|
|
}
|
|
|
|
func convertBlkioEntry(c []cgroups.BlkioStatEntry) []types.BlkioEntry {
|
|
out := make([]types.BlkioEntry, 0, len(c))
|
|
for _, e := range c {
|
|
out = append(out, types.BlkioEntry(e))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func convertL3CacheInfo(i *intelrdt.L3CacheInfo) *types.L3CacheInfo {
|
|
ci := types.L3CacheInfo(*i)
|
|
return &ci
|
|
}
|
|
|
|
func convertMemBwInfo(i *intelrdt.MemBwInfo) *types.MemBwInfo {
|
|
mi := types.MemBwInfo(*i)
|
|
return &mi
|
|
}
|