253 lines
6.1 KiB
Go
253 lines
6.1 KiB
Go
// Copyright 2014 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.
|
|
|
|
// Page for /containers/
|
|
package pages
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
info "github.com/google/cadvisor/info/v1"
|
|
"github.com/google/cadvisor/manager"
|
|
|
|
"github.com/golang/glog"
|
|
)
|
|
|
|
const ContainersPage = "/containers/"
|
|
|
|
// from http://golang.org/doc/effective_go.html#constants
|
|
type ByteSize float64
|
|
|
|
const (
|
|
_ = iota
|
|
// KB - kilobyte
|
|
KB ByteSize = 1 << (10 * iota)
|
|
// MB - megabyte
|
|
MB
|
|
// GB - gigabyte
|
|
GB
|
|
// TB - terabyte
|
|
TB
|
|
// PB - petabyte
|
|
PB
|
|
// EB - exabyte
|
|
EB
|
|
// ZB - zettabyte
|
|
ZB
|
|
// YB - yottabyte
|
|
YB
|
|
)
|
|
|
|
func (b ByteSize) Size() string {
|
|
for _, i := range [...]ByteSize{YB, ZB, EB, PB, TB, GB, MB, KB} {
|
|
if b >= i {
|
|
return fmt.Sprintf("%.2f", b/i)
|
|
}
|
|
}
|
|
return fmt.Sprintf("%.2f", b)
|
|
}
|
|
|
|
func (b ByteSize) Unit() string {
|
|
switch {
|
|
case b >= YB:
|
|
return "YB"
|
|
case b >= ZB:
|
|
return "ZB"
|
|
case b >= EB:
|
|
return "EB"
|
|
case b >= PB:
|
|
return "PB"
|
|
case b >= TB:
|
|
return "TB"
|
|
case b >= GB:
|
|
return "GB"
|
|
case b >= MB:
|
|
return "MB"
|
|
case b >= KB:
|
|
return "KB"
|
|
}
|
|
return "B"
|
|
}
|
|
|
|
var funcMap = template.FuncMap{
|
|
"printMask": printMask,
|
|
"printCores": printCores,
|
|
"printShares": printShares,
|
|
"printSize": printSize,
|
|
"printUnit": printUnit,
|
|
}
|
|
|
|
func printMask(mask string, numCores int) interface{} {
|
|
masks := make([]string, numCores)
|
|
activeCores := getActiveCores(mask)
|
|
for i := 0; i < numCores; i++ {
|
|
coreClass := "inactive-cpu"
|
|
if activeCores[i] {
|
|
coreClass = "active-cpu"
|
|
}
|
|
masks[i] = fmt.Sprintf("<span class=\"%s\">%d</span>", coreClass, i)
|
|
}
|
|
return template.HTML(strings.Join(masks, " "))
|
|
}
|
|
|
|
func getActiveCores(mask string) map[int]bool {
|
|
activeCores := make(map[int]bool)
|
|
for _, corebits := range strings.Split(mask, ",") {
|
|
cores := strings.Split(corebits, "-")
|
|
if len(cores) == 1 {
|
|
index, err := strconv.Atoi(cores[0])
|
|
if err != nil {
|
|
// Ignore malformed strings.
|
|
continue
|
|
}
|
|
activeCores[index] = true
|
|
} else if len(cores) == 2 {
|
|
start, err := strconv.Atoi(cores[0])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
end, err := strconv.Atoi(cores[1])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for i := start; i <= end; i++ {
|
|
activeCores[i] = true
|
|
}
|
|
}
|
|
}
|
|
return activeCores
|
|
}
|
|
|
|
func printCores(millicores *uint64) string {
|
|
cores := float64(*millicores) / 1000
|
|
return strconv.FormatFloat(cores, 'f', 3, 64)
|
|
}
|
|
|
|
func printShares(shares *uint64) string {
|
|
return fmt.Sprintf("%d", *shares)
|
|
}
|
|
|
|
// Size after which we consider memory to be "unlimited". This is not
|
|
// MaxInt64 due to rounding by the kernel.
|
|
const maxMemorySize = uint64(1 << 62)
|
|
|
|
func printSize(bytes uint64) string {
|
|
if bytes >= maxMemorySize {
|
|
return "unlimited"
|
|
}
|
|
return ByteSize(bytes).Size()
|
|
}
|
|
|
|
func printUnit(bytes uint64) string {
|
|
if bytes >= maxMemorySize {
|
|
return ""
|
|
}
|
|
return ByteSize(bytes).Unit()
|
|
}
|
|
|
|
func serveContainersPage(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(ContainersPage)-1:]
|
|
|
|
// Get the container.
|
|
reqParams := info.ContainerInfoRequest{
|
|
NumStats: 60,
|
|
}
|
|
cont, err := m.GetContainerInfo(containerName, &reqParams)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get container %q with error: %v", containerName, err)
|
|
}
|
|
displayName := getContainerDisplayName(cont.ContainerReference)
|
|
|
|
// Get the MachineInfo
|
|
machineInfo, err := m.GetMachineInfo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rootDir := getRootDir(containerName)
|
|
|
|
// Make a list of the parent containers and their links
|
|
pathParts := strings.Split(string(cont.Name), "/")
|
|
parentContainers := make([]link, 0, len(pathParts))
|
|
parentContainers = append(parentContainers, link{
|
|
Text: "root",
|
|
Link: path.Join(rootDir, ContainersPage),
|
|
})
|
|
for i := 1; i < len(pathParts); i++ {
|
|
// Skip empty parts.
|
|
if pathParts[i] == "" {
|
|
continue
|
|
}
|
|
parentContainers = append(parentContainers, link{
|
|
Text: pathParts[i],
|
|
Link: path.Join(rootDir, ContainersPage, path.Join(pathParts[1:i+1]...)),
|
|
})
|
|
}
|
|
|
|
// Build the links for the subcontainers.
|
|
subcontainerLinks := make([]link, 0, len(cont.Subcontainers))
|
|
for _, sub := range cont.Subcontainers {
|
|
if !m.Exists(sub.Name) {
|
|
continue
|
|
}
|
|
subcontainerLinks = append(subcontainerLinks, link{
|
|
Text: getContainerDisplayName(sub),
|
|
Link: path.Join(rootDir, ContainersPage, sub.Name),
|
|
})
|
|
}
|
|
|
|
data := &pageData{
|
|
DisplayName: displayName,
|
|
ContainerName: escapeContainerName(cont.Name),
|
|
ParentContainers: parentContainers,
|
|
Subcontainers: subcontainerLinks,
|
|
Spec: cont.Spec,
|
|
Stats: cont.Stats,
|
|
MachineInfo: machineInfo,
|
|
IsRoot: cont.Name == "/",
|
|
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,
|
|
FsAvailable: cont.Spec.HasFilesystem,
|
|
CustomMetricsAvailable: cont.Spec.HasCustomMetrics,
|
|
SubcontainersAvailable: len(subcontainerLinks) > 0,
|
|
Root: rootDir,
|
|
}
|
|
err = pageTemplate.Execute(w, data)
|
|
if err != nil {
|
|
glog.Errorf("Failed to apply template: %s", err)
|
|
}
|
|
|
|
glog.V(5).Infof("Request took %s", time.Since(start))
|
|
return nil
|
|
}
|
|
|
|
// Build a relative path to the root of the container page.
|
|
func getRootDir(containerName string) string {
|
|
// The root is at: container depth
|
|
levels := (strings.Count(containerName, "/"))
|
|
return strings.Repeat("../", levels)
|
|
}
|