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:
parent
ef57cf2810
commit
14c1c70407
4 changed files with 118 additions and 41 deletions
14
lib/stats.go
14
lib/stats.go
|
@ -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
|
||||||
|
|
|
@ -28,24 +28,9 @@ func filterContainer(c *pb.Container, filter *pb.ContainerFilter) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListContainers lists all containers by filters.
|
// filterContainerList applies a protobuf-defined filter to retrieve only intended containers. Not matching
|
||||||
func (s *Server) ListContainers(ctx context.Context, req *pb.ListContainersRequest) (resp *pb.ListContainersResponse, err error) {
|
// the filter is not considered an error but will return an empty response.
|
||||||
const operation = "list_containers"
|
func (s *Server) filterContainerList(filter *pb.ContainerFilter) (ctrList []*oci.Container, err error) {
|
||||||
defer func() {
|
|
||||||
recordOperation(operation, time.Now())
|
|
||||||
recordError(operation, err)
|
|
||||||
}()
|
|
||||||
logrus.Debugf("ListContainersRequest %+v", req)
|
|
||||||
|
|
||||||
var ctrs []*pb.Container
|
|
||||||
filter := req.GetFilter()
|
|
||||||
ctrList, err := s.ContainerServer.ListContainers()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if filter != nil {
|
|
||||||
|
|
||||||
// Filter using container id and pod id first.
|
// Filter using container id and pod id first.
|
||||||
if filter.Id != "" {
|
if filter.Id != "" {
|
||||||
id, err := s.CtrIDIndex().Get(filter.Id)
|
id, err := s.CtrIDIndex().Get(filter.Id)
|
||||||
|
@ -53,7 +38,7 @@ func (s *Server) ListContainers(ctx context.Context, req *pb.ListContainersReque
|
||||||
// If we don't find a container ID with a filter, it should not
|
// 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
|
// be considered an error. Log a warning and return an empty struct
|
||||||
logrus.Warn("unable to find container ID %s", filter.Id)
|
logrus.Warn("unable to find container ID %s", filter.Id)
|
||||||
return &pb.ListContainersResponse{}, nil
|
return []*oci.Container{}, nil
|
||||||
}
|
}
|
||||||
c := s.ContainerServer.GetContainer(id)
|
c := s.ContainerServer.GetContainer(id)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
|
@ -78,6 +63,30 @@ func (s *Server) ListContainers(ctx context.Context, req *pb.ListContainersReque
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListContainers lists all containers by filters.
|
||||||
|
func (s *Server) ListContainers(ctx context.Context, req *pb.ListContainersRequest) (resp *pb.ListContainersResponse, err error) {
|
||||||
|
const operation = "list_containers"
|
||||||
|
defer func() {
|
||||||
|
recordOperation(operation, time.Now())
|
||||||
|
recordError(operation, err)
|
||||||
|
}()
|
||||||
|
logrus.Debugf("ListContainersRequest %+v", req)
|
||||||
|
|
||||||
|
var ctrs []*pb.Container
|
||||||
|
filter := req.GetFilter()
|
||||||
|
ctrList, err := s.ContainerServer.ListContainers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter != nil {
|
||||||
|
ctrList, err = s.filterContainerList(filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ctr := range ctrList {
|
for _, ctr := range ctrList {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue