Add methods for listing and fetching container stats

This uses the previously unusued lib/stats.go code to return data
about container stats to the CRI API. Helpers have been built around
filtering based on the OCI API, and CPU stat reporting has been fixed.

No data on filesystem layer usage is returned at this time.

Fixes one-half of #1248

Signed-off-by: Yann Ramin <atrus@stackworks.net>
This commit is contained in:
Yann Ramin 2018-01-07 14:16:46 -08:00
parent ef57cf2810
commit 14c1c70407
4 changed files with 118 additions and 41 deletions

View file

@ -15,8 +15,8 @@ import (
type ContainerStats struct { type ContainerStats struct {
Container string Container string
CPU float64 CPU float64
cpuNano uint64 CPUNano uint64
systemNano uint64 SystemNano int64
MemUsage uint64 MemUsage uint64
MemLimit uint64 MemLimit uint64
MemPerc float64 MemPerc float64
@ -29,8 +29,8 @@ type ContainerStats struct {
// GetContainerStats gets the running stats for a given container // GetContainerStats gets the running stats for a given container
func (c *ContainerServer) GetContainerStats(ctr *oci.Container, previousStats *ContainerStats) (*ContainerStats, error) { func (c *ContainerServer) GetContainerStats(ctr *oci.Container, previousStats *ContainerStats) (*ContainerStats, error) {
previousCPU := previousStats.cpuNano previousCPU := previousStats.CPUNano
previousSystem := previousStats.systemNano previousSystem := previousStats.SystemNano
libcontainerStats, err := c.LibcontainerStats(ctr) libcontainerStats, err := c.LibcontainerStats(ctr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -38,6 +38,8 @@ func (c *ContainerServer) GetContainerStats(ctr *oci.Container, previousStats *C
cgroupStats := libcontainerStats.CgroupStats cgroupStats := libcontainerStats.CgroupStats
stats := new(ContainerStats) stats := new(ContainerStats)
stats.Container = ctr.ID() stats.Container = ctr.ID()
stats.CPUNano = cgroupStats.CpuStats.CpuUsage.TotalUsage
stats.SystemNano = time.Now().UnixNano()
stats.CPU = calculateCPUPercent(libcontainerStats, previousCPU, previousSystem) stats.CPU = calculateCPUPercent(libcontainerStats, previousCPU, previousSystem)
stats.MemUsage = cgroupStats.MemoryStats.Usage.Usage stats.MemUsage = cgroupStats.MemoryStats.Usage.Usage
stats.MemLimit = getMemLimit(cgroupStats.MemoryStats.Usage.Limit) stats.MemLimit = getMemLimit(cgroupStats.MemoryStats.Usage.Limit)
@ -84,11 +86,11 @@ func getContainerNetIO(stats *libcontainer.Stats) (received uint64, transmitted
return return
} }
func calculateCPUPercent(stats *libcontainer.Stats, previousCPU, previousSystem uint64) float64 { func calculateCPUPercent(stats *libcontainer.Stats, previousCPU uint64, previousSystem int64) float64 {
var ( var (
cpuPercent = 0.0 cpuPercent = 0.0
cpuDelta = float64(stats.CgroupStats.CpuStats.CpuUsage.TotalUsage - previousCPU) cpuDelta = float64(stats.CgroupStats.CpuStats.CpuUsage.TotalUsage - previousCPU)
systemDelta = float64(uint64(time.Now().UnixNano()) - previousSystem) systemDelta = float64(uint64(time.Now().UnixNano()) - uint64(previousSystem))
) )
if systemDelta > 0.0 && cpuDelta > 0.0 { if systemDelta > 0.0 && cpuDelta > 0.0 {
// gets a ratio of container cpu usage total, multiplies it by the number of cores (4 cores running // gets a ratio of container cpu usage total, multiplies it by the number of cores (4 cores running

View file

@ -28,6 +28,44 @@ func filterContainer(c *pb.Container, filter *pb.ContainerFilter) bool {
return true return true
} }
// filterContainerList applies a protobuf-defined filter to retrieve only intended containers. Not matching
// the filter is not considered an error but will return an empty response.
func (s *Server) filterContainerList(filter *pb.ContainerFilter) (ctrList []*oci.Container, err error) {
// Filter using container id and pod id first.
if filter.Id != "" {
id, err := s.CtrIDIndex().Get(filter.Id)
if err != nil {
// If we don't find a container ID with a filter, it should not
// be considered an error. Log a warning and return an empty struct
logrus.Warn("unable to find container ID %s", filter.Id)
return []*oci.Container{}, nil
}
c := s.ContainerServer.GetContainer(id)
if c != nil {
if filter.PodSandboxId != "" {
if c.Sandbox() == filter.PodSandboxId {
ctrList = []*oci.Container{c}
} else {
ctrList = []*oci.Container{}
}
} else {
ctrList = []*oci.Container{c}
}
}
} else {
if filter.PodSandboxId != "" {
pod := s.ContainerServer.GetSandbox(filter.PodSandboxId)
if pod == nil {
ctrList = []*oci.Container{}
} else {
ctrList = pod.Containers().List()
}
}
}
return
}
// ListContainers lists all containers by filters. // ListContainers lists all containers by filters.
func (s *Server) ListContainers(ctx context.Context, req *pb.ListContainersRequest) (resp *pb.ListContainersResponse, err error) { func (s *Server) ListContainers(ctx context.Context, req *pb.ListContainersRequest) (resp *pb.ListContainersResponse, err error) {
const operation = "list_containers" const operation = "list_containers"
@ -45,38 +83,9 @@ func (s *Server) ListContainers(ctx context.Context, req *pb.ListContainersReque
} }
if filter != nil { if filter != nil {
ctrList, err = s.filterContainerList(filter)
// Filter using container id and pod id first. if err != nil {
if filter.Id != "" { return nil, err
id, err := s.CtrIDIndex().Get(filter.Id)
if err != nil {
// If we don't find a container ID with a filter, it should not
// be considered an error. Log a warning and return an empty struct
logrus.Warn("unable to find container ID %s", filter.Id)
return &pb.ListContainersResponse{}, nil
}
c := s.ContainerServer.GetContainer(id)
if c != nil {
if filter.PodSandboxId != "" {
if c.Sandbox() == filter.PodSandboxId {
ctrList = []*oci.Container{c}
} else {
ctrList = []*oci.Container{}
}
} else {
ctrList = []*oci.Container{c}
}
}
} else {
if filter.PodSandboxId != "" {
pod := s.ContainerServer.GetSandbox(filter.PodSandboxId)
if pod == nil {
ctrList = []*oci.Container{}
} else {
ctrList = pod.Containers().List()
}
}
} }
} }

View file

@ -4,10 +4,32 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/kubernetes-incubator/cri-o/lib"
"github.com/kubernetes-incubator/cri-o/oci"
"golang.org/x/net/context" "golang.org/x/net/context"
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
) )
func buildContainerStats(stats *lib.ContainerStats, container *oci.Container) *pb.ContainerStats {
return &pb.ContainerStats{
Attributes: &pb.ContainerAttributes{
Id: container.ID(),
Metadata: container.Metadata(),
Labels: container.Labels(),
Annotations: container.Annotations(),
},
Cpu: &pb.CpuUsage{
Timestamp: stats.SystemNano,
UsageCoreNanoSeconds: &pb.UInt64Value{Value: stats.CPUNano},
},
Memory: &pb.MemoryUsage{
Timestamp: stats.SystemNano,
WorkingSetBytes: &pb.UInt64Value{Value: stats.MemUsage},
},
WritableLayer: nil,
}
}
// ContainerStats returns stats of the container. If the container does not // ContainerStats returns stats of the container. If the container does not
// exist, the call returns an error. // exist, the call returns an error.
func (s *Server) ContainerStats(ctx context.Context, req *pb.ContainerStatsRequest) (resp *pb.ContainerStatsResponse, err error) { func (s *Server) ContainerStats(ctx context.Context, req *pb.ContainerStatsRequest) (resp *pb.ContainerStatsResponse, err error) {
@ -16,5 +38,16 @@ func (s *Server) ContainerStats(ctx context.Context, req *pb.ContainerStatsReque
recordOperation(operation, time.Now()) recordOperation(operation, time.Now())
recordError(operation, err) recordError(operation, err)
}() }()
return nil, fmt.Errorf("not implemented")
container := s.GetContainer(req.ContainerId)
if container == nil {
return nil, fmt.Errorf("invalid container")
}
stats, err := s.GetContainerStats(container, &lib.ContainerStats{})
if err != nil {
return nil, err
}
return &pb.ContainerStatsResponse{Stats: buildContainerStats(stats, container)}, nil
} }

View file

@ -1,9 +1,10 @@
package server package server
import ( import (
"fmt"
"time" "time"
"github.com/kubernetes-incubator/cri-o/lib"
"github.com/sirupsen/logrus"
"golang.org/x/net/context" "golang.org/x/net/context"
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
) )
@ -15,5 +16,37 @@ func (s *Server) ListContainerStats(ctx context.Context, req *pb.ListContainerSt
recordOperation(operation, time.Now()) recordOperation(operation, time.Now())
recordError(operation, err) recordError(operation, err)
}() }()
return nil, fmt.Errorf("not implemented")
ctrList, err := s.ContainerServer.ListContainers()
if err != nil {
return nil, err
}
filter := req.GetFilter()
if filter != nil {
cFilter := &pb.ContainerFilter{
Id: req.Filter.Id,
PodSandboxId: req.Filter.PodSandboxId,
LabelSelector: req.Filter.LabelSelector,
}
ctrList, err = s.filterContainerList(cFilter)
if err != nil {
return nil, err
}
}
var allStats []*pb.ContainerStats
for _, container := range ctrList {
stats, err := s.GetContainerStats(container, &lib.ContainerStats{})
if err != nil {
logrus.Warn("unable to get stats for container %s", container.ID())
continue
}
response := buildContainerStats(stats, container)
allStats = append(allStats, response)
}
return &pb.ListContainerStatsResponse{
Stats: allStats,
}, nil
} }