diff --git a/info/v2/container.go b/info/v2/container.go index 2e6b5f5d..326c9ce0 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -181,5 +181,6 @@ type ProcessInfo struct { VirtualSize uint64 `json:"virtual_size"` Status string `json:"status"` RunningTime string `json:"running_time"` + CgroupPath string `json:"cgroup_path"` Cmd string `json:"cmd"` } diff --git a/manager/container.go b/manager/container.go index 19bec50c..1289f85e 100644 --- a/manager/container.go +++ b/manager/container.go @@ -19,6 +19,7 @@ import ( "fmt" "math" "os/exec" + "regexp" "sort" "strconv" "strings" @@ -41,6 +42,8 @@ var HousekeepingInterval = flag.Duration("housekeeping_interval", 1*time.Second, var maxHousekeepingInterval = flag.Duration("max_housekeeping_interval", 60*time.Second, "Largest interval to allow between container housekeepings") var allowDynamicHousekeeping = flag.Bool("allow_dynamic_housekeeping", true, "Whether to allow the housekeeping interval to be dynamic") +var cgroupPathRegExp = regexp.MustCompile(".*:devices:(.*?),.*") + // Decay value used for load average smoothing. Interval length of 10 seconds is used. var loadDecay = math.Exp(float64(-1 * (*HousekeepingInterval).Seconds() / 10)) @@ -116,6 +119,19 @@ func (c *containerData) DerivedStats() (v2.DerivedStats, error) { return c.summaryReader.DerivedStats() } +func (c *containerData) getCgroupPath(cgroups string) (string, error) { + if cgroups == "-" { + return "/", nil + } + matches := cgroupPathRegExp.FindSubmatch([]byte(cgroups)) + if len(matches) != 2 { + glog.V(3).Infof("failed to get devices cgroup path from %q", cgroups) + // return root in case of failures - devices hierarchy might not be enabled. + return "/", nil + } + return string(matches[1]), nil +} + func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { // report all processes for root. isRoot := c.info.Name == "/" @@ -130,9 +146,9 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { } } // TODO(rjnagal): Take format as an option? - format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm" + format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm,cgroup" args := []string{"-e", "-o", format} - expectedFields := 11 + expectedFields := 12 out, err := exec.Command("ps", args...).Output() if err != nil { return nil, fmt.Errorf("failed to execute ps command: %v", err) @@ -171,6 +187,14 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { if err != nil { return nil, fmt.Errorf("invalid virtual size %q: %v", fields[7], err) } + var cgroupPath string + if isRoot { + cgroupPath, err = c.getCgroupPath(fields[11]) + if err != nil { + return nil, fmt.Errorf("could not parse cgroup path from %q: %v", fields[10], err) + } + } + if isRoot || pidMap[pid] == true { processes = append(processes, v2.ProcessInfo{ User: fields[0], @@ -183,7 +207,8 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { VirtualSize: vs, Status: fields[8], RunningTime: fields[9], - Cmd: strings.Join(fields[10:], " "), + Cmd: fields[10], + CgroupPath: cgroupPath, }) } } diff --git a/pages/containers_html.go b/pages/containers_html.go index 3627faa3..41af3f95 100644 --- a/pages/containers_html.go +++ b/pages/containers_html.go @@ -215,7 +215,7 @@ const containersHtmlTemplate = ` {{end}} diff --git a/pages/static/containers_js.go b/pages/static/containers_js.go index a653b311..995b462a 100644 --- a/pages/static/containers_js.go +++ b/pages/static/containers_js.go @@ -462,12 +462,16 @@ function drawImages(images) { drawTable(titles, titleTypes, data, "docker-images", 30); } -function drawProcesses(processInfo) { +function drawProcesses(isRoot, rootDir, processInfo) { if (processInfo.length == 0) { return; } var titles = ["User", "PID", "PPID", "Start Time", "CPU %", "MEM %", "RSS", "Virtual Size", "Status", "Running Time", "Command"]; var titleTypes = ['string', 'number', 'number', 'string', 'number', 'number', 'number', 'number', 'string', 'string', 'string']; + if (isRoot) { + titles.push("Cgroup"); + titleTypes.push('string'); + } var data = [] for (var i = 0; i < processInfo.length; i++) { var elements = []; @@ -482,6 +486,12 @@ function drawProcesses(processInfo) { elements.push(processInfo[i].status); elements.push(processInfo[i].running_time); elements.push(processInfo[i].cmd); + if (isRoot) { + var cgroup = processInfo[i].cgroup_path + // Use the raw cgroup link as it works for all containers. + var cgroupLink = '' + cgroup.substr(0,30) + ' '; + elements.push({v:cgroup, f:cgroupLink}); + } data.push(elements); } drawTable(titles, titleTypes, data, "processes-top", 25); @@ -600,7 +610,7 @@ function drawCharts(machineInfo, containerInfo) { } // Executed when the page finishes loading. -function startPage(containerName, hasCpu, hasMemory, rootDir) { +function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) { // Don't fetch data if we don't have any resource. if (!hasCpu && !hasMemory) { return; @@ -612,11 +622,11 @@ function startPage(containerName, hasCpu, hasMemory, rootDir) { // Draw process information at start and refresh every 60s. getProcessInfo(rootDir, containerName, function(processInfo) { - drawProcesses(processInfo) + drawProcesses(isRoot, rootDir, processInfo) }); setInterval(function() { getProcessInfo(rootDir, containerName, function(processInfo) { - drawProcesses(processInfo) + drawProcesses(isRoot, rootDir, processInfo) }); }, 60000);