Merge pull request #296 from vmarmol/docker

Add /docker/ UI endpoint.
This commit is contained in:
Vish Kannan 2014-11-12 09:48:52 -08:00
commit 4910466050
7 changed files with 248 additions and 109 deletions

View File

@ -63,12 +63,12 @@ func main() {
// Register the raw driver.
if err := raw.Register(containerManager); err != nil {
glog.Fatalf("raw registration failed: %v.", err)
glog.Fatalf("Raw registration failed: %v.", err)
}
// Basic health handler.
if err := healthz.RegisterHandler(); err != nil {
glog.Fatalf("failed to register healthz handler: %s", err)
glog.Fatalf("Failed to register healthz handler: %s", err)
}
// Handler for static content.
@ -81,19 +81,15 @@ func main() {
// Register API handler.
if err := api.RegisterHandlers(containerManager); err != nil {
glog.Fatalf("failed to register API handlers: %s", err)
glog.Fatalf("Failed to register API handlers: %s", err)
}
// Redirect / to containers page.
http.Handle("/", http.RedirectHandler(pages.ContainersPage, http.StatusTemporaryRedirect))
// Register the handler for the containers page.
http.HandleFunc(pages.ContainersPage, func(w http.ResponseWriter, r *http.Request) {
err := pages.ServerContainersPage(containerManager, w, r.URL)
if err != nil {
fmt.Fprintf(w, "%s", err)
if err := pages.RegisterHandlers(containerManager); err != nil {
glog.Fatalf("Failed to register pages handlers: %s", err)
}
})
// Start the manager.
if err := containerManager.Start(); err != nil {

View File

@ -82,6 +82,26 @@ func IsDockerContainerName(name string) bool {
}
}
// Returns the Docker ID from the full container name.
func ContainerNameToDockerId(name string) string {
id := path.Base(name)
// Turn systemd cgroup name into Docker ID.
if useSystemd {
const systemdDockerPrefix = "docker-"
if strings.HasPrefix(id, systemdDockerPrefix) {
id = id[len(systemdDockerPrefix):]
}
const systemdScopeSuffix = ".scope"
if strings.HasSuffix(id, systemdScopeSuffix) {
id = id[:len(id)-len(systemdScopeSuffix)]
}
}
return id
}
// Returns a list of possible full container names for the specified Docker container name.
func FullDockerContainerNames(dockerName string) []string {
names := make([]string, 0, 2)
@ -107,7 +127,7 @@ func (self *dockerFactory) CanHandle(name string) (bool, error) {
}
// Check if the container is known to docker and it is active.
id := containerNameToDockerId(name)
id := ContainerNameToDockerId(name)
// We assume that if Inspect fails then the container is not known to docker.
ctnr, err := self.client.InspectContainer(id)

View File

@ -85,7 +85,7 @@ func newDockerContainerHandler(
if handler.isDockerRoot() {
return handler, nil
}
id := containerNameToDockerId(name)
id := ContainerNameToDockerId(name)
handler.id = id
ctnr, err := client.InspectContainer(id)
// We assume that if Inspect fails then the container is not known to docker.
@ -96,25 +96,6 @@ func newDockerContainerHandler(
return handler, nil
}
func containerNameToDockerId(name string) string {
id := path.Base(name)
// Turn systemd cgroup name into Docker ID.
if useSystemd {
const systemdDockerPrefix = "docker-"
if strings.HasPrefix(id, systemdDockerPrefix) {
id = id[len(systemdDockerPrefix):]
}
const systemdScopeSuffix = ".scope"
if strings.HasSuffix(id, systemdScopeSuffix) {
id = id[:len(id)-len(systemdScopeSuffix)]
}
}
return id
}
func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) {
return info.ContainerReference{
Name: self.name,

View File

@ -88,7 +88,6 @@ func (b ByteSize) Unit() string {
}
var funcMap = template.FuncMap{
"containerLink": containerLink,
"printMask": printMask,
"printCores": printCores,
"printShares": printShares,
@ -102,52 +101,6 @@ var funcMap = template.FuncMap{
"getFsUsagePercent": getFsUsagePercent,
}
// TODO(vmarmol): Consider housekeeping Spec too so we can show changes through time. We probably don't need it ever second though.
var pageTemplate *template.Template
type pageData struct {
ContainerName string
ParentContainers []info.ContainerReference
Subcontainers []info.ContainerReference
Spec info.ContainerSpec
Stats []*info.ContainerStats
MachineInfo *info.MachineInfo
ResourcesAvailable bool
CpuAvailable bool
MemoryAvailable bool
NetworkAvailable bool
FsAvailable bool
}
func init() {
pageTemplate = template.New("containersTemplate").Funcs(funcMap)
_, err := pageTemplate.Parse(containersHtmlTemplate)
if err != nil {
glog.Fatalf("Failed to parse template: %s", err)
}
}
// TODO(vmarmol): Escape this correctly.
func containerLink(container info.ContainerReference, basenameOnly bool, cssClasses string) interface{} {
var displayName string
containerName := container.Name
if len(container.Aliases) > 0 {
displayName = container.Aliases[0]
} else if basenameOnly {
displayName = path.Base(string(container.Name))
} else {
displayName = string(container.Name)
}
if container.Name == "root" {
containerName = "/"
} else if strings.Contains(container.Name, " ") {
// If it has a space, it is an a.k.a, so keep the base-name
containerName = container.Name[:strings.Index(container.Name, " ")]
}
return template.HTML(fmt.Sprintf("<a class=\"%s\" href=\"%s%s\">%s</a>", cssClasses, ContainersPage[:len(ContainersPage)-1], containerName, displayName))
}
func printMask(mask string, numCores int) interface{} {
masks := make([]string, numCores)
activeCores := getActiveCores(mask)
@ -266,7 +219,7 @@ func getFsUsagePercent(limit, used uint64) uint64 {
return uint64((float64(used) / float64(limit)) * 100)
}
func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
func serveContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
start := time.Now()
// The container name is the path after the handler
@ -278,8 +231,9 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL)
}
cont, err := m.GetContainerInfo(containerName, &reqParams)
if err != nil {
return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err)
return fmt.Errorf("Failed to get container %q with error: %v", containerName, err)
}
displayName := getContainerDisplayName(cont.ContainerReference)
// Get the MachineInfo
machineInfo, err := m.GetMachineInfo()
@ -288,41 +242,41 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL)
}
// Make a list of the parent containers and their links
var parentContainers []info.ContainerReference
parentContainers = append(parentContainers, info.ContainerReference{Name: "root"})
parentName := ""
for _, part := range strings.Split(string(cont.Name), "/") {
if part == "" {
pathParts := strings.Split(string(cont.Name), "/")
parentContainers := make([]link, 0, len(pathParts))
parentContainers = append(parentContainers, link{
Text: "root",
Link: ContainersPage,
})
for i := 1; i < len(pathParts); i++ {
// Skip empty parts.
if pathParts[i] == "" {
continue
}
parentName += "/" + part
parentContainers = append(parentContainers, info.ContainerReference{Name: parentName})
parentContainers = append(parentContainers, link{
Text: pathParts[i],
Link: path.Join(ContainersPage, path.Join(pathParts[1:i+1]...)),
})
}
// Pick the shortest name of the container as the display name.
displayName := cont.Name
for _, alias := range cont.Aliases {
if len(displayName) >= len(alias) {
displayName = alias
}
}
// Replace the last part of the parent containers with the displayName.
if displayName != cont.Name {
parentContainers[len(parentContainers)-1] = info.ContainerReference{
Name: fmt.Sprintf("%s (%s)", displayName, path.Base(cont.Name)),
}
// Build the links for the subcontainers.
subcontainerLinks := make([]link, 0, len(cont.Subcontainers))
for _, sub := range cont.Subcontainers {
subcontainerLinks = append(subcontainerLinks, link{
Text: getContainerDisplayName(sub),
Link: path.Join(ContainersPage, sub.Name),
})
}
data := &pageData{
ContainerName: displayName,
// TODO(vmarmol): Only use strings for this.
DisplayName: displayName,
ContainerName: cont.Name,
ParentContainers: parentContainers,
Subcontainers: cont.Subcontainers,
Subcontainers: subcontainerLinks,
Spec: cont.Spec,
Stats: cont.Stats,
MachineInfo: machineInfo,
ResourcesAvailable: cont.Spec.HasCpu || cont.Spec.HasMemory || cont.Spec.HasNetwork,
ResourcesAvailable: cont.Spec.HasCpu || cont.Spec.HasMemory || cont.Spec.HasNetwork || cont.Spec.HasFilesystem,
CpuAvailable: cont.Spec.HasCpu,
MemoryAvailable: cont.Spec.HasMemory,
NetworkAvailable: cont.Spec.HasNetwork,

View File

@ -17,7 +17,7 @@ package pages
const containersHtmlTemplate = `
<html>
<head>
<title>cAdvisor - Container {{.ContainerName}}</title>
<title>cAdvisor - {{.DisplayName}}</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
@ -39,11 +39,11 @@ const containersHtmlTemplate = `
</div>
<div class="col-sm-12">
<div class="page-header">
<h1>{{.ContainerName}}</h1>
<h1>{{.DisplayName}}</h1>
</div>
<ol class="breadcrumb">
{{range $parentContainer := .ParentContainers}}
<li>{{containerLink $parentContainer true ""}}</li>
<li><a href="{{$parentContainer.Link}}">{{$parentContainer.Text}}</a></li>
{{end}}
</ol>
</div>
@ -54,7 +54,7 @@ const containersHtmlTemplate = `
</div>
<div class="list-group">
{{range $subcontainer := .Subcontainers}}
{{containerLink $subcontainer false "list-group-item"}}
<a href="{{$subcontainer.Link}}" class="list-group-item">{{$subcontainer.Text}}</a>
{{end}}
</div>
</div>

106
pages/docker.go Normal file
View File

@ -0,0 +1,106 @@
package pages
import (
"fmt"
"net/http"
"net/url"
"path"
"time"
"github.com/golang/glog"
"github.com/google/cadvisor/container/docker"
"github.com/google/cadvisor/info"
"github.com/google/cadvisor/manager"
)
const DockerPage = "/docker/"
func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
start := time.Now()
// The container name is the path after the handler
containerName := u.Path[len(DockerPage)-1:]
var data *pageData
if containerName == "/" {
// Get the containers.
reqParams := info.ContainerInfoRequest{
NumStats: 0,
}
conts, err := m.DockerContainersInfo("/", &reqParams)
if err != nil {
return fmt.Errorf("Failed to get container %q with error: %v", containerName, err)
}
subcontainers := make([]link, 0, len(conts))
for _, cont := range conts {
subcontainers = append(subcontainers, link{
Text: getContainerDisplayName(cont.ContainerReference),
Link: path.Join("/docker", docker.ContainerNameToDockerId(cont.ContainerReference.Name)),
})
}
dockerContainersText := "Docker Containers"
data = &pageData{
DisplayName: dockerContainersText,
ParentContainers: []link{
link{
Text: dockerContainersText,
Link: DockerPage,
}},
Subcontainers: subcontainers,
}
} else {
// Get the container.
reqParams := info.ContainerInfoRequest{
NumStats: 60,
}
conts, err := m.DockerContainersInfo(containerName, &reqParams)
if err != nil {
return fmt.Errorf("failed to get container %q with error: %v", containerName, err)
}
if len(conts) != 1 {
return fmt.Errorf("received unexpected container for %q: %v", conts)
}
cont := conts[0]
displayName := getContainerDisplayName(cont.ContainerReference)
// Make a list of the parent containers and their links
var parentContainers []link
parentContainers = append(parentContainers, link{
Text: "Docker containers",
Link: DockerPage,
})
parentContainers = append(parentContainers, link{
Text: displayName,
Link: path.Join(DockerPage, docker.ContainerNameToDockerId(cont.Name)),
})
// Get the MachineInfo
machineInfo, err := m.GetMachineInfo()
if err != nil {
return err
}
data = &pageData{
DisplayName: displayName,
ContainerName: cont.Name,
ParentContainers: parentContainers,
Spec: cont.Spec,
Stats: cont.Stats,
MachineInfo: machineInfo,
ResourcesAvailable: cont.Spec.HasCpu || cont.Spec.HasMemory || cont.Spec.HasNetwork,
CpuAvailable: cont.Spec.HasCpu,
MemoryAvailable: cont.Spec.HasMemory,
NetworkAvailable: cont.Spec.HasNetwork,
FsAvailable: cont.Spec.HasFilesystem,
}
}
err := pageTemplate.Execute(w, data)
if err != nil {
glog.Errorf("Failed to apply template: %s", err)
}
glog.V(1).Infof("Request took %s", time.Since(start))
return nil
}

82
pages/pages.go Normal file
View File

@ -0,0 +1,82 @@
package pages
import (
"fmt"
"html/template"
"net/http"
"github.com/golang/glog"
"github.com/google/cadvisor/info"
"github.com/google/cadvisor/manager"
)
var pageTemplate *template.Template
type link struct {
// Text to show in the link.
Text string
// Web address to link to.
Link string
}
type pageData struct {
DisplayName string
ContainerName string
ParentContainers []link
Subcontainers []link
Spec info.ContainerSpec
Stats []*info.ContainerStats
MachineInfo *info.MachineInfo
ResourcesAvailable bool
CpuAvailable bool
MemoryAvailable bool
NetworkAvailable bool
FsAvailable bool
}
func init() {
pageTemplate = template.New("containersTemplate").Funcs(funcMap)
_, err := pageTemplate.Parse(containersHtmlTemplate)
if err != nil {
glog.Fatalf("Failed to parse template: %s", err)
}
}
// Register http handlers for pages.
func RegisterHandlers(containerManager manager.Manager) error {
// Register the handler for the containers page.
http.HandleFunc(ContainersPage, func(w http.ResponseWriter, r *http.Request) {
err := serveContainersPage(containerManager, w, r.URL)
if err != nil {
fmt.Fprintf(w, "%s", err)
}
})
// Register the handler for the docker page.
http.HandleFunc(DockerPage, func(w http.ResponseWriter, r *http.Request) {
err := serveDockerPage(containerManager, w, r.URL)
if err != nil {
fmt.Fprintf(w, "%s", err)
}
})
return nil
}
func getContainerDisplayName(cont info.ContainerReference) string {
// Pick the shortest name of the container as the display name.
displayName := cont.Name
for _, alias := range cont.Aliases {
if len(displayName) >= len(alias) {
displayName = alias
}
}
// Add the full container name to the display name.
if displayName != cont.Name {
displayName = fmt.Sprintf("%s (%s)", displayName, cont.Name)
}
return displayName
}