Merge pull request #1249 from theatrus/add-container-stats
Add methods for listing and fetching container stats
This commit is contained in:
commit
8f5e37a83c
4 changed files with 111 additions and 42 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,6 +28,42 @@ 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, origCtrList []*oci.Container) []*oci.Container {
|
||||||
|
// 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{}
|
||||||
|
}
|
||||||
|
c := s.ContainerServer.GetContainer(id)
|
||||||
|
if c != nil {
|
||||||
|
switch {
|
||||||
|
case filter.PodSandboxId == "":
|
||||||
|
return []*oci.Container{c}
|
||||||
|
case c.Sandbox() == filter.PodSandboxId:
|
||||||
|
return []*oci.Container{c}
|
||||||
|
default:
|
||||||
|
return []*oci.Container{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if filter.PodSandboxId != "" {
|
||||||
|
pod := s.ContainerServer.GetSandbox(filter.PodSandboxId)
|
||||||
|
if pod == nil {
|
||||||
|
return []*oci.Container{}
|
||||||
|
}
|
||||||
|
return pod.Containers().List()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logrus.Debug("no filters were applied, returning full container list")
|
||||||
|
return origCtrList
|
||||||
|
}
|
||||||
|
|
||||||
// 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,39 +81,7 @@ func (s *Server) ListContainers(ctx context.Context, req *pb.ListContainersReque
|
||||||
}
|
}
|
||||||
|
|
||||||
if filter != nil {
|
if filter != nil {
|
||||||
|
ctrList = s.filterContainerList(filter, ctrList)
|
||||||
// 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 &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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,34 @@ 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 = s.filterContainerList(cFilter, ctrList)
|
||||||
|
}
|
||||||
|
|
||||||
|
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