Merge pull request #8141 from vmarmol/cadvisor

Update cAdvisor godep.
This commit is contained in:
Rohit Jnagal 2015-05-12 18:16:00 -07:00
commit ec19d41b63
23 changed files with 1806 additions and 1440 deletions

68
Godeps/Godeps.json generated
View File

@ -206,88 +206,88 @@
},
{
"ImportPath": "github.com/google/cadvisor/api",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/collector",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/container",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/events",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/fs",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/healthz",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/http",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/info/v1",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/info/v2",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/manager",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/metrics",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/pages",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/storage",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/summary",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/utils",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/validate",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/cadvisor/version",
"Comment": "0.13.0-32-g832c6e9",
"Rev": "832c6e94c325ecdf05da884880c503be45594818"
"Comment": "0.13.0-48-g9535dd2",
"Rev": "9535dd21e2aca94f024528679d22e3637178cda0"
},
{
"ImportPath": "github.com/google/go-github/github",

View File

@ -38,6 +38,7 @@ const (
storageApi = "storage"
attributesApi = "attributes"
versionApi = "version"
psApi = "ps"
)
// Interface for a cAdvisor API version
@ -304,7 +305,7 @@ func (self *version2_0) Version() string {
}
func (self *version2_0) SupportedRequestTypes() []string {
return []string{versionApi, attributesApi, eventsApi, machineApi, summaryApi, statsApi, specApi, storageApi}
return []string{versionApi, attributesApi, eventsApi, machineApi, summaryApi, statsApi, specApi, storageApi, psApi}
}
func (self *version2_0) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error {
@ -391,6 +392,17 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
return writeResult(fi, w)
case eventsApi:
return handleEventRequest(request, m, w, r)
case psApi:
// reuse container type from request.
// ignore recursive.
// TODO(rjnagal): consider count to limit ps output.
name := getContainerName(request)
glog.V(4).Infof("Api - Spec for container %q, options %+v", name, opt)
ps, err := m.GetProcessList(name, opt)
if err != nil {
return fmt.Errorf("process listing failed: %v", err)
}
return writeResult(ps, w)
default:
return fmt.Errorf("unknown request type %q", requestType)
}

View File

@ -0,0 +1,35 @@
// Copyright 2015 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 base
import (
"time"
"github.com/google/cadvisor/collector"
"github.com/google/cadvisor/info/v2"
)
type Collector struct {
}
var _ collector.Collector = &Collector{}
func (c *Collector) Collect() (time.Time, []v2.Metric, error) {
return time.Now(), []v2.Metrics{}, nil
}
func (c *Collector) Name() string {
return "default"
}

View File

@ -0,0 +1,17 @@
// Copyright 2015 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 base
import ()

View File

@ -18,6 +18,8 @@ import (
"fmt"
"strings"
"time"
"github.com/google/cadvisor/info/v2"
)
type collectorManager struct {
@ -46,17 +48,19 @@ func (cm *collectorManager) RegisterCollector(collector Collector) error {
return nil
}
func (cm *collectorManager) Collect() (time.Time, error) {
func (cm *collectorManager) Collect() (time.Time, []v2.Metric, error) {
var errors []error
// Collect from all collectors that are ready.
var next time.Time
var metrics []v2.Metric
for _, c := range cm.collectors {
if c.nextCollectionTime.Before(time.Now()) {
nextCollection, err := c.collector.Collect()
nextCollection, newMetrics, err := c.collector.Collect()
if err != nil {
errors = append(errors, err)
}
metrics = append(metrics, newMetrics...)
c.nextCollectionTime = nextCollection
}
@ -66,7 +70,7 @@ func (cm *collectorManager) Collect() (time.Time, error) {
}
}
return next, compileErrors(errors)
return next, metrics, compileErrors(errors)
}
// Make an error slice into a single error.

View File

@ -18,6 +18,7 @@ import (
"testing"
"time"
"github.com/google/cadvisor/info/v2"
"github.com/stretchr/testify/assert"
)
@ -27,9 +28,9 @@ type fakeCollector struct {
collectedFrom int
}
func (fc *fakeCollector) Collect() (time.Time, error) {
func (fc *fakeCollector) Collect() (time.Time, []v2.Metric, error) {
fc.collectedFrom++
return fc.nextCollectionTime, fc.err
return fc.nextCollectionTime, []v2.Metric{}, fc.err
}
func (fc *fakeCollector) Name() string {
@ -53,7 +54,7 @@ func TestCollect(t *testing.T) {
assert.NoError(cm.RegisterCollector(f2))
// First collection, everyone gets collected from.
nextTime, err := cm.Collect()
nextTime, _, err := cm.Collect()
assert.Equal(firstTime, nextTime)
assert.NoError(err)
assert.Equal(1, f1.collectedFrom)
@ -62,7 +63,7 @@ func TestCollect(t *testing.T) {
f1.nextCollectionTime = time.Now().Add(2 * time.Hour)
// Second collection, only the one that is ready gets collected from.
nextTime, err = cm.Collect()
nextTime, _, err = cm.Collect()
assert.Equal(secondTime, nextTime)
assert.NoError(err)
assert.Equal(2, f1.collectedFrom)

View File

@ -16,6 +16,8 @@ package collector
import (
"time"
"github.com/google/cadvisor/info/v2"
)
type FakeCollectorManager struct {
@ -25,7 +27,7 @@ func (fkm *FakeCollectorManager) RegisterCollector(collector Collector) error {
return nil
}
func (fkm *FakeCollectorManager) Collect() (time.Time, error) {
func (fkm *FakeCollectorManager) Collect() (time.Time, []v2.Metric, error) {
var zero time.Time
return zero, nil
return zero, []v2.Metric{}, nil
}

View File

@ -15,6 +15,7 @@
package collector
import (
"github.com/google/cadvisor/info/v2"
"time"
)
@ -26,7 +27,7 @@ type Collector interface {
// Returns the next time this collector should be collected from.
// Next collection time is always returned, even when an error occurs.
// A collection time of zero means no more collection.
Collect() (time.Time, error)
Collect() (time.Time, []v2.Metric, error)
// Name of this collector.
Name() string
@ -41,5 +42,5 @@ type CollectorManager interface {
// at which a collector will be ready to collect from.
// Next collection time is always returned, even when an error occurs.
// A collection time of zero means no more collection.
Collect() (time.Time, error)
Collect() (time.Time, []v2.Metric, error)
}

View File

@ -325,8 +325,7 @@ func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]
}
func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
// TODO(vmarmol): Implement.
return nil, nil
return containerLibcontainer.GetProcesses(self.cgroupManager)
}
func (self *dockerContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error {

View File

@ -96,6 +96,14 @@ func GetStats(cgroupManager cgroups.Manager, networkInterfaces []string) (*info.
return stats, nil
}
func GetProcesses(cgroupManager cgroups.Manager) ([]int, error) {
pids, err := cgroupManager.GetPids()
if err != nil {
return nil, err
}
return pids, nil
}
func DockerStateDir(dockerRoot string) string {
return path.Join(dockerRoot, "containers")
}
@ -186,10 +194,21 @@ func toContainerStats2(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.HierarchicalData.Pgmajfault = v
}
if v, ok := s.MemoryStats.Stats["total_inactive_anon"]; ok {
ret.Memory.WorkingSet = ret.Memory.Usage - v
if v, ok := s.MemoryStats.Stats["total_active_file"]; ok {
ret.Memory.WorkingSet -= v
workingSet := ret.Memory.Usage
if workingSet < v {
workingSet = 0
} else {
workingSet -= v
}
if v, ok := s.MemoryStats.Stats["total_inactive_file"]; ok {
if workingSet < v {
workingSet = 0
} else {
workingSet -= v
}
}
ret.Memory.WorkingSet = workingSet
}
}

View File

@ -264,6 +264,7 @@ func (self *rawContainerHandler) getFsStats(stats *info.ContainerStats) error {
Device: fs.Device,
Limit: fs.Capacity,
Usage: fs.Capacity - fs.Free,
Available: fs.Available,
ReadsCompleted: fs.DiskStats.ReadsCompleted,
ReadsMerged: fs.DiskStats.ReadsMerged,
SectorsRead: fs.DiskStats.SectorsRead,
@ -398,8 +399,7 @@ func (self *rawContainerHandler) ListThreads(listType container.ListType) ([]int
}
func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
// TODO(vmarmol): Implement
return nil, nil
return libcontainer.GetProcesses(self.cgroupManager)
}
func (self *rawContainerHandler) watchDirectory(dir string, containerName string) error {

View File

@ -20,6 +20,7 @@ package fs
/*
extern int getBytesFree(const char *path, unsigned long long *bytes);
extern int getBytesTotal(const char *path, unsigned long long *bytes);
extern int getBytesAvail(const char *path, unsigned long long *bytes);
*/
import "C"
@ -176,7 +177,7 @@ func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, er
_, hasMount := mountSet[partition.mountpoint]
_, hasDevice := deviceSet[device]
if mountSet == nil || (hasMount && !hasDevice) {
total, free, err := getVfsStats(partition.mountpoint)
total, free, avail, err := getVfsStats(partition.mountpoint)
if err != nil {
glog.Errorf("Statvfs failed. Error: %v", err)
} else {
@ -186,7 +187,7 @@ func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, er
Major: uint(partition.major),
Minor: uint(partition.minor),
}
fs := Fs{deviceInfo, total, free, diskStatsMap[device]}
fs := Fs{deviceInfo, total, free, avail, diskStatsMap[device]}
filesystems = append(filesystems, fs)
}
}
@ -287,18 +288,22 @@ func (self *RealFsInfo) GetDirUsage(dir string) (uint64, error) {
return usageInKb * 1024, nil
}
func getVfsStats(path string) (total uint64, free uint64, err error) {
func getVfsStats(path string) (total uint64, free uint64, avail uint64, err error) {
_p0, err := syscall.BytePtrFromString(path)
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}
res, err := C.getBytesFree((*C.char)(unsafe.Pointer(_p0)), (*_Ctype_ulonglong)(unsafe.Pointer(&free)))
if res != 0 {
return 0, 0, err
return 0, 0, 0, err
}
res, err = C.getBytesTotal((*C.char)(unsafe.Pointer(_p0)), (*_Ctype_ulonglong)(unsafe.Pointer(&total)))
if res != 0 {
return 0, 0, err
return 0, 0, 0, err
}
return total, free, nil
res, err = C.getBytesAvail((*C.char)(unsafe.Pointer(_p0)), (*_Ctype_ulonglong)(unsafe.Pointer(&avail)))
if res != 0 {
return 0, 0, 0, err
}
return total, free, avail, nil
}

View File

@ -21,3 +21,14 @@ int getBytesTotal(const char *path, unsigned long long *bytes) {
*bytes = buf.f_frsize * buf.f_blocks;
return 0;
}
// Bytes available to non-root.
int getBytesAvail(const char *path, unsigned long long *bytes) {
struct statvfs buf;
int res;
if ((res = statvfs(path, &buf)) && res != 0) {
return -1;
}
*bytes = buf.f_frsize * buf.f_bavail;
return 0;
}

View File

@ -24,6 +24,7 @@ type Fs struct {
DeviceInfo
Capacity uint64
Free uint64
Available uint64
DiskStats DiskStats
}

View File

@ -341,6 +341,9 @@ type FsStats struct {
// Number of bytes that is consumed by the container on this filesystem.
Usage uint64 `json:"usage"`
// Number of bytes available for non-root user.
Available uint64 `json:"available"`
// Number of reads completed
// This is the total number of reads completed successfully.
ReadsCompleted uint64 `json:"reads_completed"`

View File

@ -151,6 +151,9 @@ type FsInfo struct {
// Filesystem usage in bytes.
Capacity uint64 `json:"capacity"`
// Bytes available for non-root use.
Available uint64 `json:"available"`
// Number of bytes used on this filesystem.
Usage uint64 `json:"usage"`
@ -166,3 +169,16 @@ type RequestOptions struct {
// Whether to include stats for child subcontainers.
Recursive bool `json:"recursive"`
}
type ProcessInfo struct {
User string `json:"user"`
Pid int `json:"pid"`
Ppid int `json:"parent_pid"`
StartTime string `json:"start_time"`
PercentCpu string `json:"percent_cpu"`
RSS string `json:"rss"`
VirtualSize string `json:"virtual_size"`
Status string `json:"status"`
RunningTime string `json:"running_time"`
Cmd string `json:"cmd"`
}

View File

@ -0,0 +1,69 @@
// Copyright 2015 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 (
"time"
)
// Type of metric being exported.
type MetricType string
const (
// Instantaneous value. May increase or decrease.
MetricGauge MetricType = "gauge"
// A counter-like value that is only expected to increase.
MetricCumulative = "cumulative"
// Rate over a time period.
MetricDelta = "delta"
)
// An exported metric.
type Metric struct {
// The name of the metric.
Name string `json:"name"`
// Type of the metric.
Type MetricType `json:"type"`
// Metadata associated with this metric.
Labels map[string]string
// Value of the metric. Only one of these values will be
// available according to the output type of the metric.
// If no values are available, there are no data points.
IntPoints []IntPoint `json:"int_points,omitempty"`
FloatPoints []FloatPoint `json:"float_points,omitempty"`
}
// An integer metric data point.
type IntPoint struct {
// Time at which the metric was queried
Timestamp time.Time `json:"timestamp"`
// The value of the metric at this point.
Value int64 `json:"value"`
}
// A float metric data point.
type FloatPoint struct {
// Time at which the metric was queried
Timestamp time.Time `json:"timestamp"`
// The value of the metric at this point.
Value float64 `json:"value"`
}

View File

@ -18,7 +18,10 @@ import (
"flag"
"fmt"
"math"
"os/exec"
"sort"
"strconv"
"strings"
"sync"
"time"
@ -113,6 +116,63 @@ func (c *containerData) DerivedStats() (v2.DerivedStats, error) {
return c.summaryReader.DerivedStats()
}
func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) {
// report all processes for root.
isRoot := c.info.Name == "/"
pidMap := map[int]bool{}
if !isRoot {
pids, err := c.handler.ListProcesses(container.ListSelf)
if err != nil {
return nil, err
}
for _, pid := range pids {
pidMap[pid] = true
}
}
// TODO(rjnagal): Take format as an option?
format := "user,pid,ppid,stime,pcpu,rss,vsz,stat,time,comm"
args := []string{"-e", "-o", format}
expectedFields := 10
out, err := exec.Command("ps", args...).Output()
if err != nil {
return nil, fmt.Errorf("failed to execute ps command: %v", err)
}
processes := []v2.ProcessInfo{}
lines := strings.Split(string(out), "\n")
for _, line := range lines[1:] {
if len(line) == 0 {
continue
}
fields := strings.Fields(line)
if len(fields) < expectedFields {
return nil, fmt.Errorf("expected at least %d fields, found %d: output: %q", expectedFields, len(fields), line)
}
pid, err := strconv.Atoi(fields[1])
if err != nil {
return nil, fmt.Errorf("invalid pid %q: %v", fields[1], err)
}
ppid, err := strconv.Atoi(fields[2])
if err != nil {
return nil, fmt.Errorf("invalid ppid %q: %v", fields[2], err)
}
if isRoot || pidMap[pid] == true {
processes = append(processes, v2.ProcessInfo{
User: fields[0],
Pid: pid,
Ppid: ppid,
StartTime: fields[3],
PercentCpu: fields[4],
RSS: fields[5],
VirtualSize: fields[6],
Status: fields[7],
RunningTime: fields[8],
Cmd: strings.Join(fields[9:], " "),
})
}
}
return processes, nil
}
func newContainerData(containerName string, memoryStorage *memory.InMemoryStorage, handler container.ContainerHandler, loadReader cpuload.CpuLoadReader, logUsage bool, collectorManager collector.CollectorManager) (*containerData, error) {
if memoryStorage == nil {
return nil, fmt.Errorf("nil memory storage")
@ -232,8 +292,9 @@ func (c *containerData) housekeeping() {
}
}
// TODO(vmarmol): Export metrics.
// Run custom collectors.
nextCollectionTime, err := c.collectorManager.Collect()
nextCollectionTime, _, err := c.collectorManager.Collect()
if err != nil && c.allowErrorLogging() {
glog.Warningf("[%s] Collection failed: %v", c.info.Name, err)
}

View File

@ -90,6 +90,9 @@ type Manager interface {
// Returns information for all global filesystems if label is empty.
GetFsInfo(label string) ([]v2.FsInfo, error)
// Get ps output for a container.
GetProcessList(containerName string, options v2.RequestOptions) ([]v2.ProcessInfo, error)
// Get events streamed through passedChannel that fit the request.
WatchForEvents(request *events.Request) (*events.EventChannel, error)
@ -609,6 +612,7 @@ func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) {
Mountpoint: mountpoint,
Capacity: fs.Limit,
Usage: fs.Usage,
Available: fs.Available,
Labels: labels,
}
fsInfo = append(fsInfo, fi)
@ -640,6 +644,27 @@ func (m *manager) Exists(containerName string) bool {
return false
}
func (m *manager) GetProcessList(containerName string, options v2.RequestOptions) ([]v2.ProcessInfo, error) {
// override recursive. Only support single container listing.
options.Recursive = false
conts, err := m.getRequestedContainers(containerName, options)
if err != nil {
return nil, err
}
if len(conts) != 1 {
return nil, fmt.Errorf("Expected the request to match only one container")
}
// TODO(rjnagal): handle count? Only if we can do count by type (eg. top 5 cpu users)
ps := []v2.ProcessInfo{}
for _, cont := range conts {
ps, err = cont.GetProcessList()
if err != nil {
return nil, err
}
}
return ps, nil
}
// Create a container.
func (m *manager) createContainer(containerName string) error {
handler, accept, err := container.NewContainerHandler(containerName)

View File

@ -99,3 +99,8 @@ func (c *ManagerMock) GetFsInfo() ([]v2.FsInfo, error) {
args := c.Called()
return args.Get(0).([]v2.FsInfo), args.Error(1)
}
func (c *ManagerMock) GetProcessList(name string, options v2.RequestOptions) ([]v2.ProcessInfo, error) {
args := c.Called()
return args.Get(0).([]v2.ProcessInfo), args.Error(1)
}

View File

@ -108,6 +108,12 @@ const containersHtmlTemplate = `
</div>
<div id="usage-gauge" class="panel-body"></div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Processes</h3>
</div>
<div id="processes-top" class="panel-body"></div>
</div>
{{if .CpuAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff