cli: implement events command

Events cli display container events such as cpu,
memory, and IO usage statistics.

By now OOM notifications and intel RDT are not fully supproted.

Fixes: #186

Signed-off-by: Haomin <caihaomin@huawei.com>
This commit is contained in:
c00416947 2018-05-11 19:27:26 +08:00
parent f1f534c6ae
commit 1205e347f2
19 changed files with 767 additions and 11 deletions

270
cli/events.go Normal file
View File

@ -0,0 +1,270 @@
// Copyright (c) 2014,2015,2016,2017 Docker, Inc.
// Copyright (c) 2018 Huawei Corporation.
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"encoding/json"
"fmt"
"os"
"sync"
"time"
vc "github.com/kata-containers/runtime/virtcontainers"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
type event struct {
Type string `json:"type"`
ID string `json:"id"`
Data interface{} `json:"data,omitempty"`
}
// stats is the runc specific stats structure for stability when encoding and decoding stats.
type stats struct {
CPU cpu `json:"cpu"`
Memory memory `json:"memory"`
Pids pids `json:"pids"`
Blkio blkio `json:"blkio"`
Hugetlb map[string]hugetlb `json:"hugetlb"`
IntelRdt intelRdt `json:"intel_rdt"`
}
type hugetlb struct {
Usage uint64 `json:"usage,omitempty"`
Max uint64 `json:"max,omitempty"`
Failcnt uint64 `json:"failcnt"`
}
type blkioEntry struct {
Major uint64 `json:"major,omitempty"`
Minor uint64 `json:"minor,omitempty"`
Op string `json:"op,omitempty"`
Value uint64 `json:"value,omitempty"`
}
type blkio struct {
IoServiceBytesRecursive []blkioEntry `json:"ioServiceBytesRecursive,omitempty"`
IoServicedRecursive []blkioEntry `json:"ioServicedRecursive,omitempty"`
IoQueuedRecursive []blkioEntry `json:"ioQueueRecursive,omitempty"`
IoServiceTimeRecursive []blkioEntry `json:"ioServiceTimeRecursive,omitempty"`
IoWaitTimeRecursive []blkioEntry `json:"ioWaitTimeRecursive,omitempty"`
IoMergedRecursive []blkioEntry `json:"ioMergedRecursive,omitempty"`
IoTimeRecursive []blkioEntry `json:"ioTimeRecursive,omitempty"`
SectorsRecursive []blkioEntry `json:"sectorsRecursive,omitempty"`
}
type pids struct {
Current uint64 `json:"current,omitempty"`
Limit uint64 `json:"limit,omitempty"`
}
type throttling struct {
Periods uint64 `json:"periods,omitempty"`
ThrottledPeriods uint64 `json:"throttledPeriods,omitempty"`
ThrottledTime uint64 `json:"throttledTime,omitempty"`
}
type cpuUsage struct {
// Units: nanoseconds.
Total uint64 `json:"total,omitempty"`
Percpu []uint64 `json:"percpu,omitempty"`
Kernel uint64 `json:"kernel"`
User uint64 `json:"user"`
}
type cpu struct {
Usage cpuUsage `json:"usage,omitempty"`
Throttling throttling `json:"throttling,omitempty"`
}
type memoryEntry struct {
Limit uint64 `json:"limit"`
Usage uint64 `json:"usage,omitempty"`
Max uint64 `json:"max,omitempty"`
Failcnt uint64 `json:"failcnt"`
}
type memory struct {
Cache uint64 `json:"cache,omitempty"`
Usage memoryEntry `json:"usage,omitempty"`
Swap memoryEntry `json:"swap,omitempty"`
Kernel memoryEntry `json:"kernel,omitempty"`
KernelTCP memoryEntry `json:"kernelTCP,omitempty"`
Raw map[string]uint64 `json:"raw,omitempty"`
}
type l3CacheInfo struct {
CbmMask string `json:"cbm_mask,omitempty"`
MinCbmBits uint64 `json:"min_cbm_bits,omitempty"`
NumClosids uint64 `json:"num_closids,omitempty"`
}
type intelRdt struct {
// The read-only L3 cache information
L3CacheInfo *l3CacheInfo `json:"l3_cache_info,omitempty"`
// The read-only L3 cache schema in root
L3CacheSchemaRoot string `json:"l3_cache_schema_root,omitempty"`
// The L3 cache schema in 'container_id' group
L3CacheSchema string `json:"l3_cache_schema,omitempty"`
}
var eventsCLICommand = 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.`,
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 *cli.Context) error {
containerID := context.Args().First()
if containerID == "" {
return fmt.Errorf("container id cannot be empty")
}
duration := context.Duration("interval")
if duration <= 0 {
return fmt.Errorf("duration interval must be greater than 0")
}
status, sandboxID, err := getExistingContainerInfo(containerID)
if err != nil {
return err
}
if status.State.State == vc.StateStopped {
return fmt.Errorf("container with id %s is not running", status.ID)
}
var (
events = make(chan *event, 1024)
group = &sync.WaitGroup{}
)
group.Add(1)
go func() {
defer group.Done()
enc := json.NewEncoder(os.Stdout)
for e := range events {
if err := enc.Encode(e); err != nil {
logrus.Error(err)
}
}
}()
if context.Bool("stats") {
s, err := vci.StatsContainer(sandboxID, containerID)
if err != nil {
return err
}
events <- &event{Type: "stats", ID: status.ID, Data: convertVirtcontainerStats(&s)}
close(events)
group.Wait()
return nil
}
go func() {
for range time.Tick(context.Duration("interval")) {
s, err := vci.StatsContainer(sandboxID, containerID)
if err != nil {
logrus.Error(err)
continue
}
events <- &event{Type: "stats", ID: status.ID, Data: convertVirtcontainerStats(&s)}
}
}()
group.Wait()
return nil
},
}
func convertVirtcontainerStats(containerStats *vc.ContainerStats) *stats {
cg := containerStats.CgroupStats
if cg == nil {
return nil
}
var s 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.Throttling.Periods = cg.CPUStats.ThrottlingData.Periods
s.CPU.Throttling.ThrottledPeriods = cg.CPUStats.ThrottlingData.ThrottledPeriods
s.CPU.Throttling.ThrottledTime = cg.CPUStats.ThrottlingData.ThrottledTime
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.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.Hugetlb = make(map[string]hugetlb)
for k, v := range cg.HugetlbStats {
s.Hugetlb[k] = convertHugtlb(v)
}
return &s
}
func convertHugtlb(c vc.HugetlbStats) hugetlb {
return hugetlb{
Usage: c.Usage,
Max: c.MaxUsage,
Failcnt: c.Failcnt,
}
}
func convertMemoryEntry(c vc.MemoryData) memoryEntry {
return memoryEntry{
Limit: c.Limit,
Usage: c.Usage,
Max: c.MaxUsage,
Failcnt: c.Failcnt,
}
}
func convertBlkioEntry(c []vc.BlkioStatEntry) []blkioEntry {
var out []blkioEntry
for _, e := range c {
out = append(out, blkioEntry{
Major: e.Major,
Minor: e.Minor,
Op: e.Op,
Value: e.Value,
})
}
return out
}

141
cli/events_test.go Normal file
View File

@ -0,0 +1,141 @@
// Copyright (c) 2018 Huawei Corporation.
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"flag"
"os"
"testing"
"time"
vc "github.com/kata-containers/runtime/virtcontainers"
vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
"github.com/kata-containers/runtime/virtcontainers/pkg/vcmock"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
)
func TestEventsCliAction(t *testing.T) {
assert := assert.New(t)
// get Action function
actionFunc, ok := eventsCLICommand.Action.(func(ctx *cli.Context) error)
flagSet := flag.NewFlagSet("events", flag.ContinueOnError)
// create a new fake context
ctx := cli.NewContext(&cli.App{}, flagSet, nil)
assert.True(ok)
err := actionFunc(ctx)
assert.Error(err, "Missing container ID")
}
func TestEventsCLIFailure(t *testing.T) {
assert := assert.New(t)
flagSet := flag.NewFlagSet("events", flag.ContinueOnError)
ctx := cli.NewContext(&cli.App{}, flagSet, nil)
actionFunc, ok := eventsCLICommand.Action.(func(ctx *cli.Context) error)
assert.True(ok)
// missing container ID
err := actionFunc(ctx)
assert.Error(err)
// interval is negative
flagSet.Parse([]string{testContainerID})
flagSet.Duration("interval", (-1)*time.Second, "")
ctx = cli.NewContext(&cli.App{}, flagSet, nil)
err = actionFunc(ctx)
assert.Error(err)
// interval is zero
flagSet = flag.NewFlagSet("events", flag.ContinueOnError)
flagSet.Parse([]string{testContainerID})
flagSet.Duration("interval", 0*time.Second, "")
ctx = cli.NewContext(&cli.App{}, flagSet, nil)
err = actionFunc(ctx)
assert.Error(err)
// not running
sandbox := &vcmock.Sandbox{
MockID: testContainerID,
}
sandbox.MockContainers = []*vcmock.Container{
{
MockID: sandbox.ID(),
MockSandbox: sandbox,
},
}
testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) {
return vc.ContainerStatus{
ID: sandbox.ID(),
Annotations: map[string]string{
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
},
}, nil
}
defer func() {
testingImpl.StatusContainerFunc = nil
}()
err = actionFunc(ctx)
assert.Error(err)
}
func TestEventsCLISuccessful(t *testing.T) {
assert := assert.New(t)
sandbox := &vcmock.Sandbox{
MockID: testContainerID,
}
sandbox.MockContainers = []*vcmock.Container{
{
MockID: sandbox.ID(),
MockSandbox: sandbox,
},
}
testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) {
return vc.ContainerStatus{
ID: sandbox.ID(),
Annotations: map[string]string{
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
},
State: vc.State{
State: vc.StateRunning,
},
}, nil
}
testingImpl.StatsContainerFunc = func(sandboxID, containerID string) (vc.ContainerStats, error) {
return vc.ContainerStats{}, nil
}
defer func() {
testingImpl.StatusContainerFunc = nil
testingImpl.StatsContainerFunc = nil
}()
path, err := createTempContainerIDMapping(sandbox.ID(), sandbox.ID())
assert.NoError(err)
defer os.RemoveAll(path)
actionFunc, ok := eventsCLICommand.Action.(func(ctx *cli.Context) error)
assert.True(ok)
flagSet := flag.NewFlagSet("events", flag.ContinueOnError)
flagSet.Parse([]string{testContainerID})
flagSet.Duration("interval", 5*time.Second, "")
flagSet.Bool("stats", true, "")
ctx := cli.NewContext(&cli.App{}, flagSet, nil)
err = actionFunc(ctx)
assert.NoError(err)
}

View File

@ -117,6 +117,7 @@ var runtimeCommands = []cli.Command{
startCLICommand,
stateCLICommand,
updateCLICommand,
eventsCLICommand,
versionCLICommand,
// Kata Containers specific extensions

View File

@ -194,4 +194,7 @@ type agent interface {
// This function should be called after hot adding vCPUs or Memory.
// cpus specifies the number of CPUs that were added and the agent should online
onlineCPUMem(cpus uint32) error
// statsContainer will tell the agent to get stats from a container related to a Sandbox
statsContainer(sandbox *Sandbox, c Container) (*ContainerStats, error)
}

View File

@ -628,3 +628,28 @@ func UpdateContainer(sandboxID, containerID string, resources specs.LinuxResourc
return s.UpdateContainer(containerID, resources)
}
// StatsContainer is the virtcontainers container stats entry point.
// StatsContainer returns a detailed container stats.
func StatsContainer(sandboxID, containerID string) (ContainerStats, error) {
if sandboxID == "" {
return ContainerStats{}, errNeedSandboxID
}
if containerID == "" {
return ContainerStats{}, errNeedContainerID
}
lockFile, err := rLockSandbox(sandboxID)
if err != nil {
return ContainerStats{}, err
}
defer unlockSandbox(lockFile)
s, err := fetchSandbox(sandboxID)
if err != nil {
return ContainerStats{}, err
}
return s.StatsContainer(containerID)
}

View File

@ -1934,6 +1934,74 @@ func TestStatusContainerFailing(t *testing.T) {
}
}
func TestStatsContainerFailing(t *testing.T) {
cleanUp()
contID := "100"
config := newTestSandboxConfigNoop()
p, err := CreateSandbox(config)
if p == nil || err != nil {
t.Fatal(err)
}
pImpl, ok := p.(*Sandbox)
assert.True(t, ok)
os.RemoveAll(pImpl.configPath)
globalSandboxList.removeSandbox(p.ID())
_, err = StatsContainer(p.ID(), contID)
if err == nil {
t.Fatal()
}
}
func TestStatsContainer(t *testing.T) {
cleanUp()
assert := assert.New(t)
contID := "100"
_, err := StatsContainer("", "")
assert.Error(err)
_, err = StatsContainer("abc", "")
assert.Error(err)
_, err = StatsContainer("abc", "abc")
assert.Error(err)
config := newTestSandboxConfigNoop()
p, err := CreateSandbox(config)
assert.NoError(err)
assert.NotNil(p)
p, err = StartSandbox(p.ID())
if p == nil || err != nil {
t.Fatal(err)
}
pImpl, ok := p.(*Sandbox)
assert.True(ok)
defer os.RemoveAll(pImpl.configPath)
contConfig := newTestContainerConfigNoop(contID)
_, c, err := CreateContainer(p.ID(), contConfig)
assert.NoError(err)
assert.NotNil(c)
_, err = StatsContainer(pImpl.id, "xyz")
assert.Error(err)
_, err = StatsContainer("xyz", contID)
assert.Error(err)
stats, err := StatsContainer(pImpl.id, contID)
assert.NoError(err)
assert.Equal(stats, ContainerStats{})
}
func TestProcessListContainer(t *testing.T) {
cleanUp()

View File

@ -1,5 +1,6 @@
// +build linux
// Copyright (c) 2016 Intel Corporation
//
// Copyright (c) 2014,2015,2016,2017 Docker, Inc.
// SPDX-License-Identifier: Apache-2.0
//
@ -55,6 +56,119 @@ type ContainerStatus struct {
Annotations map[string]string
}
// ThrottlingData gather the date related to container cpu throttling.
type ThrottlingData struct {
// Number of periods with throttling active
Periods uint64 `json:"periods,omitempty"`
// Number of periods when the container hit its throttling limit.
ThrottledPeriods uint64 `json:"throttled_periods,omitempty"`
// Aggregate time the container was throttled for in nanoseconds.
ThrottledTime uint64 `json:"throttled_time,omitempty"`
}
// CPUUsage denotes the usage of a CPU.
// All CPU stats are aggregate since container inception.
type CPUUsage struct {
// Total CPU time consumed.
// Units: nanoseconds.
TotalUsage uint64 `json:"total_usage,omitempty"`
// Total CPU time consumed per core.
// Units: nanoseconds.
PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
// Time spent by tasks of the cgroup in kernel mode.
// Units: nanoseconds.
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
// Time spent by tasks of the cgroup in user mode.
// Units: nanoseconds.
UsageInUsermode uint64 `json:"usage_in_usermode"`
}
// CPUStats describes the cpu stats
type CPUStats struct {
CPUUsage CPUUsage `json:"cpu_usage,omitempty"`
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
}
// MemoryData gather the data related to memory
type MemoryData struct {
Usage uint64 `json:"usage,omitempty"`
MaxUsage uint64 `json:"max_usage,omitempty"`
Failcnt uint64 `json:"failcnt"`
Limit uint64 `json:"limit"`
}
// MemoryStats describes the memory stats
type MemoryStats struct {
// memory used for cache
Cache uint64 `json:"cache,omitempty"`
// usage of memory
Usage MemoryData `json:"usage,omitempty"`
// usage of memory swap
SwapUsage MemoryData `json:"swap_usage,omitempty"`
// usage of kernel memory
KernelUsage MemoryData `json:"kernel_usage,omitempty"`
// usage of kernel TCP memory
KernelTCPUsage MemoryData `json:"kernel_tcp_usage,omitempty"`
// if true, memory usage is accounted for throughout a hierarchy of cgroups.
UseHierarchy bool `json:"use_hierarchy"`
Stats map[string]uint64 `json:"stats,omitempty"`
}
// PidsStats describes the pids stats
type PidsStats struct {
// number of pids in the cgroup
Current uint64 `json:"current,omitempty"`
// active pids hard limit
Limit uint64 `json:"limit,omitempty"`
}
// BlkioStatEntry gather date related to a block device
type BlkioStatEntry struct {
Major uint64 `json:"major,omitempty"`
Minor uint64 `json:"minor,omitempty"`
Op string `json:"op,omitempty"`
Value uint64 `json:"value,omitempty"`
}
// BlkioStats describes block io stats
type BlkioStats struct {
// number of bytes tranferred to and from the block device
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"`
IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive,omitempty"`
IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"`
IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive,omitempty"`
IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive,omitempty"`
IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive,omitempty"`
IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive,omitempty"`
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"`
}
// HugetlbStats describes hugetable memory stats
type HugetlbStats struct {
// current res_counter usage for hugetlb
Usage uint64 `json:"usage,omitempty"`
// maximum usage ever recorded.
MaxUsage uint64 `json:"max_usage,omitempty"`
// number of times hugetlb usage allocation failure.
Failcnt uint64 `json:"failcnt"`
}
// CgroupStats describes all cgroup subsystem stats
type CgroupStats struct {
CPUStats CPUStats `json:"cpu_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
PidsStats PidsStats `json:"pids_stats,omitempty"`
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
// the map is in the format "size of hugepage: stats of the hugepage"
HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"`
}
// ContainerStats describes a container stats.
type ContainerStats struct {
CgroupStats *CgroupStats
}
// ContainerResources describes container resources
type ContainerResources struct {
// CPUQuota specifies the total amount of time in microseconds
@ -788,6 +902,13 @@ func (c *Container) processList(options ProcessListOptions) (ProcessList, error)
return c.sandbox.agent.processListContainer(c.sandbox, *c, options)
}
func (c *Container) stats() (*ContainerStats, error) {
if err := c.checkSandboxRunning("stats"); err != nil {
return nil, err
}
return c.sandbox.agent.statsContainer(c.sandbox, *c)
}
func (c *Container) update(resources specs.LinuxResources) error {
if err := c.checkSandboxRunning("update"); err != nil {
return err

View File

@ -609,6 +609,11 @@ func (h *hyper) processListContainer(sandbox *Sandbox, c Container, options Proc
return h.processListOneContainer(sandbox.id, c.id, options)
}
// statsContainer is the hyperstart agent Container stats implementation. It does nothing.
func (h *hyper) statsContainer(sandbox *Sandbox, c Container) (*ContainerStats, error) {
return &ContainerStats{}, nil
}
func (h *hyper) updateContainer(sandbox *Sandbox, c Container, resources specs.LinuxResources) error {
// hyperstart-agent does not support update
return nil

View File

@ -106,6 +106,11 @@ func (impl *VCImpl) StatusContainer(sandboxID, containerID string) (ContainerSta
return StatusContainer(sandboxID, containerID)
}
// StatsContainer implements the VC function of the same name.
func (impl *VCImpl) StatsContainer(sandboxID, containerID string) (ContainerStats, error) {
return StatsContainer(sandboxID, containerID)
}
// KillContainer implements the VC function of the same name.
func (impl *VCImpl) KillContainer(sandboxID, containerID string, signal syscall.Signal, all bool) error {
return KillContainer(sandboxID, containerID, signal, all)

View File

@ -34,6 +34,7 @@ type VC interface {
KillContainer(sandboxID, containerID string, signal syscall.Signal, all bool) error
StartContainer(sandboxID, containerID string) (VCContainer, error)
StatusContainer(sandboxID, containerID string) (ContainerStatus, error)
StatsContainer(sandboxID, containerID string) (ContainerStats, error)
StopContainer(sandboxID, containerID string) (VCContainer, error)
ProcessListContainer(sandboxID, containerID string, options ProcessListOptions) (ProcessList, error)
UpdateContainer(sandboxID, containerID string, resources specs.LinuxResources) error
@ -59,6 +60,7 @@ type VCSandbox interface {
DeleteContainer(contID string) (VCContainer, error)
StartContainer(containerID string) (VCContainer, error)
StatusContainer(containerID string) (ContainerStatus, error)
StatsContainer(containerID string) (ContainerStats, error)
EnterContainer(containerID string, cmd Cmd) (VCContainer, *Process, error)
UpdateContainer(containerID string, resources specs.LinuxResources) error
WaitProcess(containerID, processID string) (int32, error)

View File

@ -941,6 +941,38 @@ func (k *kataAgent) onlineCPUMem(cpus uint32) error {
return err
}
func (k *kataAgent) statsContainer(sandbox *Sandbox, c Container) (*ContainerStats, error) {
req := &grpc.StatsContainerRequest{
ContainerId: c.id,
}
returnStats, err := k.sendReq(req)
if err != nil {
return nil, err
}
stats, ok := returnStats.(*grpc.StatsContainerResponse)
if !ok {
return nil, fmt.Errorf("irregular response container stats")
}
data, err := json.Marshal(stats.CgroupStats)
if err != nil {
return nil, err
}
var cgroupStats CgroupStats
err = json.Unmarshal(data, &cgroupStats)
if err != nil {
return nil, err
}
containerStats := &ContainerStats{
CgroupStats: &cgroupStats,
}
return containerStats, nil
}
func (k *kataAgent) connect() error {
if k.client != nil {
return nil
@ -1069,6 +1101,9 @@ func (k *kataAgent) installReqFunc(c *kataclient.AgentClient) {
k.reqHandlers["grpc.CloseStdinRequest"] = func(ctx context.Context, req interface{}, opts ...golangGrpc.CallOption) (interface{}, error) {
return k.client.CloseStdin(ctx, req.(*grpc.CloseStdinRequest), opts...)
}
k.reqHandlers["grpc.StatsContainerRequest"] = func(ctx context.Context, req interface{}, opts ...golangGrpc.CallOption) (interface{}, error) {
return k.client.StatsContainer(ctx, req.(*grpc.StatsContainerRequest), opts...)
}
}
func (k *kataAgent) sendReq(request interface{}) (interface{}, error) {

View File

@ -199,6 +199,10 @@ func (p *gRPCProxy) OnlineCPUMem(ctx context.Context, req *pb.OnlineCPUMemReques
return emptyResp, nil
}
func (p *gRPCProxy) StatsContainer(ctx context.Context, req *pb.StatsContainerRequest) (*pb.StatsContainerResponse, error) {
return &pb.StatsContainerResponse{}, nil
}
func (p *gRPCProxy) Check(ctx context.Context, req *pb.CheckRequest) (*pb.HealthCheckResponse, error) {
return &pb.HealthCheckResponse{}, nil
}
@ -226,6 +230,7 @@ var reqList = []interface{}{
&pb.SignalProcessRequest{},
&pb.CheckRequest{},
&pb.WaitProcessRequest{},
&pb.StatsContainerRequest{},
}
func TestKataAgentSendReq(t *testing.T) {

View File

@ -91,6 +91,11 @@ func (n *noopAgent) check() error {
return nil
}
// statsContainer is the Noop agent Container stats implementation. It does nothing.
func (n *noopAgent) statsContainer(sandbox *Sandbox, c Container) (*ContainerStats, error) {
return &ContainerStats{}, nil
}
// waitProcess is the Noop agent process waiter. It does nothing.
func (n *noopAgent) waitProcess(c *Container, processID string) (int32, error) {
return 0, nil

View File

@ -117,3 +117,16 @@ func TestNoopAgentStopContainer(t *testing.T) {
t.Fatal(err)
}
}
func TestNoopAgentStatsContainer(t *testing.T) {
n := &noopAgent{}
sandbox, container, err := testCreateNoopContainer()
if err != nil {
t.Fatal(err)
}
defer cleanUp()
_, err = n.statsContainer(sandbox, *container)
if err != nil {
t.Fatal(err)
}
}

View File

@ -179,6 +179,15 @@ func (m *VCMock) StatusContainer(sandboxID, containerID string) (vc.ContainerSta
return vc.ContainerStatus{}, fmt.Errorf("%s: %s (%+v): sandboxID: %v, containerID: %v", mockErrorPrefix, getSelf(), m, sandboxID, containerID)
}
// StatsContainer implements the VC function of the same name.
func (m *VCMock) StatsContainer(sandboxID, containerID string) (vc.ContainerStats, error) {
if m.StatsContainerFunc != nil {
return m.StatsContainerFunc(sandboxID, containerID)
}
return vc.ContainerStats{}, fmt.Errorf("%s: %s (%+v): sandboxID: %v, containerID: %v", mockErrorPrefix, getSelf(), m, sandboxID, containerID)
}
// KillContainer implements the VC function of the same name.
func (m *VCMock) KillContainer(sandboxID, containerID string, signal syscall.Signal, all bool) error {
if m.KillContainerFunc != nil {

View File

@ -510,6 +510,33 @@ func TestVCMockStatusContainer(t *testing.T) {
assert.True(IsMockError(err))
}
func TestVCMockStatsContainer(t *testing.T) {
assert := assert.New(t)
m := &VCMock{}
assert.Nil(m.StatsContainerFunc)
_, err := m.StatsContainer(testSandboxID, testContainerID)
assert.Error(err)
assert.True(IsMockError(err))
m.StatsContainerFunc = func(sandboxID, containerID string) (vc.ContainerStats, error) {
return vc.ContainerStats{}, nil
}
stats, err := m.StatsContainer(testSandboxID, testContainerID)
assert.NoError(err)
assert.Equal(stats, vc.ContainerStats{})
// reset
m.StatsContainerFunc = nil
_, err = m.StatsContainer(testSandboxID, testContainerID)
assert.Error(err)
assert.True(IsMockError(err))
}
func TestVCMockStopContainer(t *testing.T) {
assert := assert.New(t)

View File

@ -94,6 +94,11 @@ func (p *Sandbox) StatusContainer(contID string) (vc.ContainerStatus, error) {
return vc.ContainerStatus{}, nil
}
// StatsContainer implements the VCSandbox function of the same name.
func (p *Sandbox) StatsContainer(contID string) (vc.ContainerStats, error) {
return vc.ContainerStats{}, nil
}
// Status implements the VCSandbox function of the same name.
func (p *Sandbox) Status() vc.SandboxStatus {
return vc.SandboxStatus{}

View File

@ -37,16 +37,17 @@ type Container struct {
type VCMock struct {
SetLoggerFunc func(logger logrus.FieldLogger)
CreateSandboxFunc func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error)
DeleteSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
ListSandboxFunc func() ([]vc.SandboxStatus, error)
FetchSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
PauseSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
ResumeSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
RunSandboxFunc func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error)
StartSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
StatusSandboxFunc func(sandboxID string) (vc.SandboxStatus, error)
StopSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
CreateSandboxFunc func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error)
DeleteSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
ListSandboxFunc func() ([]vc.SandboxStatus, error)
FetchSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
PauseSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
ResumeSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
RunSandboxFunc func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error)
StartSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
StatusSandboxFunc func(sandboxID string) (vc.SandboxStatus, error)
StatsContainerFunc func(sandboxID, containerID string) (vc.ContainerStats, error)
StopSandboxFunc func(sandboxID string) (vc.VCSandbox, error)
CreateContainerFunc func(sandboxID string, containerConfig vc.ContainerConfig) (vc.VCSandbox, vc.VCContainer, error)
DeleteContainerFunc func(sandboxID, containerID string) (vc.VCContainer, error)

View File

@ -1101,6 +1101,21 @@ func (s *Sandbox) UpdateContainer(containerID string, resources specs.LinuxResou
return c.update(resources)
}
// StatsContainer return the stats of a running container
func (s *Sandbox) StatsContainer(containerID string) (ContainerStats, error) {
// Fetch the container.
c, err := s.findContainer(containerID)
if err != nil {
return ContainerStats{}, err
}
stats, err := c.stats()
if err != nil {
return ContainerStats{}, err
}
return *stats, nil
}
// createContainers registers all containers to the proxy, create the
// containers in the guest and starts one shim per container.
func (s *Sandbox) createContainers() error {