Merge pull request #1045 from timstclair/v2-info-simple
Add V2 ContainerInfo API
This commit is contained in:
commit
b47498c16e
114
api/versions.go
114
api/versions.go
@ -19,7 +19,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
info "github.com/google/cadvisor/info/v1"
|
info "github.com/google/cadvisor/info/v1"
|
||||||
"github.com/google/cadvisor/info/v2"
|
"github.com/google/cadvisor/info/v2"
|
||||||
@ -358,27 +357,26 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
|
|||||||
case statsApi:
|
case statsApi:
|
||||||
name := getContainerName(request)
|
name := getContainerName(request)
|
||||||
glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt)
|
glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt)
|
||||||
conts, err := m.GetRequestedContainersInfo(name, opt)
|
infos, err := m.GetContainerInfoV2(name, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contStats := make(map[string][]v2.ContainerStats, 0)
|
contStats := make(map[string][]*v2.ContainerStats, 0)
|
||||||
for name, cont := range conts {
|
for name, cinfo := range infos {
|
||||||
contStats[name] = convertStats(cont)
|
contStats[name] = cinfo.Stats
|
||||||
}
|
}
|
||||||
return writeResult(contStats, w)
|
return writeResult(contStats, w)
|
||||||
case customMetricsApi:
|
case customMetricsApi:
|
||||||
containerName := getContainerName(request)
|
containerName := getContainerName(request)
|
||||||
glog.V(4).Infof("Api - Custom Metrics: Looking for metrics for container %q, options %+v", containerName, opt)
|
glog.V(4).Infof("Api - Custom Metrics: Looking for metrics for container %q, options %+v", containerName, opt)
|
||||||
conts, err := m.GetRequestedContainersInfo(containerName, opt)
|
infos, err := m.GetContainerInfoV2(containerName, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contMetrics := make(map[string]map[string]map[string][]info.MetricValBasic, 0)
|
contMetrics := make(map[string]map[string]map[string][]info.MetricValBasic, 0)
|
||||||
for _, cont := range conts {
|
for _, cinfo := range infos {
|
||||||
metrics := make(map[string]map[string][]info.MetricValBasic, 0)
|
metrics := make(map[string]map[string][]info.MetricValBasic, 0)
|
||||||
contStats := convertStats(cont)
|
for _, contStat := range cinfo.Stats {
|
||||||
for _, contStat := range contStats {
|
|
||||||
if contStat.HasCustomMetrics {
|
if contStat.HasCustomMetrics {
|
||||||
for name, allLabels := range contStat.CustomMetrics {
|
for name, allLabels := range contStat.CustomMetrics {
|
||||||
metricLabels := make(map[string][]info.MetricValBasic, 0)
|
metricLabels := make(map[string][]info.MetricValBasic, 0)
|
||||||
@ -451,104 +449,6 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func instCpuStats(last, cur *info.ContainerStats) (*v2.CpuInstStats, error) {
|
|
||||||
if last == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if !cur.Timestamp.After(last.Timestamp) {
|
|
||||||
return nil, fmt.Errorf("container stats move backwards in time")
|
|
||||||
}
|
|
||||||
if len(last.Cpu.Usage.PerCpu) != len(cur.Cpu.Usage.PerCpu) {
|
|
||||||
return nil, fmt.Errorf("different number of cpus")
|
|
||||||
}
|
|
||||||
timeDelta := cur.Timestamp.Sub(last.Timestamp)
|
|
||||||
if timeDelta <= 100*time.Millisecond {
|
|
||||||
return nil, fmt.Errorf("time delta unexpectedly small")
|
|
||||||
}
|
|
||||||
// Nanoseconds to gain precision and avoid having zero seconds if the
|
|
||||||
// difference between the timestamps is just under a second
|
|
||||||
timeDeltaNs := uint64(timeDelta.Nanoseconds())
|
|
||||||
convertToRate := func(lastValue, curValue uint64) (uint64, error) {
|
|
||||||
if curValue < lastValue {
|
|
||||||
return 0, fmt.Errorf("cumulative stats decrease")
|
|
||||||
}
|
|
||||||
valueDelta := curValue - lastValue
|
|
||||||
return (valueDelta * 1e9) / timeDeltaNs, nil
|
|
||||||
}
|
|
||||||
total, err := convertToRate(last.Cpu.Usage.Total, cur.Cpu.Usage.Total)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
percpu := make([]uint64, len(last.Cpu.Usage.PerCpu))
|
|
||||||
for i := range percpu {
|
|
||||||
var err error
|
|
||||||
percpu[i], err = convertToRate(last.Cpu.Usage.PerCpu[i], cur.Cpu.Usage.PerCpu[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
user, err := convertToRate(last.Cpu.Usage.User, cur.Cpu.Usage.User)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
system, err := convertToRate(last.Cpu.Usage.System, cur.Cpu.Usage.System)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &v2.CpuInstStats{
|
|
||||||
Usage: v2.CpuInstUsage{
|
|
||||||
Total: total,
|
|
||||||
PerCpu: percpu,
|
|
||||||
User: user,
|
|
||||||
System: system,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertStats(cont *info.ContainerInfo) []v2.ContainerStats {
|
|
||||||
stats := make([]v2.ContainerStats, 0, len(cont.Stats))
|
|
||||||
var last *info.ContainerStats
|
|
||||||
for _, val := range cont.Stats {
|
|
||||||
stat := v2.ContainerStats{
|
|
||||||
Timestamp: val.Timestamp,
|
|
||||||
HasCpu: cont.Spec.HasCpu,
|
|
||||||
HasMemory: cont.Spec.HasMemory,
|
|
||||||
HasNetwork: cont.Spec.HasNetwork,
|
|
||||||
HasFilesystem: cont.Spec.HasFilesystem,
|
|
||||||
HasDiskIo: cont.Spec.HasDiskIo,
|
|
||||||
HasCustomMetrics: cont.Spec.HasCustomMetrics,
|
|
||||||
}
|
|
||||||
if stat.HasCpu {
|
|
||||||
stat.Cpu = val.Cpu
|
|
||||||
cpuInst, err := instCpuStats(last, val)
|
|
||||||
if err != nil {
|
|
||||||
glog.Warningf("Could not get instant cpu stats: %v", err)
|
|
||||||
} else {
|
|
||||||
stat.CpuInst = cpuInst
|
|
||||||
}
|
|
||||||
last = val
|
|
||||||
}
|
|
||||||
if stat.HasMemory {
|
|
||||||
stat.Memory = val.Memory
|
|
||||||
}
|
|
||||||
if stat.HasNetwork {
|
|
||||||
stat.Network.Interfaces = val.Network.Interfaces
|
|
||||||
}
|
|
||||||
if stat.HasFilesystem {
|
|
||||||
stat.Filesystem = val.Filesystem
|
|
||||||
}
|
|
||||||
if stat.HasDiskIo {
|
|
||||||
stat.DiskIo = val.DiskIo
|
|
||||||
}
|
|
||||||
if stat.HasCustomMetrics {
|
|
||||||
stat.CustomMetrics = val.CustomMetrics
|
|
||||||
}
|
|
||||||
// TODO(rjnagal): Handle load stats.
|
|
||||||
stats = append(stats, stat)
|
|
||||||
}
|
|
||||||
return stats
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRequestOptions(r *http.Request) (v2.RequestOptions, error) {
|
func getRequestOptions(r *http.Request) (v2.RequestOptions, error) {
|
||||||
supportedTypes := map[string]bool{
|
supportedTypes := map[string]bool{
|
||||||
v2.TypeName: true,
|
v2.TypeName: true,
|
||||||
|
@ -19,11 +19,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/cadvisor/events"
|
"github.com/google/cadvisor/events"
|
||||||
info "github.com/google/cadvisor/info/v1"
|
info "github.com/google/cadvisor/info/v1"
|
||||||
"github.com/google/cadvisor/info/v2"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -81,170 +79,3 @@ func TestGetEventRequestDoubleArgument(t *testing.T) {
|
|||||||
assert.True(t, stream)
|
assert.True(t, stream)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstCpuStats(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
last *info.ContainerStats
|
|
||||||
cur *info.ContainerStats
|
|
||||||
want *v2.CpuInstStats
|
|
||||||
}{
|
|
||||||
// Last is missing
|
|
||||||
{
|
|
||||||
nil,
|
|
||||||
&info.ContainerStats{},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
// Goes back in time
|
|
||||||
{
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0).Add(time.Second),
|
|
||||||
},
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0),
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
// Zero time delta
|
|
||||||
{
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0),
|
|
||||||
},
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0),
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
// Unexpectedly small time delta
|
|
||||||
{
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0),
|
|
||||||
},
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0).Add(30 * time.Millisecond),
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
// Different number of cpus
|
|
||||||
{
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0),
|
|
||||||
Cpu: info.CpuStats{
|
|
||||||
Usage: info.CpuUsage{
|
|
||||||
PerCpu: []uint64{100, 200},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0).Add(time.Second),
|
|
||||||
Cpu: info.CpuStats{
|
|
||||||
Usage: info.CpuUsage{
|
|
||||||
PerCpu: []uint64{100, 200, 300},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
// Stat numbers decrease
|
|
||||||
{
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0),
|
|
||||||
Cpu: info.CpuStats{
|
|
||||||
Usage: info.CpuUsage{
|
|
||||||
Total: 300,
|
|
||||||
PerCpu: []uint64{100, 200},
|
|
||||||
User: 250,
|
|
||||||
System: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0).Add(time.Second),
|
|
||||||
Cpu: info.CpuStats{
|
|
||||||
Usage: info.CpuUsage{
|
|
||||||
Total: 200,
|
|
||||||
PerCpu: []uint64{100, 100},
|
|
||||||
User: 150,
|
|
||||||
System: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
// One second elapsed
|
|
||||||
{
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0),
|
|
||||||
Cpu: info.CpuStats{
|
|
||||||
Usage: info.CpuUsage{
|
|
||||||
Total: 300,
|
|
||||||
PerCpu: []uint64{100, 200},
|
|
||||||
User: 250,
|
|
||||||
System: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0).Add(time.Second),
|
|
||||||
Cpu: info.CpuStats{
|
|
||||||
Usage: info.CpuUsage{
|
|
||||||
Total: 500,
|
|
||||||
PerCpu: []uint64{200, 300},
|
|
||||||
User: 400,
|
|
||||||
System: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&v2.CpuInstStats{
|
|
||||||
Usage: v2.CpuInstUsage{
|
|
||||||
Total: 200,
|
|
||||||
PerCpu: []uint64{100, 100},
|
|
||||||
User: 150,
|
|
||||||
System: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Two seconds elapsed
|
|
||||||
{
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0),
|
|
||||||
Cpu: info.CpuStats{
|
|
||||||
Usage: info.CpuUsage{
|
|
||||||
Total: 300,
|
|
||||||
PerCpu: []uint64{100, 200},
|
|
||||||
User: 250,
|
|
||||||
System: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&info.ContainerStats{
|
|
||||||
Timestamp: time.Unix(100, 0).Add(2 * time.Second),
|
|
||||||
Cpu: info.CpuStats{
|
|
||||||
Usage: info.CpuUsage{
|
|
||||||
Total: 500,
|
|
||||||
PerCpu: []uint64{200, 300},
|
|
||||||
User: 400,
|
|
||||||
System: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&v2.CpuInstStats{
|
|
||||||
Usage: v2.CpuInstUsage{
|
|
||||||
Total: 100,
|
|
||||||
PerCpu: []uint64{50, 50},
|
|
||||||
User: 75,
|
|
||||||
System: 25,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, c := range tests {
|
|
||||||
got, err := instCpuStats(c.last, c.cur)
|
|
||||||
if err != nil {
|
|
||||||
if c.want == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
assert.Equal(t, c.want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -52,6 +52,14 @@ type MemorySpec struct {
|
|||||||
SwapLimit uint64 `json:"swap_limit,omitempty"`
|
SwapLimit uint64 `json:"swap_limit,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContainerInfo struct {
|
||||||
|
// Describes the container.
|
||||||
|
Spec ContainerSpec `json:"spec,omitempty"`
|
||||||
|
|
||||||
|
// Historical statistics gathered from the container.
|
||||||
|
Stats []*ContainerStats `json:"stats,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ContainerSpec struct {
|
type ContainerSpec struct {
|
||||||
// Time at which the container was created.
|
// Time at which the container was created.
|
||||||
CreationTime time.Time `json:"creation_time,omitempty"`
|
CreationTime time.Time `json:"creation_time,omitempty"`
|
||||||
|
153
info/v2/conversion.go
Normal file
153
info/v2/conversion.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Utilities for converting v1 structs to v2 structs.
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/google/cadvisor/info/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get V2 container spec from v1 container info.
|
||||||
|
func ContainerSpecFromV1(specV1 *v1.ContainerSpec, aliases []string, namespace string) ContainerSpec {
|
||||||
|
specV2 := ContainerSpec{
|
||||||
|
CreationTime: specV1.CreationTime,
|
||||||
|
HasCpu: specV1.HasCpu,
|
||||||
|
HasMemory: specV1.HasMemory,
|
||||||
|
HasFilesystem: specV1.HasFilesystem,
|
||||||
|
HasNetwork: specV1.HasNetwork,
|
||||||
|
HasDiskIo: specV1.HasDiskIo,
|
||||||
|
HasCustomMetrics: specV1.HasCustomMetrics,
|
||||||
|
Image: specV1.Image,
|
||||||
|
Labels: specV1.Labels,
|
||||||
|
}
|
||||||
|
if specV1.HasCpu {
|
||||||
|
specV2.Cpu.Limit = specV1.Cpu.Limit
|
||||||
|
specV2.Cpu.MaxLimit = specV1.Cpu.MaxLimit
|
||||||
|
specV2.Cpu.Mask = specV1.Cpu.Mask
|
||||||
|
}
|
||||||
|
if specV1.HasMemory {
|
||||||
|
specV2.Memory.Limit = specV1.Memory.Limit
|
||||||
|
specV2.Memory.Reservation = specV1.Memory.Reservation
|
||||||
|
specV2.Memory.SwapLimit = specV1.Memory.SwapLimit
|
||||||
|
}
|
||||||
|
if specV1.HasCustomMetrics {
|
||||||
|
specV2.CustomMetrics = specV1.CustomMetrics
|
||||||
|
}
|
||||||
|
specV2.Aliases = aliases
|
||||||
|
specV2.Namespace = namespace
|
||||||
|
return specV2
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContainerStatsFromV1(statsV1 []*v1.ContainerStats, specV1 *v1.ContainerSpec) []*ContainerStats {
|
||||||
|
stats := make([]*ContainerStats, 0, len(statsV1))
|
||||||
|
var last *v1.ContainerStats
|
||||||
|
for _, val := range statsV1 {
|
||||||
|
stat := ContainerStats{
|
||||||
|
Timestamp: val.Timestamp,
|
||||||
|
HasCpu: specV1.HasCpu,
|
||||||
|
HasMemory: specV1.HasMemory,
|
||||||
|
HasNetwork: specV1.HasNetwork,
|
||||||
|
HasFilesystem: specV1.HasFilesystem,
|
||||||
|
HasDiskIo: specV1.HasDiskIo,
|
||||||
|
HasCustomMetrics: specV1.HasCustomMetrics,
|
||||||
|
}
|
||||||
|
if stat.HasCpu {
|
||||||
|
stat.Cpu = val.Cpu
|
||||||
|
cpuInst, err := instCpuStats(last, val)
|
||||||
|
if err != nil {
|
||||||
|
glog.Warningf("Could not get instant cpu stats: %v", err)
|
||||||
|
} else {
|
||||||
|
stat.CpuInst = cpuInst
|
||||||
|
}
|
||||||
|
last = val
|
||||||
|
}
|
||||||
|
if stat.HasMemory {
|
||||||
|
stat.Memory = val.Memory
|
||||||
|
}
|
||||||
|
if stat.HasNetwork {
|
||||||
|
stat.Network.Interfaces = val.Network.Interfaces
|
||||||
|
}
|
||||||
|
if stat.HasFilesystem {
|
||||||
|
stat.Filesystem = val.Filesystem
|
||||||
|
}
|
||||||
|
if stat.HasDiskIo {
|
||||||
|
stat.DiskIo = val.DiskIo
|
||||||
|
}
|
||||||
|
if stat.HasCustomMetrics {
|
||||||
|
stat.CustomMetrics = val.CustomMetrics
|
||||||
|
}
|
||||||
|
// TODO(rjnagal): Handle load stats.
|
||||||
|
stats = append(stats, &stat)
|
||||||
|
}
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func instCpuStats(last, cur *v1.ContainerStats) (*CpuInstStats, error) {
|
||||||
|
if last == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if !cur.Timestamp.After(last.Timestamp) {
|
||||||
|
return nil, fmt.Errorf("container stats move backwards in time")
|
||||||
|
}
|
||||||
|
if len(last.Cpu.Usage.PerCpu) != len(cur.Cpu.Usage.PerCpu) {
|
||||||
|
return nil, fmt.Errorf("different number of cpus")
|
||||||
|
}
|
||||||
|
timeDelta := cur.Timestamp.Sub(last.Timestamp)
|
||||||
|
if timeDelta <= 100*time.Millisecond {
|
||||||
|
return nil, fmt.Errorf("time delta unexpectedly small")
|
||||||
|
}
|
||||||
|
// Nanoseconds to gain precision and avoid having zero seconds if the
|
||||||
|
// difference between the timestamps is just under a second
|
||||||
|
timeDeltaNs := uint64(timeDelta.Nanoseconds())
|
||||||
|
convertToRate := func(lastValue, curValue uint64) (uint64, error) {
|
||||||
|
if curValue < lastValue {
|
||||||
|
return 0, fmt.Errorf("cumulative stats decrease")
|
||||||
|
}
|
||||||
|
valueDelta := curValue - lastValue
|
||||||
|
return (valueDelta * 1e9) / timeDeltaNs, nil
|
||||||
|
}
|
||||||
|
total, err := convertToRate(last.Cpu.Usage.Total, cur.Cpu.Usage.Total)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
percpu := make([]uint64, len(last.Cpu.Usage.PerCpu))
|
||||||
|
for i := range percpu {
|
||||||
|
var err error
|
||||||
|
percpu[i], err = convertToRate(last.Cpu.Usage.PerCpu[i], cur.Cpu.Usage.PerCpu[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user, err := convertToRate(last.Cpu.Usage.User, cur.Cpu.Usage.User)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
system, err := convertToRate(last.Cpu.Usage.System, cur.Cpu.Usage.System)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &CpuInstStats{
|
||||||
|
Usage: CpuInstUsage{
|
||||||
|
Total: total,
|
||||||
|
PerCpu: percpu,
|
||||||
|
User: user,
|
||||||
|
System: system,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
264
info/v2/conversion_test.go
Normal file
264
info/v2/conversion_test.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/info/v1"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
timestamp = time.Date(1987, time.August, 10, 0, 0, 0, 0, time.UTC)
|
||||||
|
labels = map[string]string{"foo": "bar"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConvertSpec(t *testing.T) {
|
||||||
|
v1Spec := v1.ContainerSpec{
|
||||||
|
CreationTime: timestamp,
|
||||||
|
Labels: labels,
|
||||||
|
HasCpu: true,
|
||||||
|
Cpu: v1.CpuSpec{
|
||||||
|
Limit: 2048,
|
||||||
|
MaxLimit: 4096,
|
||||||
|
Mask: "cpu_mask",
|
||||||
|
},
|
||||||
|
HasMemory: true,
|
||||||
|
Memory: v1.MemorySpec{
|
||||||
|
Limit: 2048,
|
||||||
|
Reservation: 1024,
|
||||||
|
SwapLimit: 8192,
|
||||||
|
},
|
||||||
|
HasNetwork: true,
|
||||||
|
HasFilesystem: true,
|
||||||
|
HasDiskIo: true,
|
||||||
|
HasCustomMetrics: true,
|
||||||
|
CustomMetrics: []v1.MetricSpec{{
|
||||||
|
Name: "foo",
|
||||||
|
Type: v1.MetricGauge,
|
||||||
|
Format: v1.IntType,
|
||||||
|
Units: "bars",
|
||||||
|
}},
|
||||||
|
Image: "gcr.io/kubernetes/kubernetes:v1",
|
||||||
|
}
|
||||||
|
|
||||||
|
aliases := []string{"baz", "oof"}
|
||||||
|
namespace := "foo_bar_baz"
|
||||||
|
|
||||||
|
expectedV2Spec := ContainerSpec{
|
||||||
|
CreationTime: timestamp,
|
||||||
|
Labels: labels,
|
||||||
|
HasCpu: true,
|
||||||
|
Cpu: CpuSpec{
|
||||||
|
Limit: 2048,
|
||||||
|
MaxLimit: 4096,
|
||||||
|
Mask: "cpu_mask",
|
||||||
|
},
|
||||||
|
HasMemory: true,
|
||||||
|
Memory: MemorySpec{
|
||||||
|
Limit: 2048,
|
||||||
|
Reservation: 1024,
|
||||||
|
SwapLimit: 8192,
|
||||||
|
},
|
||||||
|
HasNetwork: true,
|
||||||
|
HasFilesystem: true,
|
||||||
|
HasDiskIo: true,
|
||||||
|
HasCustomMetrics: true,
|
||||||
|
CustomMetrics: []v1.MetricSpec{{
|
||||||
|
Name: "foo",
|
||||||
|
Type: v1.MetricGauge,
|
||||||
|
Format: v1.IntType,
|
||||||
|
Units: "bars",
|
||||||
|
}},
|
||||||
|
Image: "gcr.io/kubernetes/kubernetes:v1",
|
||||||
|
Aliases: aliases,
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
v2Spec := ContainerSpecFromV1(&v1Spec, aliases, namespace)
|
||||||
|
if !reflect.DeepEqual(v2Spec, expectedV2Spec) {
|
||||||
|
t.Errorf("Converted spec differs from expectation!\nExpected: %+v\n Got: %+v\n", expectedV2Spec, v2Spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstCpuStats(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
last *v1.ContainerStats
|
||||||
|
cur *v1.ContainerStats
|
||||||
|
want *CpuInstStats
|
||||||
|
}{
|
||||||
|
// Last is missing
|
||||||
|
{
|
||||||
|
nil,
|
||||||
|
&v1.ContainerStats{},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
// Goes back in time
|
||||||
|
{
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0).Add(time.Second),
|
||||||
|
},
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
// Zero time delta
|
||||||
|
{
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0),
|
||||||
|
},
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
// Unexpectedly small time delta
|
||||||
|
{
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0),
|
||||||
|
},
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0).Add(30 * time.Millisecond),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
// Different number of cpus
|
||||||
|
{
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0),
|
||||||
|
Cpu: v1.CpuStats{
|
||||||
|
Usage: v1.CpuUsage{
|
||||||
|
PerCpu: []uint64{100, 200},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0).Add(time.Second),
|
||||||
|
Cpu: v1.CpuStats{
|
||||||
|
Usage: v1.CpuUsage{
|
||||||
|
PerCpu: []uint64{100, 200, 300},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
// Stat numbers decrease
|
||||||
|
{
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0),
|
||||||
|
Cpu: v1.CpuStats{
|
||||||
|
Usage: v1.CpuUsage{
|
||||||
|
Total: 300,
|
||||||
|
PerCpu: []uint64{100, 200},
|
||||||
|
User: 250,
|
||||||
|
System: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0).Add(time.Second),
|
||||||
|
Cpu: v1.CpuStats{
|
||||||
|
Usage: v1.CpuUsage{
|
||||||
|
Total: 200,
|
||||||
|
PerCpu: []uint64{100, 100},
|
||||||
|
User: 150,
|
||||||
|
System: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
// One second elapsed
|
||||||
|
{
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0),
|
||||||
|
Cpu: v1.CpuStats{
|
||||||
|
Usage: v1.CpuUsage{
|
||||||
|
Total: 300,
|
||||||
|
PerCpu: []uint64{100, 200},
|
||||||
|
User: 250,
|
||||||
|
System: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0).Add(time.Second),
|
||||||
|
Cpu: v1.CpuStats{
|
||||||
|
Usage: v1.CpuUsage{
|
||||||
|
Total: 500,
|
||||||
|
PerCpu: []uint64{200, 300},
|
||||||
|
User: 400,
|
||||||
|
System: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&CpuInstStats{
|
||||||
|
Usage: CpuInstUsage{
|
||||||
|
Total: 200,
|
||||||
|
PerCpu: []uint64{100, 100},
|
||||||
|
User: 150,
|
||||||
|
System: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Two seconds elapsed
|
||||||
|
{
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0),
|
||||||
|
Cpu: v1.CpuStats{
|
||||||
|
Usage: v1.CpuUsage{
|
||||||
|
Total: 300,
|
||||||
|
PerCpu: []uint64{100, 200},
|
||||||
|
User: 250,
|
||||||
|
System: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&v1.ContainerStats{
|
||||||
|
Timestamp: time.Unix(100, 0).Add(2 * time.Second),
|
||||||
|
Cpu: v1.CpuStats{
|
||||||
|
Usage: v1.CpuUsage{
|
||||||
|
Total: 500,
|
||||||
|
PerCpu: []uint64{200, 300},
|
||||||
|
User: 400,
|
||||||
|
System: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&CpuInstStats{
|
||||||
|
Usage: CpuInstUsage{
|
||||||
|
Total: 100,
|
||||||
|
PerCpu: []uint64{50, 50},
|
||||||
|
User: 75,
|
||||||
|
System: 25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range tests {
|
||||||
|
got, err := instCpuStats(c.last, c.cur)
|
||||||
|
if err != nil {
|
||||||
|
if c.want == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, c.want, got)
|
||||||
|
}
|
||||||
|
}
|
@ -62,6 +62,9 @@ type Manager interface {
|
|||||||
// Get information about a container.
|
// Get information about a container.
|
||||||
GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
|
|
||||||
|
// Get V2 information about a container.
|
||||||
|
GetContainerInfoV2(containerName string, options v2.RequestOptions) (map[string]v2.ContainerInfo, error)
|
||||||
|
|
||||||
// Get information about all subcontainers of the specified container (includes self).
|
// Get information about all subcontainers of the specified container (includes self).
|
||||||
SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error)
|
SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error)
|
||||||
|
|
||||||
@ -375,33 +378,8 @@ func (self *manager) GetContainerSpec(containerName string, options v2.RequestOp
|
|||||||
|
|
||||||
// Get V2 container spec from v1 container info.
|
// Get V2 container spec from v1 container info.
|
||||||
func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec {
|
func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec {
|
||||||
specV1 := self.getAdjustedSpec(cinfo)
|
spec := self.getAdjustedSpec(cinfo)
|
||||||
specV2 := v2.ContainerSpec{
|
return v2.ContainerSpecFromV1(&spec, cinfo.Aliases, cinfo.Namespace)
|
||||||
CreationTime: specV1.CreationTime,
|
|
||||||
HasCpu: specV1.HasCpu,
|
|
||||||
HasMemory: specV1.HasMemory,
|
|
||||||
HasFilesystem: specV1.HasFilesystem,
|
|
||||||
HasNetwork: specV1.HasNetwork,
|
|
||||||
HasDiskIo: specV1.HasDiskIo,
|
|
||||||
HasCustomMetrics: specV1.HasCustomMetrics,
|
|
||||||
Image: specV1.Image,
|
|
||||||
}
|
|
||||||
if specV1.HasCpu {
|
|
||||||
specV2.Cpu.Limit = specV1.Cpu.Limit
|
|
||||||
specV2.Cpu.MaxLimit = specV1.Cpu.MaxLimit
|
|
||||||
specV2.Cpu.Mask = specV1.Cpu.Mask
|
|
||||||
}
|
|
||||||
if specV1.HasMemory {
|
|
||||||
specV2.Memory.Limit = specV1.Memory.Limit
|
|
||||||
specV2.Memory.Reservation = specV1.Memory.Reservation
|
|
||||||
specV2.Memory.SwapLimit = specV1.Memory.SwapLimit
|
|
||||||
}
|
|
||||||
if specV1.HasCustomMetrics {
|
|
||||||
specV2.CustomMetrics = specV1.CustomMetrics
|
|
||||||
}
|
|
||||||
specV2.Aliases = cinfo.Aliases
|
|
||||||
specV2.Namespace = cinfo.Namespace
|
|
||||||
return specV2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec {
|
func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec {
|
||||||
@ -417,7 +395,6 @@ func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec {
|
|||||||
return spec
|
return spec
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a container by name.
|
|
||||||
func (self *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
func (self *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
cont, err := self.getContainerData(containerName)
|
cont, err := self.getContainerData(containerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -426,6 +403,34 @@ func (self *manager) GetContainerInfo(containerName string, query *info.Containe
|
|||||||
return self.containerDataToContainerInfo(cont, query)
|
return self.containerDataToContainerInfo(cont, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *manager) GetContainerInfoV2(containerName string, options v2.RequestOptions) (map[string]v2.ContainerInfo, error) {
|
||||||
|
containers, err := self.getRequestedContainers(containerName, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
infos := make(map[string]v2.ContainerInfo, len(containers))
|
||||||
|
for name, container := range containers {
|
||||||
|
cinfo, err := container.GetInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nilTime time.Time // Ignored.
|
||||||
|
stats, err := self.memoryCache.RecentStats(name, nilTime, nilTime, options.Count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
infos[name] = v2.ContainerInfo{
|
||||||
|
Spec: self.getV2Spec(cinfo),
|
||||||
|
Stats: v2.ContainerStatsFromV1(stats, &cinfo.Spec),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (self *manager) containerDataToContainerInfo(cont *containerData, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
func (self *manager) containerDataToContainerInfo(cont *containerData, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
// Get the info from the container.
|
// Get the info from the container.
|
||||||
cinfo, err := cont.GetInfo()
|
cinfo, err := cont.GetInfo()
|
||||||
|
Loading…
Reference in New Issue
Block a user