diff --git a/api/versions.go b/api/versions.go index a9324bf7..c4d6f7c7 100644 --- a/api/versions.go +++ b/api/versions.go @@ -19,7 +19,6 @@ import ( "net/http" "path" "strconv" - "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/info/v2" @@ -358,27 +357,26 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma case statsApi: name := getContainerName(request) 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 { return err } - contStats := make(map[string][]v2.ContainerStats, 0) - for name, cont := range conts { - contStats[name] = convertStats(cont) + contStats := make(map[string][]*v2.ContainerStats, 0) + for name, cinfo := range infos { + contStats[name] = cinfo.Stats } return writeResult(contStats, w) case customMetricsApi: containerName := getContainerName(request) 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 { return err } 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) - contStats := convertStats(cont) - for _, contStat := range contStats { + for _, contStat := range cinfo.Stats { if contStat.HasCustomMetrics { for name, allLabels := range contStat.CustomMetrics { 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) { supportedTypes := map[string]bool{ v2.TypeName: true, diff --git a/api/versions_test.go b/api/versions_test.go index 49b7e816..dadbffea 100644 --- a/api/versions_test.go +++ b/api/versions_test.go @@ -19,11 +19,9 @@ import ( "net/http" "reflect" "testing" - "time" "github.com/google/cadvisor/events" info "github.com/google/cadvisor/info/v1" - "github.com/google/cadvisor/info/v2" "github.com/stretchr/testify/assert" ) @@ -81,170 +79,3 @@ func TestGetEventRequestDoubleArgument(t *testing.T) { assert.True(t, stream) 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) - } -} diff --git a/info/v2/container.go b/info/v2/container.go index 3063ea44..a6388931 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -52,6 +52,14 @@ type MemorySpec struct { 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 { // Time at which the container was created. CreationTime time.Time `json:"creation_time,omitempty"` diff --git a/info/v2/conversion.go b/info/v2/conversion.go new file mode 100644 index 00000000..5efbd203 --- /dev/null +++ b/info/v2/conversion.go @@ -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 +} diff --git a/info/v2/conversion_test.go b/info/v2/conversion_test.go new file mode 100644 index 00000000..6963cce5 --- /dev/null +++ b/info/v2/conversion_test.go @@ -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) + } +} diff --git a/manager/manager.go b/manager/manager.go index 121580d6..d2be1461 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -62,6 +62,9 @@ type Manager interface { // Get information about a container. 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). 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. func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec { - specV1 := self.getAdjustedSpec(cinfo) - specV2 := v2.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, - } - 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 + spec := self.getAdjustedSpec(cinfo) + return v2.ContainerSpecFromV1(&spec, cinfo.Aliases, cinfo.Namespace) } func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec { @@ -417,7 +395,6 @@ func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec { return spec } -// Get a container by name. func (self *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) { cont, err := self.getContainerData(containerName) if err != nil { @@ -426,6 +403,34 @@ func (self *manager) GetContainerInfo(containerName string, query *info.Containe 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) { // Get the info from the container. cinfo, err := cont.GetInfo()