rebase: update K8s packages to v0.32.1

Update K8s packages in go.mod to v0.32.1

Signed-off-by: Praveen M <m.praveen@ibm.com>
This commit is contained in:
Praveen M
2025-01-16 09:41:46 +05:30
committed by mergify[bot]
parent 5aef21ea4e
commit 7eb99fc6c9
2442 changed files with 273386 additions and 47788 deletions

View File

@ -0,0 +1,60 @@
// 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.
// Unmarshal's a Containers description json file. The json file contains
// an array of ContainerHint structs, each with a container's id and networkInterface
// This allows collecting stats about network interfaces configured outside docker
// and lxc
package common
import (
"encoding/json"
"flag"
"os"
)
var ArgContainerHints = flag.String("container_hints", "/etc/cadvisor/container_hints.json", "location of the container hints file")
type ContainerHints struct {
AllHosts []containerHint `json:"all_hosts,omitempty"`
}
type containerHint struct {
FullName string `json:"full_path,omitempty"`
NetworkInterface *networkInterface `json:"network_interface,omitempty"`
Mounts []Mount `json:"mounts,omitempty"`
}
type Mount struct {
HostDir string `json:"host_dir,omitempty"`
ContainerDir string `json:"container_dir,omitempty"`
}
type networkInterface struct {
VethHost string `json:"veth_host,omitempty"`
VethChild string `json:"veth_child,omitempty"`
}
func GetContainerHintsFromFile(containerHintsFile string) (ContainerHints, error) {
dat, err := os.ReadFile(containerHintsFile)
if os.IsNotExist(err) {
return ContainerHints{}, nil
}
var cHints ContainerHints
if err == nil {
err = json.Unmarshal(dat, &cHints)
}
return cHints, err
}

View File

@ -0,0 +1,155 @@
// 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.
// Handler for Docker containers.
package common
import (
"fmt"
"sync"
"time"
"github.com/google/cadvisor/fs"
"k8s.io/klog/v2"
)
type FsHandler interface {
Start()
Usage() FsUsage
Stop()
}
type FsUsage struct {
BaseUsageBytes uint64
TotalUsageBytes uint64
InodeUsage uint64
}
type realFsHandler struct {
sync.RWMutex
lastUpdate time.Time
usage FsUsage
period time.Duration
minPeriod time.Duration
rootfs string
extraDir string
fsInfo fs.FsInfo
// Tells the container to stop.
stopChan chan struct{}
}
const (
maxBackoffFactor = 20
)
const DefaultPeriod = time.Minute
var _ FsHandler = &realFsHandler{}
func NewFsHandler(period time.Duration, rootfs, extraDir string, fsInfo fs.FsInfo) FsHandler {
return &realFsHandler{
lastUpdate: time.Time{},
usage: FsUsage{},
period: period,
minPeriod: period,
rootfs: rootfs,
extraDir: extraDir,
fsInfo: fsInfo,
stopChan: make(chan struct{}, 1),
}
}
func (fh *realFsHandler) update() error {
var (
rootUsage, extraUsage fs.UsageInfo
rootErr, extraErr error
)
// TODO(vishh): Add support for external mounts.
if fh.rootfs != "" {
rootUsage, rootErr = fh.fsInfo.GetDirUsage(fh.rootfs)
}
if fh.extraDir != "" {
extraUsage, extraErr = fh.fsInfo.GetDirUsage(fh.extraDir)
}
// Wait to handle errors until after all operartions are run.
// An error in one will not cause an early return, skipping others
fh.Lock()
defer fh.Unlock()
fh.lastUpdate = time.Now()
if fh.rootfs != "" && rootErr == nil {
fh.usage.InodeUsage = rootUsage.Inodes
fh.usage.BaseUsageBytes = rootUsage.Bytes
fh.usage.TotalUsageBytes = rootUsage.Bytes
}
if fh.extraDir != "" && extraErr == nil {
if fh.rootfs != "" {
fh.usage.TotalUsageBytes += extraUsage.Bytes
} else {
// rootfs is empty, totalUsageBytes use extra usage bytes
fh.usage.TotalUsageBytes = extraUsage.Bytes
}
}
// Combine errors into a single error to return
if rootErr != nil || extraErr != nil {
return fmt.Errorf("rootDiskErr: %v, extraDiskErr: %v", rootErr, extraErr)
}
return nil
}
func (fh *realFsHandler) trackUsage() {
longOp := time.Second
for {
start := time.Now()
if err := fh.update(); err != nil {
klog.Errorf("failed to collect filesystem stats - %v", err)
fh.period = fh.period * 2
if fh.period > maxBackoffFactor*fh.minPeriod {
fh.period = maxBackoffFactor * fh.minPeriod
}
} else {
fh.period = fh.minPeriod
}
duration := time.Since(start)
if duration > longOp {
// adapt longOp time so that message doesn't continue to print
// if the long duration is persistent either because of slow
// disk or lots of containers.
longOp = longOp + time.Second
klog.V(2).Infof("fs: disk usage and inodes count on following dirs took %v: %v; will not log again for this container unless duration exceeds %v", duration, []string{fh.rootfs, fh.extraDir}, longOp)
}
select {
case <-fh.stopChan:
return
case <-time.After(fh.period):
}
}
}
func (fh *realFsHandler) Start() {
go fh.trackUsage()
}
func (fh *realFsHandler) Stop() {
close(fh.stopChan)
}
func (fh *realFsHandler) Usage() FsUsage {
fh.RLock()
defer fh.RUnlock()
return fh.usage
}

View File

@ -0,0 +1,459 @@
// 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 common
import (
"fmt"
"math"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/karrick/godirwalk"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/google/cadvisor/container"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils"
"k8s.io/klog/v2"
)
func DebugInfo(watches map[string][]string) map[string][]string {
out := make(map[string][]string)
lines := make([]string, 0, len(watches))
for containerName, cgroupWatches := range watches {
lines = append(lines, fmt.Sprintf("%s:", containerName))
for _, cg := range cgroupWatches {
lines = append(lines, fmt.Sprintf("\t%s", cg))
}
}
out["Inotify watches"] = lines
return out
}
var bootTime = func() time.Time {
now := time.Now()
var sysinfo unix.Sysinfo_t
if err := unix.Sysinfo(&sysinfo); err != nil {
return now
}
sinceBoot := time.Duration(sysinfo.Uptime) * time.Second
return now.Add(-1 * sinceBoot).Truncate(time.Minute)
}()
func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoFactory, hasNetwork, hasFilesystem bool) (info.ContainerSpec, error) {
return getSpecInternal(cgroupPaths, machineInfoFactory, hasNetwork, hasFilesystem, cgroups.IsCgroup2UnifiedMode())
}
func getSpecInternal(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoFactory, hasNetwork, hasFilesystem, cgroup2UnifiedMode bool) (info.ContainerSpec, error) {
var spec info.ContainerSpec
// Assume unified hierarchy containers.
// Get the lowest creation time from all hierarchies as the container creation time.
now := time.Now()
lowestTime := now
for _, cgroupPathDir := range cgroupPaths {
dir, err := os.Stat(cgroupPathDir)
if err == nil && dir.ModTime().Before(lowestTime) {
lowestTime = dir.ModTime()
} else if os.IsNotExist(err) {
// Directory does not exist, skip checking for files within.
continue
}
// The modified time of the cgroup directory sometimes changes whenever a subcontainer is created.
// eg. /docker will have creation time matching the creation of latest docker container.
// Use clone_children/events as a workaround as it isn't usually modified. It is only likely changed
// immediately after creating a container. If the directory modified time is lower, we use that.
cgroupPathFile := path.Join(cgroupPathDir, "cgroup.clone_children")
if cgroup2UnifiedMode {
cgroupPathFile = path.Join(cgroupPathDir, "cgroup.events")
}
fi, err := os.Stat(cgroupPathFile)
if err == nil && fi.ModTime().Before(lowestTime) {
lowestTime = fi.ModTime()
}
}
if lowestTime.Before(bootTime) {
lowestTime = bootTime
}
if lowestTime != now {
spec.CreationTime = lowestTime
}
// Get machine info.
mi, err := machineInfoFactory.GetMachineInfo()
if err != nil {
return spec, err
}
// CPU.
cpuRoot, ok := GetControllerPath(cgroupPaths, "cpu", cgroup2UnifiedMode)
if ok {
if utils.FileExists(cpuRoot) {
if cgroup2UnifiedMode {
spec.HasCpu = true
weight := readUInt64(cpuRoot, "cpu.weight")
if weight > 0 {
limit, err := convertCPUWeightToCPULimit(weight)
if err != nil {
klog.Errorf("GetSpec: Failed to read CPULimit from %q: %s", path.Join(cpuRoot, "cpu.weight"), err)
} else {
spec.Cpu.Limit = limit
}
}
max := readString(cpuRoot, "cpu.max")
if max != "" {
splits := strings.SplitN(max, " ", 2)
if len(splits) != 2 {
klog.Errorf("GetSpec: Failed to parse CPUmax from %q", path.Join(cpuRoot, "cpu.max"))
} else {
if splits[0] != "max" {
spec.Cpu.Quota = parseUint64String(splits[0])
}
spec.Cpu.Period = parseUint64String(splits[1])
}
}
} else {
spec.HasCpu = true
spec.Cpu.Limit = readUInt64(cpuRoot, "cpu.shares")
spec.Cpu.Period = readUInt64(cpuRoot, "cpu.cfs_period_us")
quota := readString(cpuRoot, "cpu.cfs_quota_us")
if quota != "" && quota != "-1" {
val, err := strconv.ParseUint(quota, 10, 64)
if err != nil {
klog.Errorf("GetSpec: Failed to parse CPUQuota from %q: %s", path.Join(cpuRoot, "cpu.cfs_quota_us"), err)
} else {
spec.Cpu.Quota = val
}
}
}
}
}
// Cpu Mask.
// This will fail for non-unified hierarchies. We'll return the whole machine mask in that case.
cpusetRoot, ok := GetControllerPath(cgroupPaths, "cpuset", cgroup2UnifiedMode)
if ok {
if utils.FileExists(cpusetRoot) {
spec.HasCpu = true
mask := ""
if cgroup2UnifiedMode {
mask = readString(cpusetRoot, "cpuset.cpus.effective")
} else {
mask = readString(cpusetRoot, "cpuset.cpus")
}
spec.Cpu.Mask = utils.FixCpuMask(mask, mi.NumCores)
}
}
// Memory
memoryRoot, ok := GetControllerPath(cgroupPaths, "memory", cgroup2UnifiedMode)
if ok {
if cgroup2UnifiedMode {
if utils.FileExists(path.Join(memoryRoot, "memory.max")) {
spec.HasMemory = true
spec.Memory.Reservation = readUInt64(memoryRoot, "memory.min")
spec.Memory.Limit = readUInt64(memoryRoot, "memory.max")
spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.swap.max")
}
} else {
if utils.FileExists(memoryRoot) {
spec.HasMemory = true
spec.Memory.Limit = readUInt64(memoryRoot, "memory.limit_in_bytes")
spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.memsw.limit_in_bytes")
spec.Memory.Reservation = readUInt64(memoryRoot, "memory.soft_limit_in_bytes")
}
}
}
// Hugepage
hugepageRoot, ok := cgroupPaths["hugetlb"]
if ok {
if utils.FileExists(hugepageRoot) {
spec.HasHugetlb = true
}
}
// Processes, read it's value from pids path directly
pidsRoot, ok := GetControllerPath(cgroupPaths, "pids", cgroup2UnifiedMode)
if ok {
if utils.FileExists(pidsRoot) {
spec.HasProcesses = true
spec.Processes.Limit = readUInt64(pidsRoot, "pids.max")
}
}
spec.HasNetwork = hasNetwork
spec.HasFilesystem = hasFilesystem
ioControllerName := "blkio"
if cgroup2UnifiedMode {
ioControllerName = "io"
}
if blkioRoot, ok := GetControllerPath(cgroupPaths, ioControllerName, cgroup2UnifiedMode); ok && utils.FileExists(blkioRoot) {
spec.HasDiskIo = true
}
return spec, nil
}
func GetControllerPath(cgroupPaths map[string]string, controllerName string, cgroup2UnifiedMode bool) (string, bool) {
ok := false
path := ""
if cgroup2UnifiedMode {
path, ok = cgroupPaths[""]
} else {
path, ok = cgroupPaths[controllerName]
}
return path, ok
}
func readString(dirpath string, file string) string {
cgroupFile := path.Join(dirpath, file)
// Read
out, err := os.ReadFile(cgroupFile)
if err != nil {
// Ignore non-existent files
if !os.IsNotExist(err) {
klog.Warningf("readString: Failed to read %q: %s", cgroupFile, err)
}
return ""
}
return strings.TrimSpace(string(out))
}
// Convert from [1-10000] to [2-262144]
func convertCPUWeightToCPULimit(weight uint64) (uint64, error) {
const (
// minWeight is the lowest value possible for cpu.weight
minWeight = 1
// maxWeight is the highest value possible for cpu.weight
maxWeight = 10000
)
if weight < minWeight || weight > maxWeight {
return 0, fmt.Errorf("convertCPUWeightToCPULimit: invalid cpu weight: %v", weight)
}
return 2 + ((weight-1)*262142)/9999, nil
}
func parseUint64String(strValue string) uint64 {
if strValue == "max" {
return math.MaxUint64
}
if strValue == "" {
return 0
}
val, err := strconv.ParseUint(strValue, 10, 64)
if err != nil {
klog.Errorf("parseUint64String: Failed to parse int %q: %s", strValue, err)
return 0
}
return val
}
func readUInt64(dirpath string, file string) uint64 {
out := readString(dirpath, file)
if out == "max" {
return math.MaxUint64
}
if out == "" {
return 0
}
val, err := strconv.ParseUint(out, 10, 64)
if err != nil {
klog.Errorf("readUInt64: Failed to parse int %q from file %q: %s", out, path.Join(dirpath, file), err)
return 0
}
return val
}
// Lists all directories under "path" and outputs the results as children of "parent".
func ListDirectories(dirpath string, parent string, recursive bool, output map[string]struct{}) error {
buf := make([]byte, godirwalk.MinimumScratchBufferSize)
return listDirectories(dirpath, parent, recursive, output, buf)
}
func listDirectories(dirpath string, parent string, recursive bool, output map[string]struct{}, buf []byte) error {
dirents, err := godirwalk.ReadDirents(dirpath, buf)
if err != nil {
// Ignore if this hierarchy does not exist.
if os.IsNotExist(errors.Cause(err)) {
err = nil
}
return err
}
for _, dirent := range dirents {
// We only grab directories.
if !dirent.IsDir() {
continue
}
dirname := dirent.Name()
name := path.Join(parent, dirname)
output[name] = struct{}{}
// List subcontainers if asked to.
if recursive {
err := listDirectories(path.Join(dirpath, dirname), name, true, output, buf)
if err != nil {
return err
}
}
}
return nil
}
func MakeCgroupPaths(mountPoints map[string]string, name string) map[string]string {
cgroupPaths := make(map[string]string, len(mountPoints))
for key, val := range mountPoints {
cgroupPaths[key] = path.Join(val, name)
}
return cgroupPaths
}
func CgroupExists(cgroupPaths map[string]string) bool {
// If any cgroup exists, the container is still alive.
for _, cgroupPath := range cgroupPaths {
if utils.FileExists(cgroupPath) {
return true
}
}
return false
}
func ListContainers(name string, cgroupPaths map[string]string, listType container.ListType) ([]info.ContainerReference, error) {
containers := make(map[string]struct{})
for _, cgroupPath := range cgroupPaths {
err := ListDirectories(cgroupPath, name, listType == container.ListRecursive, containers)
if err != nil {
return nil, err
}
}
// Make into container references.
ret := make([]info.ContainerReference, 0, len(containers))
for cont := range containers {
ret = append(ret, info.ContainerReference{
Name: cont,
})
}
return ret, nil
}
// AssignDeviceNamesToDiskStats assigns the Device field on the provided DiskIoStats by looking up
// the device major and minor identifiers in the provided device namer.
func AssignDeviceNamesToDiskStats(namer DeviceNamer, stats *info.DiskIoStats) {
assignDeviceNamesToPerDiskStats(
namer,
stats.IoMerged,
stats.IoQueued,
stats.IoServiceBytes,
stats.IoServiceTime,
stats.IoServiced,
stats.IoTime,
stats.IoWaitTime,
stats.Sectors,
)
}
// assignDeviceNamesToPerDiskStats looks up device names for the provided stats, caching names
// if necessary.
func assignDeviceNamesToPerDiskStats(namer DeviceNamer, diskStats ...[]info.PerDiskStats) {
devices := make(deviceIdentifierMap)
for _, stats := range diskStats {
for i, stat := range stats {
stats[i].Device = devices.Find(stat.Major, stat.Minor, namer)
}
}
}
// DeviceNamer returns string names for devices by their major and minor id.
type DeviceNamer interface {
// DeviceName returns the name of the device by its major and minor ids, or false if no
// such device is recognized.
DeviceName(major, minor uint64) (string, bool)
}
type MachineInfoNamer info.MachineInfo
func (n *MachineInfoNamer) DeviceName(major, minor uint64) (string, bool) {
for _, info := range n.DiskMap {
if info.Major == major && info.Minor == minor {
return "/dev/" + info.Name, true
}
}
for _, info := range n.Filesystems {
if info.DeviceMajor == major && info.DeviceMinor == minor {
return info.Device, true
}
}
return "", false
}
type deviceIdentifier struct {
major uint64
minor uint64
}
type deviceIdentifierMap map[deviceIdentifier]string
// Find locates the device name by device identifier out of from, caching the result as necessary.
func (m deviceIdentifierMap) Find(major, minor uint64, namer DeviceNamer) string {
d := deviceIdentifier{major, minor}
if s, ok := m[d]; ok {
return s
}
s, _ := namer.DeviceName(major, minor)
m[d] = s
return s
}
// RemoveNetMetrics is used to remove any network metrics from the given MetricSet.
// It returns the original set as is if remove is false, or if there are no metrics
// to remove.
func RemoveNetMetrics(metrics container.MetricSet, remove bool) container.MetricSet {
if !remove {
return metrics
}
// Check if there is anything we can remove, to avoid useless copying.
if !metrics.HasAny(container.AllNetworkMetrics) {
return metrics
}
// A copy of all metrics except for network ones.
return metrics.Difference(container.AllNetworkMetrics)
}

View File

@ -0,0 +1,135 @@
// 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 common
import (
"sync"
inotify "k8s.io/utils/inotify"
)
// Watcher for container-related inotify events in the cgroup hierarchy.
//
// Implementation is thread-safe.
type InotifyWatcher struct {
// Underlying inotify watcher.
watcher *inotify.Watcher
// Map of containers being watched to cgroup paths watched for that container.
containersWatched map[string]map[string]bool
// Lock for all datastructure access.
lock sync.Mutex
}
func NewInotifyWatcher() (*InotifyWatcher, error) {
w, err := inotify.NewWatcher()
if err != nil {
return nil, err
}
return &InotifyWatcher{
watcher: w,
containersWatched: make(map[string]map[string]bool),
}, nil
}
// Add a watch to the specified directory. Returns if the container was already being watched.
func (iw *InotifyWatcher) AddWatch(containerName, dir string) (bool, error) {
iw.lock.Lock()
defer iw.lock.Unlock()
cgroupsWatched, alreadyWatched := iw.containersWatched[containerName]
// Register an inotify notification.
if !cgroupsWatched[dir] {
err := iw.watcher.AddWatch(dir, inotify.InCreate|inotify.InDelete|inotify.InMove)
if err != nil {
return alreadyWatched, err
}
if cgroupsWatched == nil {
cgroupsWatched = make(map[string]bool)
}
cgroupsWatched[dir] = true
}
// Record our watching of the container.
if !alreadyWatched {
iw.containersWatched[containerName] = cgroupsWatched
}
return alreadyWatched, nil
}
// Remove watch from the specified directory. Returns if this was the last watch on the specified container.
func (iw *InotifyWatcher) RemoveWatch(containerName, dir string) (bool, error) {
iw.lock.Lock()
defer iw.lock.Unlock()
// If we don't have a watch registered for this, just return.
cgroupsWatched, ok := iw.containersWatched[containerName]
if !ok {
return false, nil
}
// Remove the inotify watch if it exists.
if cgroupsWatched[dir] {
err := iw.watcher.RemoveWatch(dir)
if err != nil {
return false, nil
}
delete(cgroupsWatched, dir)
}
// Remove the record if this is the last watch.
if len(cgroupsWatched) == 0 {
delete(iw.containersWatched, containerName)
return true, nil
}
return false, nil
}
// Errors are returned on this channel.
func (iw *InotifyWatcher) Error() chan error {
return iw.watcher.Error
}
// Events are returned on this channel.
func (iw *InotifyWatcher) Event() chan *inotify.Event {
return iw.watcher.Event
}
// Closes the inotify watcher.
func (iw *InotifyWatcher) Close() error {
return iw.watcher.Close()
}
// Returns a map of containers to the cgroup paths being watched.
func (iw *InotifyWatcher) GetWatches() map[string][]string {
out := make(map[string][]string, len(iw.containersWatched))
for k, v := range iw.containersWatched {
out[k] = mapToSlice(v)
}
return out
}
func mapToSlice(m map[string]bool) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}

View File

@ -0,0 +1,79 @@
// 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.
// Package container defines types for sub-container events and also
// defines an interface for container operation handlers.
package container
import info "github.com/google/cadvisor/info/v1"
// ListType describes whether listing should be just for a
// specific container or performed recursively.
type ListType int
const (
ListSelf ListType = iota
ListRecursive
)
type ContainerType int
const (
ContainerTypeRaw ContainerType = iota
ContainerTypeDocker
ContainerTypeCrio
ContainerTypeContainerd
ContainerTypeMesos
ContainerTypePodman
)
// Interface for container operation handlers.
type ContainerHandler interface {
// Returns the ContainerReference
ContainerReference() (info.ContainerReference, error)
// Returns container's isolation spec.
GetSpec() (info.ContainerSpec, error)
// Returns the current stats values of the container.
GetStats() (*info.ContainerStats, error)
// Returns the subcontainers of this container.
ListContainers(listType ListType) ([]info.ContainerReference, error)
// Returns the processes inside this container.
ListProcesses(listType ListType) ([]int, error)
// Returns absolute cgroup path for the requested resource.
GetCgroupPath(resource string) (string, error)
// Returns container labels, if available.
GetContainerLabels() map[string]string
// Returns the container's ip address, if available
GetContainerIPAddress() string
// Returns whether the container still exists.
Exists() bool
// Cleanup frees up any resources being held like fds or go routines, etc.
Cleanup()
// Start starts any necessary background goroutines - must be cleaned up in Cleanup().
// It is expected that most implementations will be a no-op.
Start()
// Type of handler
Type() ContainerType
}

View File

@ -0,0 +1,162 @@
// Copyright 2017 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 containerd
import (
"context"
"errors"
"fmt"
"net"
"sync"
"time"
containersapi "github.com/containerd/containerd/api/services/containers/v1"
tasksapi "github.com/containerd/containerd/api/services/tasks/v1"
versionapi "github.com/containerd/containerd/api/services/version/v1"
tasktypes "github.com/containerd/containerd/api/types/task"
"github.com/containerd/errdefs"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure"
emptypb "google.golang.org/protobuf/types/known/emptypb"
"github.com/google/cadvisor/container/containerd/containers"
"github.com/google/cadvisor/container/containerd/pkg/dialer"
)
type client struct {
containerService containersapi.ContainersClient
taskService tasksapi.TasksClient
versionService versionapi.VersionClient
}
type ContainerdClient interface {
LoadContainer(ctx context.Context, id string) (*containers.Container, error)
TaskPid(ctx context.Context, id string) (uint32, error)
Version(ctx context.Context) (string, error)
}
var (
ErrTaskIsInUnknownState = errors.New("containerd task is in unknown state") // used when process reported in containerd task is in Unknown State
)
var once sync.Once
var ctrdClient ContainerdClient = nil
const (
maxBackoffDelay = 3 * time.Second
baseBackoffDelay = 100 * time.Millisecond
connectionTimeout = 2 * time.Second
maxMsgSize = 16 * 1024 * 1024 // 16MB
)
// Client creates a containerd client
func Client(address, namespace string) (ContainerdClient, error) {
var retErr error
once.Do(func() {
tryConn, err := net.DialTimeout("unix", address, connectionTimeout)
if err != nil {
retErr = fmt.Errorf("containerd: cannot unix dial containerd api service: %v", err)
return
}
tryConn.Close()
connParams := grpc.ConnectParams{
Backoff: backoff.DefaultConfig,
}
connParams.Backoff.BaseDelay = baseBackoffDelay
connParams.Backoff.MaxDelay = maxBackoffDelay
//nolint:staticcheck // SA1019
gopts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(dialer.ContextDialer),
grpc.WithBlock(),
grpc.WithConnectParams(connParams),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)),
}
unary, stream := newNSInterceptors(namespace)
gopts = append(gopts,
grpc.WithUnaryInterceptor(unary),
grpc.WithStreamInterceptor(stream),
)
ctx, cancel := context.WithTimeout(context.Background(), connectionTimeout)
defer cancel()
//nolint:staticcheck // SA1019
conn, err := grpc.DialContext(ctx, dialer.DialAddress(address), gopts...)
if err != nil {
retErr = err
return
}
ctrdClient = &client{
containerService: containersapi.NewContainersClient(conn),
taskService: tasksapi.NewTasksClient(conn),
versionService: versionapi.NewVersionClient(conn),
}
})
return ctrdClient, retErr
}
func (c *client) LoadContainer(ctx context.Context, id string) (*containers.Container, error) {
r, err := c.containerService.Get(ctx, &containersapi.GetContainerRequest{
ID: id,
})
if err != nil {
return nil, errdefs.FromGRPC(err)
}
return containerFromProto(r.Container), nil
}
func (c *client) TaskPid(ctx context.Context, id string) (uint32, error) {
response, err := c.taskService.Get(ctx, &tasksapi.GetRequest{
ContainerID: id,
})
if err != nil {
return 0, errdefs.FromGRPC(err)
}
if response.Process.Status == tasktypes.Status_UNKNOWN {
return 0, ErrTaskIsInUnknownState
}
return response.Process.Pid, nil
}
func (c *client) Version(ctx context.Context) (string, error) {
response, err := c.versionService.Version(ctx, &emptypb.Empty{})
if err != nil {
return "", errdefs.FromGRPC(err)
}
return response.Version, nil
}
func containerFromProto(containerpb *containersapi.Container) *containers.Container {
var runtime containers.RuntimeInfo
// TODO: is nil check required for containerpb
if containerpb.Runtime != nil {
runtime = containers.RuntimeInfo{
Name: containerpb.Runtime.Name,
Options: containerpb.Runtime.Options,
}
}
return &containers.Container{
ID: containerpb.ID,
Labels: containerpb.Labels,
Image: containerpb.Image,
Runtime: runtime,
Spec: containerpb.Spec,
Snapshotter: containerpb.Snapshotter,
SnapshotKey: containerpb.SnapshotKey,
Extensions: containerpb.Extensions,
}
}

View File

@ -0,0 +1,125 @@
// Copyright 2017 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.
/*
Copyright The containerd Authors.
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 containers
import (
"context"
"time"
"google.golang.org/protobuf/types/known/anypb"
)
// Container represents the set of data pinned by a container. Unless otherwise
// noted, the resources here are considered in use by the container.
//
// The resources specified in this object are used to create tasks from the container.
type Container struct {
// ID uniquely identifies the container in a namespace.
//
// This property is required and cannot be changed after creation.
ID string
// Labels provide metadata extension for a container.
//
// These are optional and fully mutable.
Labels map[string]string
// Image specifies the image reference used for a container.
//
// This property is optional and mutable.
Image string
// Runtime specifies which runtime should be used when launching container
// tasks.
//
// This property is required and immutable.
Runtime RuntimeInfo
// Spec should carry the runtime specification used to implement the
// container.
//
// This field is required but mutable.
Spec *anypb.Any
// SnapshotKey specifies the snapshot key to use for the container's root
// filesystem. When starting a task from this container, a caller should
// look up the mounts from the snapshot service and include those on the
// task create request.
//
// This field is not required but mutable.
SnapshotKey string
// Snapshotter specifies the snapshotter name used for rootfs
//
// This field is not required but immutable.
Snapshotter string
// CreatedAt is the time at which the container was created.
CreatedAt time.Time
// UpdatedAt is the time at which the container was updated.
UpdatedAt time.Time
// Extensions stores client-specified metadata
Extensions map[string]*anypb.Any
}
// RuntimeInfo holds runtime specific information
type RuntimeInfo struct {
Name string
Options *anypb.Any
}
// Store interacts with the underlying container storage
type Store interface {
// Get a container using the id.
//
// Container object is returned on success. If the id is not known to the
// store, an error will be returned.
Get(ctx context.Context, id string) (Container, error)
// List returns containers that match one or more of the provided filters.
List(ctx context.Context, filters ...string) ([]Container, error)
// Create a container in the store from the provided container.
Create(ctx context.Context, container Container) (Container, error)
// Update the container with the provided container object. ID must be set.
//
// If one or more fieldpaths are provided, only the field corresponding to
// the fieldpaths will be mutated.
Update(ctx context.Context, container Container, fieldpaths ...string) (Container, error)
// Delete a container using the id.
//
// nil will be returned on success. If the container is not known to the
// store, ErrNotFound will be returned.
Delete(ctx context.Context, id string) error
}

View File

@ -0,0 +1,157 @@
// Copyright 2017 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 containerd
import (
"flag"
"fmt"
"path"
"regexp"
"strings"
"golang.org/x/net/context"
"k8s.io/klog/v2"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/watcher"
)
var ArgContainerdEndpoint = flag.String("containerd", "/run/containerd/containerd.sock", "containerd endpoint")
var ArgContainerdNamespace = flag.String("containerd-namespace", "k8s.io", "containerd namespace")
var containerdEnvMetadataWhiteList = flag.String("containerd_env_metadata_whitelist", "", "DEPRECATED: this flag will be removed, please use `env_metadata_whitelist`. A comma-separated list of environment variable keys matched with specified prefix that needs to be collected for containerd containers")
// The namespace under which containerd aliases are unique.
const k8sContainerdNamespace = "containerd"
// Regexp that identifies containerd cgroups, containers started with
// --cgroup-parent have another prefix than 'containerd'
var containerdCgroupRegexp = regexp.MustCompile(`([a-z0-9]{64})`)
type containerdFactory struct {
machineInfoFactory info.MachineInfoFactory
client ContainerdClient
version string
// Information about the mounted cgroup subsystems.
cgroupSubsystems map[string]string
// Information about mounted filesystems.
fsInfo fs.FsInfo
includedMetrics container.MetricSet
}
func (f *containerdFactory) String() string {
return k8sContainerdNamespace
}
func (f *containerdFactory) NewContainerHandler(name string, metadataEnvAllowList []string, inHostNamespace bool) (handler container.ContainerHandler, err error) {
client, err := Client(*ArgContainerdEndpoint, *ArgContainerdNamespace)
if err != nil {
return
}
containerdMetadataEnvAllowList := strings.Split(*containerdEnvMetadataWhiteList, ",")
// prefer using the unified metadataEnvAllowList
if len(metadataEnvAllowList) != 0 {
containerdMetadataEnvAllowList = metadataEnvAllowList
}
return newContainerdContainerHandler(
client,
name,
f.machineInfoFactory,
f.fsInfo,
f.cgroupSubsystems,
inHostNamespace,
containerdMetadataEnvAllowList,
f.includedMetrics,
)
}
// Returns the containerd ID from the full container name.
func ContainerNameToContainerdID(name string) string {
id := path.Base(name)
if matches := containerdCgroupRegexp.FindStringSubmatch(id); matches != nil {
return matches[1]
}
return id
}
// isContainerName returns true if the cgroup with associated name
// corresponds to a containerd container.
func isContainerName(name string) bool {
// TODO: May be check with HasPrefix ContainerdNamespace
if strings.HasSuffix(name, ".mount") {
return false
}
return containerdCgroupRegexp.MatchString(path.Base(name))
}
// Containerd can handle and accept all containerd created containers
func (f *containerdFactory) CanHandleAndAccept(name string) (bool, bool, error) {
// if the container is not associated with containerd, we can't handle it or accept it.
if !isContainerName(name) {
return false, false, nil
}
// Check if the container is known to containerd and it is running.
id := ContainerNameToContainerdID(name)
// If container and task lookup in containerd fails then we assume
// that the container state is not known to containerd
ctx := context.Background()
_, err := f.client.LoadContainer(ctx, id)
if err != nil {
return false, false, fmt.Errorf("failed to load container: %v", err)
}
return true, true, nil
}
func (f *containerdFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
// Register root container before running this function!
func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) error {
client, err := Client(*ArgContainerdEndpoint, *ArgContainerdNamespace)
if err != nil {
return fmt.Errorf("unable to create containerd client: %v", err)
}
containerdVersion, err := client.Version(context.Background())
if err != nil {
return fmt.Errorf("failed to fetch containerd client version: %v", err)
}
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics)
if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err)
}
klog.V(1).Infof("Registering containerd factory")
f := &containerdFactory{
cgroupSubsystems: cgroupSubsystems,
client: client,
fsInfo: fsInfo,
machineInfoFactory: factory,
version: containerdVersion,
includedMetrics: includedMetrics,
}
container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw})
return nil
}

View File

@ -0,0 +1,49 @@
// Copyright 2017 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.
// This code has been taken from containerd repo to avoid large library import
package containerd
import (
"github.com/google/cadvisor/container/containerd/namespaces"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
type namespaceInterceptor struct {
namespace string
}
func (ni namespaceInterceptor) unary(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
_, ok := namespaces.Namespace(ctx)
if !ok {
ctx = namespaces.WithNamespace(ctx, ni.namespace)
}
return invoker(ctx, method, req, reply, cc, opts...)
}
func (ni namespaceInterceptor) stream(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
_, ok := namespaces.Namespace(ctx)
if !ok {
ctx = namespaces.WithNamespace(ctx, ni.namespace)
}
return streamer(ctx, desc, cc, method, opts...)
}
func newNSInterceptors(ns string) (grpc.UnaryClientInterceptor, grpc.StreamClientInterceptor) {
ni := namespaceInterceptor{
namespace: ns,
}
return grpc.UnaryClientInterceptor(ni.unary), grpc.StreamClientInterceptor(ni.stream)
}

View File

@ -0,0 +1,250 @@
// Copyright 2017 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.
// Handler for containerd containers.
package containerd
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/containerd/errdefs"
"github.com/opencontainers/runc/libcontainer/cgroups"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/net/context"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
containerlibcontainer "github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
)
type containerdContainerHandler struct {
machineInfoFactory info.MachineInfoFactory
// Absolute path to the cgroup hierarchies of this container.
// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
cgroupPaths map[string]string
fsInfo fs.FsInfo
// Metadata associated with the container.
reference info.ContainerReference
envs map[string]string
labels map[string]string
// Image name used for this container.
image string
// Filesystem handler.
includedMetrics container.MetricSet
libcontainerHandler *containerlibcontainer.Handler
}
var _ container.ContainerHandler = &containerdContainerHandler{}
// newContainerdContainerHandler returns a new container.ContainerHandler
func newContainerdContainerHandler(
client ContainerdClient,
name string,
machineInfoFactory info.MachineInfoFactory,
fsInfo fs.FsInfo,
cgroupSubsystems map[string]string,
inHostNamespace bool,
metadataEnvAllowList []string,
includedMetrics container.MetricSet,
) (container.ContainerHandler, error) {
// Create the cgroup paths.
cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems, name)
// Generate the equivalent cgroup manager for this container.
cgroupManager, err := containerlibcontainer.NewCgroupManager(name, cgroupPaths)
if err != nil {
return nil, err
}
id := ContainerNameToContainerdID(name)
// We assume that if load fails then the container is not known to containerd.
ctx := context.Background()
cntr, err := client.LoadContainer(ctx, id)
if err != nil {
return nil, err
}
var spec specs.Spec
if err := json.Unmarshal(cntr.Spec.Value, &spec); err != nil {
return nil, err
}
// Cgroup is created during task creation. When cadvisor sees the cgroup,
// task may not be fully created yet. Use a retry+backoff to tolerant the
// race condition.
// TODO(random-liu): Use cri-containerd client to talk with cri-containerd
// instead. cri-containerd has some internal synchronization to make sure
// `ContainerStatus` only returns result after `StartContainer` finishes.
var taskPid uint32
backoff := 100 * time.Millisecond
retry := 5
for {
taskPid, err = client.TaskPid(ctx, id)
if err == nil {
break
}
// Retry when task is not created yet or task is in unknown state (likely in process of initializing)
isRetriableError := errdefs.IsNotFound(err) || errors.Is(err, ErrTaskIsInUnknownState)
if !isRetriableError || retry == 0 {
return nil, err
}
retry--
time.Sleep(backoff)
backoff *= 2
}
rootfs := "/"
if !inHostNamespace {
rootfs = "/rootfs"
}
containerReference := info.ContainerReference{
Id: id,
Name: name,
Namespace: k8sContainerdNamespace,
Aliases: []string{id, name},
}
// Containers that don't have their own network -- this includes
// containers running in Kubernetes pods that use the network of the
// infrastructure container -- does not need their stats to be
// reported. This stops metrics being reported multiple times for each
// container in a pod.
metrics := common.RemoveNetMetrics(includedMetrics, cntr.Labels["io.cri-containerd.kind"] != "sandbox")
libcontainerHandler := containerlibcontainer.NewHandler(cgroupManager, rootfs, int(taskPid), metrics)
handler := &containerdContainerHandler{
machineInfoFactory: machineInfoFactory,
cgroupPaths: cgroupPaths,
fsInfo: fsInfo,
envs: make(map[string]string),
labels: cntr.Labels,
includedMetrics: metrics,
reference: containerReference,
libcontainerHandler: libcontainerHandler,
}
// Add the name and bare ID as aliases of the container.
handler.image = cntr.Image
for _, exposedEnv := range metadataEnvAllowList {
if exposedEnv == "" {
// if no containerdEnvWhitelist provided, len(metadataEnvAllowList) == 1, metadataEnvAllowList[0] == ""
continue
}
for _, envVar := range spec.Process.Env {
if envVar != "" {
splits := strings.SplitN(envVar, "=", 2)
if len(splits) == 2 && strings.HasPrefix(splits[0], exposedEnv) {
handler.envs[splits[0]] = splits[1]
}
}
}
}
return handler, nil
}
func (h *containerdContainerHandler) ContainerReference() (info.ContainerReference, error) {
return h.reference, nil
}
func (h *containerdContainerHandler) GetSpec() (info.ContainerSpec, error) {
// TODO: Since we dont collect disk usage stats for containerd, we set hasFilesystem
// to false. Revisit when we support disk usage stats for containerd
hasFilesystem := false
hasNet := h.includedMetrics.Has(container.NetworkUsageMetrics)
spec, err := common.GetSpec(h.cgroupPaths, h.machineInfoFactory, hasNet, hasFilesystem)
spec.Labels = h.labels
spec.Envs = h.envs
spec.Image = h.image
return spec, err
}
func (h *containerdContainerHandler) getFsStats(stats *info.ContainerStats) error {
mi, err := h.machineInfoFactory.GetMachineInfo()
if err != nil {
return err
}
if h.includedMetrics.Has(container.DiskIOMetrics) {
common.AssignDeviceNamesToDiskStats((*common.MachineInfoNamer)(mi), &stats.DiskIo)
}
return nil
}
func (h *containerdContainerHandler) GetStats() (*info.ContainerStats, error) {
stats, err := h.libcontainerHandler.GetStats()
if err != nil {
return stats, err
}
// Get filesystem stats.
err = h.getFsStats(stats)
return stats, err
}
func (h *containerdContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
return []info.ContainerReference{}, nil
}
func (h *containerdContainerHandler) GetCgroupPath(resource string) (string, error) {
var res string
if !cgroups.IsCgroup2UnifiedMode() {
res = resource
}
path, ok := h.cgroupPaths[res]
if !ok {
return "", fmt.Errorf("could not find path for resource %q for container %q", resource, h.reference.Name)
}
return path, nil
}
func (h *containerdContainerHandler) GetContainerLabels() map[string]string {
return h.labels
}
func (h *containerdContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
return h.libcontainerHandler.GetProcesses()
}
func (h *containerdContainerHandler) Exists() bool {
return common.CgroupExists(h.cgroupPaths)
}
func (h *containerdContainerHandler) Type() container.ContainerType {
return container.ContainerTypeContainerd
}
func (h *containerdContainerHandler) Start() {
}
func (h *containerdContainerHandler) Cleanup() {
}
func (h *containerdContainerHandler) GetContainerIPAddress() string {
// containerd doesnt take care of networking.So it doesnt maintain networking states
return ""
}

View File

@ -0,0 +1,86 @@
// Copyright 2017 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.
/*
Copyright The containerd Authors.
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 identifiers provides common validation for identifiers and keys
// across containerd.
//
// Identifiers in containerd must be a alphanumeric, allowing limited
// underscores, dashes and dots.
//
// While the character set may be expanded in the future, identifiers
// are guaranteed to be safely used as filesystem path components.
package identifiers
import (
"regexp"
"github.com/containerd/errdefs"
"github.com/pkg/errors"
)
const (
maxLength = 76
alphanum = `[A-Za-z0-9]+`
separators = `[._-]`
)
var (
// identifierRe defines the pattern for valid identifiers.
identifierRe = regexp.MustCompile(reAnchor(alphanum + reGroup(separators+reGroup(alphanum)) + "*"))
)
// Validate returns nil if the string s is a valid identifier.
//
// identifiers are similar to the domain name rules according to RFC 1035, section 2.3.1. However
// rules in this package are relaxed to allow numerals to follow period (".") and mixed case is
// allowed.
//
// In general identifiers that pass this validation should be safe for use as filesystem path components.
func Validate(s string) error {
if len(s) == 0 {
return errors.Wrapf(errdefs.ErrInvalidArgument, "identifier must not be empty")
}
if len(s) > maxLength {
return errors.Wrapf(errdefs.ErrInvalidArgument, "identifier %q greater than maximum length (%d characters)", s, maxLength)
}
if !identifierRe.MatchString(s) {
return errors.Wrapf(errdefs.ErrInvalidArgument, "identifier %q must match %v", s, identifierRe)
}
return nil
}
func reGroup(s string) string {
return `(?:` + s + `)`
}
func reAnchor(s string) string {
return `^` + s + `$`
}

View File

@ -0,0 +1,30 @@
// Copyright 2019 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.
// The install package registers containerd.NewPlugin() as the "containerd" container provider when imported
package install
import (
"k8s.io/klog/v2"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/containerd"
)
func init() {
err := container.RegisterPlugin("containerd", containerd.NewPlugin())
if err != nil {
klog.Fatalf("Failed to register containerd plugin: %v", err)
}
}

View File

@ -0,0 +1,92 @@
// Copyright 2017 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.
/*
Copyright The containerd Authors.
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 namespaces
import (
"context"
"os"
"github.com/containerd/errdefs"
"github.com/pkg/errors"
"github.com/google/cadvisor/container/containerd/identifiers"
)
const (
// NamespaceEnvVar is the environment variable key name
NamespaceEnvVar = "CONTAINERD_NAMESPACE"
// Default is the name of the default namespace
Default = "default"
)
type namespaceKey struct{}
// WithNamespace sets a given namespace on the context
func WithNamespace(ctx context.Context, namespace string) context.Context {
ctx = context.WithValue(ctx, namespaceKey{}, namespace) // set our key for namespace
// also store on the grpc and ttrpc headers so it gets picked up by any clients that
// are using this.
return withTTRPCNamespaceHeader(withGRPCNamespaceHeader(ctx, namespace), namespace)
}
// NamespaceFromEnv uses the namespace defined in CONTAINERD_NAMESPACE or
// default
func NamespaceFromEnv(ctx context.Context) context.Context {
namespace := os.Getenv(NamespaceEnvVar)
if namespace == "" {
namespace = Default
}
return WithNamespace(ctx, namespace)
}
// Namespace returns the namespace from the context.
//
// The namespace is not guaranteed to be valid.
func Namespace(ctx context.Context) (string, bool) {
namespace, ok := ctx.Value(namespaceKey{}).(string)
if !ok {
if namespace, ok = fromGRPCHeader(ctx); !ok {
return fromTTRPCHeader(ctx)
}
}
return namespace, ok
}
// NamespaceRequired returns the valid namespace from the context or an error.
func NamespaceRequired(ctx context.Context) (string, error) {
namespace, ok := Namespace(ctx)
if !ok || namespace == "" {
return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "namespace is required")
}
if err := identifiers.Validate(namespace); err != nil {
return "", errors.Wrap(err, "namespace validation")
}
return namespace, nil
}

View File

@ -0,0 +1,74 @@
// Copyright 2017 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.
/*
Copyright The containerd Authors.
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 namespaces
import (
"context"
"google.golang.org/grpc/metadata"
)
const (
// GRPCHeader defines the header name for specifying a containerd namespace.
GRPCHeader = "containerd-namespace"
)
// NOTE(stevvooe): We can stub this file out if we don't want a grpc dependency here.
func withGRPCNamespaceHeader(ctx context.Context, namespace string) context.Context {
// also store on the grpc headers so it gets picked up by any clients that
// are using this.
nsheader := metadata.Pairs(GRPCHeader, namespace)
md, ok := metadata.FromOutgoingContext(ctx) // merge with outgoing context.
if !ok {
md = nsheader
} else {
// order ensures the latest is first in this list.
md = metadata.Join(nsheader, md)
}
return metadata.NewOutgoingContext(ctx, md)
}
func fromGRPCHeader(ctx context.Context) (string, bool) {
// try to extract for use in grpc servers.
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
// TODO(stevvooe): Check outgoing context?
return "", false
}
values := md[GRPCHeader]
if len(values) == 0 {
return "", false
}
return values[0], true
}

View File

@ -0,0 +1,57 @@
// Copyright 2017 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.
/*
Copyright The containerd Authors.
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 namespaces
import "context"
// Store provides introspection about namespaces.
//
// Note that these are slightly different than other objects, which are record
// oriented. A namespace is really just a name and a set of labels. Objects
// that belong to a namespace are returned when the namespace is assigned to a
// given context.
type Store interface {
Create(ctx context.Context, namespace string, labels map[string]string) error
Labels(ctx context.Context, namespace string) (map[string]string, error)
SetLabel(ctx context.Context, namespace, key, value string) error
List(ctx context.Context) ([]string, error)
// Delete removes the namespace. The namespace must be empty to be deleted.
Delete(ctx context.Context, namespace string, opts ...DeleteOpts) error
}
// DeleteInfo specifies information for the deletion of a namespace
type DeleteInfo struct {
// Name of the namespace
Name string
}
// DeleteOpts allows the caller to set options for namespace deletion
type DeleteOpts func(context.Context, *DeleteInfo) error

View File

@ -0,0 +1,64 @@
// Copyright 2017 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.
/*
Copyright The containerd Authors.
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 namespaces
import (
"context"
"github.com/containerd/ttrpc"
)
const (
// TTRPCHeader defines the header name for specifying a containerd namespace
TTRPCHeader = "containerd-namespace-ttrpc"
)
func copyMetadata(src ttrpc.MD) ttrpc.MD {
md := ttrpc.MD{}
for k, v := range src {
md[k] = append(md[k], v...)
}
return md
}
func withTTRPCNamespaceHeader(ctx context.Context, namespace string) context.Context {
md, ok := ttrpc.GetMetadata(ctx)
if !ok {
md = ttrpc.MD{}
} else {
md = copyMetadata(md)
}
md.Set(TTRPCHeader, namespace)
return ttrpc.WithMetadata(ctx, md)
}
func fromTTRPCHeader(ctx context.Context) (string, bool) {
return ttrpc.GetMetadataValue(ctx, TTRPCHeader)
}

View File

@ -0,0 +1,92 @@
// Copyright 2017 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.
/*
Copyright The containerd Authors.
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 dialer
import (
"context"
"net"
"time"
"github.com/pkg/errors"
)
type dialResult struct {
c net.Conn
err error
}
// ContextDialer returns a GRPC net.Conn connected to the provided address
func ContextDialer(ctx context.Context, address string) (net.Conn, error) {
if deadline, ok := ctx.Deadline(); ok {
return timeoutDialer(address, time.Until(deadline))
}
return timeoutDialer(address, 0)
}
// Dialer returns a GRPC net.Conn connected to the provided address
// Deprecated: use ContextDialer and grpc.WithContextDialer.
var Dialer = timeoutDialer
func timeoutDialer(address string, timeout time.Duration) (net.Conn, error) {
var (
stopC = make(chan struct{})
synC = make(chan *dialResult)
)
go func() {
defer close(synC)
for {
select {
case <-stopC:
return
default:
c, err := dialer(address, timeout)
if isNoent(err) {
<-time.After(10 * time.Millisecond)
continue
}
synC <- &dialResult{c, err}
return
}
}
}()
select {
case dr := <-synC:
return dr.c, dr.err
case <-time.After(timeout):
close(stopC)
go func() {
dr := <-synC
if dr != nil && dr.c != nil {
dr.c.Close()
}
}()
return nil, errors.Errorf("dial %s: timeout", address)
}
}

View File

@ -0,0 +1,66 @@
// Copyright 2017 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.
//go:build !windows
// +build !windows
/*
Copyright The containerd Authors.
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 dialer
import (
"fmt"
"net"
"os"
"strings"
"syscall"
"time"
)
// DialAddress returns the address with unix:// prepended to the
// provided address
func DialAddress(address string) string {
return fmt.Sprintf("unix://%s", address)
}
func isNoent(err error) bool {
if err != nil {
if nerr, ok := err.(*net.OpError); ok {
if serr, ok := nerr.Err.(*os.SyscallError); ok {
if serr.Err == syscall.ENOENT {
return true
}
}
}
}
return false
}
func dialer(address string, timeout time.Duration) (net.Conn, error) {
address = strings.TrimPrefix(address, "unix://")
return net.DialTimeout("unix", address, timeout)
}

View File

@ -0,0 +1,51 @@
// Copyright 2017 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.
/*
Copyright The containerd Authors.
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 dialer
import (
"net"
"os"
"time"
winio "github.com/Microsoft/go-winio"
)
func isNoent(err error) bool {
return os.IsNotExist(err)
}
func dialer(address string, timeout time.Duration) (net.Conn, error) {
return winio.DialPipe(address, &timeout)
}
// DialAddress returns the dial address
func DialAddress(address string) string {
return address
}

View File

@ -0,0 +1,38 @@
// Copyright 2019 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 containerd
import (
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/watcher"
)
// NewPlugin returns an implementation of container.Plugin suitable for passing to container.RegisterPlugin()
func NewPlugin() container.Plugin {
return &plugin{}
}
type plugin struct{}
func (p *plugin) InitializeFSContext(context *fs.Context) error {
return nil
}
func (p *plugin) Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) (watcher.ContainerWatcher, error) {
err := Register(factory, fsInfo, includedMetrics)
return nil, err
}

View File

@ -0,0 +1,167 @@
// Copyright 2017 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 crio
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"net"
"net/http"
"sync"
"syscall"
"time"
)
var crioClientTimeout = flag.Duration("crio_client_timeout", time.Duration(0), "CRI-O client timeout. Default is no timeout.")
const (
CrioSocket = "/var/run/crio/crio.sock"
maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
)
var (
theClient CrioClient
clientErr error
crioClientOnce sync.Once
)
// Info represents CRI-O information as sent by the CRI-O server
type Info struct {
StorageDriver string `json:"storage_driver"`
StorageRoot string `json:"storage_root"`
StorageImage string `json:"storage_image"`
}
// ContainerInfo represents a given container information
type ContainerInfo struct {
Name string `json:"name"`
Pid int `json:"pid"`
Image string `json:"image"`
CreatedTime int64 `json:"created_time"`
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
LogPath string `json:"log_path"`
Root string `json:"root"`
IP string `json:"ip_address"`
IPs []string `json:"ip_addresses"`
}
type CrioClient interface {
Info() (Info, error)
ContainerInfo(string) (*ContainerInfo, error)
}
type crioClientImpl struct {
client *http.Client
}
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
if len(addr) > maxUnixSocketPathSize {
return fmt.Errorf("Unix socket path %q is too long", addr)
}
// No need for compression in local communications.
tr.DisableCompression = true
tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
return net.DialTimeout(proto, addr, 32*time.Second)
}
return nil
}
// Client returns a new configured CRI-O client
func Client() (CrioClient, error) {
crioClientOnce.Do(func() {
tr := new(http.Transport)
theClient = nil
if clientErr = configureUnixTransport(tr, "unix", CrioSocket); clientErr != nil {
return
}
theClient = &crioClientImpl{
client: &http.Client{
Transport: tr,
Timeout: *crioClientTimeout,
},
}
})
return theClient, clientErr
}
func getRequest(path string) (*http.Request, error) {
req, err := http.NewRequest("GET", path, nil)
if err != nil {
return nil, err
}
// For local communications over a unix socket, it doesn't matter what
// the host is. We just need a valid and meaningful host name.
req.Host = "crio"
req.URL.Host = CrioSocket
req.URL.Scheme = "http"
return req, nil
}
// Info returns generic info from the CRI-O server
func (c *crioClientImpl) Info() (Info, error) {
info := Info{}
req, err := getRequest("/info")
if err != nil {
return info, err
}
resp, err := c.client.Do(req)
if err != nil {
return info, err
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return info, err
}
return info, nil
}
// ContainerInfo returns information about a given container
func (c *crioClientImpl) ContainerInfo(id string) (*ContainerInfo, error) {
req, err := getRequest("/containers/" + id)
if err != nil {
return nil, err
}
cInfo := ContainerInfo{}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// golang's http.Do doesn't return an error if non 200 response code is returned
// handle this case here, rather than failing to decode the body
if resp.StatusCode != http.StatusOK {
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Error finding container %s: Status %d", id, resp.StatusCode)
}
return nil, fmt.Errorf("Error finding container %s: Status %d returned error %s", id, resp.StatusCode, string(respBody))
}
if err := json.NewDecoder(resp.Body).Decode(&cInfo); err != nil {
return nil, err
}
if len(cInfo.IP) > 0 {
return &cInfo, nil
}
if len(cInfo.IPs) > 0 {
cInfo.IP = cInfo.IPs[0]
}
return &cInfo, nil
}

View File

@ -0,0 +1,166 @@
// Copyright 2017 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 crio
import (
"fmt"
"path"
"regexp"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/watcher"
"k8s.io/klog/v2"
)
// The namespace under which crio aliases are unique.
const CrioNamespace = "crio"
// The namespace systemd runs components under.
const SystemdNamespace = "system-systemd"
// Regexp that identifies CRI-O cgroups
var crioCgroupRegexp = regexp.MustCompile(`([a-z0-9]{64})`)
type storageDriver string
const (
// TODO add full set of supported drivers in future..
overlayStorageDriver storageDriver = "overlay"
overlay2StorageDriver storageDriver = "overlay2"
)
type crioFactory struct {
machineInfoFactory info.MachineInfoFactory
storageDriver storageDriver
storageDir string
// Information about the mounted cgroup subsystems.
cgroupSubsystems map[string]string
// Information about mounted filesystems.
fsInfo fs.FsInfo
includedMetrics container.MetricSet
client CrioClient
}
func (f *crioFactory) String() string {
return CrioNamespace
}
func (f *crioFactory) NewContainerHandler(name string, metadataEnvAllowList []string, inHostNamespace bool) (handler container.ContainerHandler, err error) {
client, err := Client()
if err != nil {
return
}
handler, err = newCrioContainerHandler(
client,
name,
f.machineInfoFactory,
f.fsInfo,
f.storageDriver,
f.storageDir,
f.cgroupSubsystems,
inHostNamespace,
metadataEnvAllowList,
f.includedMetrics,
)
return
}
// Returns the CRIO ID from the full container name.
func ContainerNameToCrioId(name string) string {
id := path.Base(name)
if matches := crioCgroupRegexp.FindStringSubmatch(id); matches != nil {
return matches[1]
}
return id
}
// isContainerName returns true if the cgroup with associated name
// corresponds to a crio container.
func isContainerName(name string) bool {
// always ignore .mount cgroup even if associated with crio and delegate to systemd
if strings.HasSuffix(name, ".mount") {
return false
}
return crioCgroupRegexp.MatchString(path.Base(name))
}
// crio handles all containers under /crio
func (f *crioFactory) CanHandleAndAccept(name string) (bool, bool, error) {
if strings.HasPrefix(path.Base(name), "crio-conmon") {
// TODO(runcom): should we include crio-conmon cgroups?
return false, false, nil
}
if !strings.HasPrefix(path.Base(name), CrioNamespace) {
return false, false, nil
}
if strings.HasPrefix(path.Base(name), SystemdNamespace) {
return true, false, nil
}
// if the container is not associated with CRI-O, we can't handle it or accept it.
if !isContainerName(name) {
return false, false, nil
}
return true, true, nil
}
func (f *crioFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
// Register root container before running this function!
func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) error {
client, err := Client()
if err != nil {
return err
}
info, err := client.Info()
if err != nil {
return err
}
// TODO determine crio version so we can work differently w/ future versions if needed
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics)
if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err)
}
klog.V(1).Infof("Registering CRI-O factory")
f := &crioFactory{
client: client,
cgroupSubsystems: cgroupSubsystems,
fsInfo: fsInfo,
machineInfoFactory: factory,
storageDriver: storageDriver(info.StorageDriver),
storageDir: info.StorageRoot,
includedMetrics: includedMetrics,
}
container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw})
return nil
}

View File

@ -0,0 +1,362 @@
// Copyright 2017 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.
// Handler for CRI-O containers.
package crio
import (
"fmt"
"path/filepath"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
containerlibcontainer "github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
)
type crioContainerHandler struct {
client CrioClient
name string
machineInfoFactory info.MachineInfoFactory
// Absolute path to the cgroup hierarchies of this container.
// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
cgroupPaths map[string]string
// the CRI-O storage driver
storageDriver storageDriver
fsInfo fs.FsInfo
rootfsStorageDir string
// Metadata associated with the container.
envs map[string]string
labels map[string]string
// TODO
// crio version handling...
// Image name used for this container.
image string
// The network mode of the container
// TODO
// Filesystem handler.
fsHandler common.FsHandler
// The IP address of the container
ipAddress string
includedMetrics container.MetricSet
reference info.ContainerReference
libcontainerHandler *containerlibcontainer.Handler
cgroupManager cgroups.Manager
rootFs string
pidKnown bool
}
var _ container.ContainerHandler = &crioContainerHandler{}
// newCrioContainerHandler returns a new container.ContainerHandler
func newCrioContainerHandler(
client CrioClient,
name string,
machineInfoFactory info.MachineInfoFactory,
fsInfo fs.FsInfo,
storageDriver storageDriver,
storageDir string,
cgroupSubsystems map[string]string,
inHostNamespace bool,
metadataEnvAllowList []string,
includedMetrics container.MetricSet,
) (container.ContainerHandler, error) {
// Create the cgroup paths.
cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems, name)
// Generate the equivalent cgroup manager for this container.
cgroupManager, err := containerlibcontainer.NewCgroupManager(name, cgroupPaths)
if err != nil {
return nil, err
}
rootFs := "/"
if !inHostNamespace {
rootFs = "/rootfs"
}
id := ContainerNameToCrioId(name)
pidKnown := true
cInfo, err := client.ContainerInfo(id)
if err != nil {
return nil, err
}
if cInfo.Pid == 0 {
// If pid is not known yet, network related stats can not be retrieved by the
// libcontainer handler GetStats(). In this case, the crio handler GetStats()
// will reattempt to get the pid and, if now known, will construct the libcontainer
// handler. This libcontainer handler is then cached and reused without additional
// calls to crio.
pidKnown = false
}
// passed to fs handler below ...
// XXX: this is using the full container logpath, as constructed by the CRI
// /var/log/pods/<pod_uuid>/container_instance.log
// It's not actually a log dir, as the CRI doesn't have per-container dirs
// under /var/log/pods/<pod_uuid>/
// We can't use /var/log/pods/<pod_uuid>/ to count per-container log usage.
// We use the container log file directly.
storageLogDir := cInfo.LogPath
// Determine the rootfs storage dir
rootfsStorageDir := cInfo.Root
// TODO(runcom): CRI-O doesn't strip /merged but we need to in order to
// get device ID from root, otherwise, it's going to error out as overlay
// mounts doesn't have fixed dev ids.
rootfsStorageDir = strings.TrimSuffix(rootfsStorageDir, "/merged")
switch storageDriver {
case overlayStorageDriver, overlay2StorageDriver:
// overlay and overlay2 driver are the same "overlay2" driver so treat
// them the same.
rootfsStorageDir = filepath.Join(rootfsStorageDir, "diff")
}
containerReference := info.ContainerReference{
Id: id,
Name: name,
Aliases: []string{cInfo.Name, id},
Namespace: CrioNamespace,
}
// Find out if we need network metrics reported for this container.
// Containers that don't have their own network -- this includes
// containers running in Kubernetes pods that use the network of the
// infrastructure container -- does not need their stats to be
// reported. This stops metrics being reported multiple times for each
// container in a pod.
metrics := common.RemoveNetMetrics(includedMetrics, cInfo.Labels["io.kubernetes.container.name"] != "POD")
libcontainerHandler := containerlibcontainer.NewHandler(cgroupManager, rootFs, cInfo.Pid, metrics)
// TODO: extract object mother method
handler := &crioContainerHandler{
client: client,
name: name,
machineInfoFactory: machineInfoFactory,
cgroupPaths: cgroupPaths,
storageDriver: storageDriver,
fsInfo: fsInfo,
rootfsStorageDir: rootfsStorageDir,
envs: make(map[string]string),
labels: cInfo.Labels,
includedMetrics: metrics,
reference: containerReference,
libcontainerHandler: libcontainerHandler,
cgroupManager: cgroupManager,
rootFs: rootFs,
pidKnown: pidKnown,
}
handler.image = cInfo.Image
// TODO: we wantd to know graph driver DeviceId (dont think this is needed now)
// ignore err and get zero as default, this happens with sandboxes, not sure why...
// kube isn't sending restart count in labels for sandboxes.
restartCount, _ := strconv.Atoi(cInfo.Annotations["io.kubernetes.container.restartCount"])
// Only adds restartcount label if it's greater than 0
if restartCount > 0 {
handler.labels["restartcount"] = strconv.Itoa(restartCount)
}
handler.ipAddress = cInfo.IP
// we optionally collect disk usage metrics
if includedMetrics.Has(container.DiskUsageMetrics) {
handler.fsHandler = common.NewFsHandler(common.DefaultPeriod, rootfsStorageDir, storageLogDir, fsInfo)
}
// TODO for env vars we wanted to show from container.Config.Env from whitelist
//for _, exposedEnv := range metadataEnvAllowList {
//klog.V(4).Infof("TODO env whitelist: %v", exposedEnv)
//}
return handler, nil
}
func (h *crioContainerHandler) Start() {
if h.fsHandler != nil {
h.fsHandler.Start()
}
}
func (h *crioContainerHandler) Cleanup() {
if h.fsHandler != nil {
h.fsHandler.Stop()
}
}
func (h *crioContainerHandler) ContainerReference() (info.ContainerReference, error) {
return h.reference, nil
}
func (h *crioContainerHandler) GetSpec() (info.ContainerSpec, error) {
hasFilesystem := h.includedMetrics.Has(container.DiskUsageMetrics)
hasNet := h.includedMetrics.Has(container.NetworkUsageMetrics)
spec, err := common.GetSpec(h.cgroupPaths, h.machineInfoFactory, hasNet, hasFilesystem)
spec.Labels = h.labels
spec.Envs = h.envs
spec.Image = h.image
return spec, err
}
func (h *crioContainerHandler) getFsStats(stats *info.ContainerStats) error {
mi, err := h.machineInfoFactory.GetMachineInfo()
if err != nil {
return err
}
if h.includedMetrics.Has(container.DiskIOMetrics) {
common.AssignDeviceNamesToDiskStats((*common.MachineInfoNamer)(mi), &stats.DiskIo)
}
if !h.includedMetrics.Has(container.DiskUsageMetrics) {
return nil
}
var device string
switch h.storageDriver {
case overlay2StorageDriver, overlayStorageDriver:
deviceInfo, err := h.fsInfo.GetDirFsDevice(h.rootfsStorageDir)
if err != nil {
return fmt.Errorf("unable to determine device info for dir: %v: %v", h.rootfsStorageDir, err)
}
device = deviceInfo.Device
default:
return nil
}
var (
limit uint64
fsType string
)
// crio does not impose any filesystem limits for containers. So use capacity as limit.
for _, fs := range mi.Filesystems {
if fs.Device == device {
limit = fs.Capacity
fsType = fs.Type
break
}
}
if fsType == "" {
return fmt.Errorf("unable to determine fs type for device: %v", device)
}
fsStat := info.FsStats{Device: device, Type: fsType, Limit: limit}
usage := h.fsHandler.Usage()
fsStat.BaseUsage = usage.BaseUsageBytes
fsStat.Usage = usage.TotalUsageBytes
fsStat.Inodes = usage.InodeUsage
stats.Filesystem = append(stats.Filesystem, fsStat)
return nil
}
func (h *crioContainerHandler) getLibcontainerHandler() *containerlibcontainer.Handler {
if h.pidKnown {
return h.libcontainerHandler
}
id := ContainerNameToCrioId(h.name)
cInfo, err := h.client.ContainerInfo(id)
if err != nil || cInfo.Pid == 0 {
return h.libcontainerHandler
}
h.pidKnown = true
h.libcontainerHandler = containerlibcontainer.NewHandler(h.cgroupManager, h.rootFs, cInfo.Pid, h.includedMetrics)
return h.libcontainerHandler
}
func (h *crioContainerHandler) GetStats() (*info.ContainerStats, error) {
libcontainerHandler := h.getLibcontainerHandler()
stats, err := libcontainerHandler.GetStats()
if err != nil {
return stats, err
}
if h.includedMetrics.Has(container.NetworkUsageMetrics) && len(stats.Network.Interfaces) == 0 {
// No network related information indicates that the pid of the
// container is not longer valid and we need to ask crio to
// provide the pid of another container from that pod
h.pidKnown = false
return stats, nil
}
// Get filesystem stats.
err = h.getFsStats(stats)
if err != nil {
return stats, err
}
return stats, nil
}
func (h *crioContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
// No-op for Docker driver.
return []info.ContainerReference{}, nil
}
func (h *crioContainerHandler) GetCgroupPath(resource string) (string, error) {
var res string
if !cgroups.IsCgroup2UnifiedMode() {
res = resource
}
path, ok := h.cgroupPaths[res]
if !ok {
return "", fmt.Errorf("could not find path for resource %q for container %q", resource, h.reference.Name)
}
return path, nil
}
func (h *crioContainerHandler) GetContainerLabels() map[string]string {
return h.labels
}
func (h *crioContainerHandler) GetContainerIPAddress() string {
return h.ipAddress
}
func (h *crioContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
return h.libcontainerHandler.GetProcesses()
}
func (h *crioContainerHandler) Exists() bool {
return common.CgroupExists(h.cgroupPaths)
}
func (h *crioContainerHandler) Type() container.ContainerType {
return container.ContainerTypeCrio
}

View File

@ -0,0 +1,30 @@
// Copyright 2019 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.
// The install package registers crio.NewPlugin() as the "crio" container provider when imported
package install
import (
"k8s.io/klog/v2"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/crio"
)
func init() {
err := container.RegisterPlugin("crio", crio.NewPlugin())
if err != nil {
klog.Fatalf("Failed to register crio plugin: %v", err)
}
}

View File

@ -0,0 +1,51 @@
// Copyright 2019 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 crio
import (
"k8s.io/klog/v2"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/watcher"
)
// NewPlugin returns an implementation of container.Plugin suitable for passing to container.RegisterPlugin()
func NewPlugin() container.Plugin {
return &plugin{}
}
type plugin struct{}
func (p *plugin) InitializeFSContext(context *fs.Context) error {
crioClient, err := Client()
if err != nil {
return err
}
crioInfo, err := crioClient.Info()
if err != nil {
klog.V(5).Infof("CRI-O not connected: %v", err)
} else {
context.Crio = fs.CrioContext{Root: crioInfo.StorageRoot, ImageStore: crioInfo.StorageImage, Driver: crioInfo.StorageDriver}
}
return nil
}
func (p *plugin) Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) (watcher.ContainerWatcher, error) {
err := Register(factory, fsInfo, includedMetrics)
return nil, err
}

330
vendor/github.com/google/cadvisor/container/factory.go generated vendored Normal file
View File

@ -0,0 +1,330 @@
// 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.
package container
import (
"fmt"
"sort"
"strings"
"sync"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/watcher"
"k8s.io/klog/v2"
)
type ContainerHandlerFactory interface {
// Create a new ContainerHandler using this factory. CanHandleAndAccept() must have returned true.
NewContainerHandler(name string, metadataEnvAllowList []string, inHostNamespace bool) (c ContainerHandler, err error)
// Returns whether this factory can handle and accept the specified container.
CanHandleAndAccept(name string) (handle bool, accept bool, err error)
// Name of the factory.
String() string
// Returns debugging information. Map of lines per category.
DebugInfo() map[string][]string
}
// MetricKind represents the kind of metrics that cAdvisor exposes.
type MetricKind string
const (
CpuUsageMetrics MetricKind = "cpu"
ProcessSchedulerMetrics MetricKind = "sched"
PerCpuUsageMetrics MetricKind = "percpu"
MemoryUsageMetrics MetricKind = "memory"
MemoryNumaMetrics MetricKind = "memory_numa"
CpuLoadMetrics MetricKind = "cpuLoad"
DiskIOMetrics MetricKind = "diskIO"
DiskUsageMetrics MetricKind = "disk"
NetworkUsageMetrics MetricKind = "network"
NetworkTcpUsageMetrics MetricKind = "tcp"
NetworkAdvancedTcpUsageMetrics MetricKind = "advtcp"
NetworkUdpUsageMetrics MetricKind = "udp"
AppMetrics MetricKind = "app"
ProcessMetrics MetricKind = "process"
HugetlbUsageMetrics MetricKind = "hugetlb"
PerfMetrics MetricKind = "perf_event"
ReferencedMemoryMetrics MetricKind = "referenced_memory"
CPUTopologyMetrics MetricKind = "cpu_topology"
ResctrlMetrics MetricKind = "resctrl"
CPUSetMetrics MetricKind = "cpuset"
OOMMetrics MetricKind = "oom_event"
)
// AllMetrics represents all kinds of metrics that cAdvisor supported.
var AllMetrics = MetricSet{
CpuUsageMetrics: struct{}{},
ProcessSchedulerMetrics: struct{}{},
PerCpuUsageMetrics: struct{}{},
MemoryUsageMetrics: struct{}{},
MemoryNumaMetrics: struct{}{},
CpuLoadMetrics: struct{}{},
DiskIOMetrics: struct{}{},
DiskUsageMetrics: struct{}{},
NetworkUsageMetrics: struct{}{},
NetworkTcpUsageMetrics: struct{}{},
NetworkAdvancedTcpUsageMetrics: struct{}{},
NetworkUdpUsageMetrics: struct{}{},
ProcessMetrics: struct{}{},
AppMetrics: struct{}{},
HugetlbUsageMetrics: struct{}{},
PerfMetrics: struct{}{},
ReferencedMemoryMetrics: struct{}{},
CPUTopologyMetrics: struct{}{},
ResctrlMetrics: struct{}{},
CPUSetMetrics: struct{}{},
OOMMetrics: struct{}{},
}
// AllNetworkMetrics represents all network metrics that cAdvisor supports.
var AllNetworkMetrics = MetricSet{
NetworkUsageMetrics: struct{}{},
NetworkTcpUsageMetrics: struct{}{},
NetworkAdvancedTcpUsageMetrics: struct{}{},
NetworkUdpUsageMetrics: struct{}{},
}
func (mk MetricKind) String() string {
return string(mk)
}
type MetricSet map[MetricKind]struct{}
func (ms MetricSet) Has(mk MetricKind) bool {
_, exists := ms[mk]
return exists
}
func (ms MetricSet) HasAny(ms1 MetricSet) bool {
for m := range ms1 {
if _, ok := ms[m]; ok {
return true
}
}
return false
}
func (ms MetricSet) add(mk MetricKind) {
ms[mk] = struct{}{}
}
func (ms MetricSet) String() string {
values := make([]string, 0, len(ms))
for metric := range ms {
values = append(values, string(metric))
}
sort.Strings(values)
return strings.Join(values, ",")
}
// Not thread-safe, exported only for https://pkg.go.dev/flag#Value
func (ms *MetricSet) Set(value string) error {
*ms = MetricSet{}
if value == "" {
return nil
}
for _, metric := range strings.Split(value, ",") {
if AllMetrics.Has(MetricKind(metric)) {
(*ms).add(MetricKind(metric))
} else {
return fmt.Errorf("unsupported metric %q specified", metric)
}
}
return nil
}
func (ms MetricSet) Difference(ms1 MetricSet) MetricSet {
result := MetricSet{}
for kind := range ms {
if !ms1.Has(kind) {
result.add(kind)
}
}
return result
}
func (ms MetricSet) Append(ms1 MetricSet) MetricSet {
result := ms
for kind := range ms1 {
if !ms.Has(kind) {
result.add(kind)
}
}
return result
}
// All registered auth provider plugins.
var pluginsLock sync.Mutex
var plugins = make(map[string]Plugin)
type Plugin interface {
// InitializeFSContext is invoked when populating an fs.Context object for a new manager.
// A returned error here is fatal.
InitializeFSContext(context *fs.Context) error
// Register is invoked when starting a manager. It can optionally return a container watcher.
// A returned error is logged, but is not fatal.
Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics MetricSet) (watcher.ContainerWatcher, error)
}
func RegisterPlugin(name string, plugin Plugin) error {
pluginsLock.Lock()
defer pluginsLock.Unlock()
if _, found := plugins[name]; found {
return fmt.Errorf("Plugin %q was registered twice", name)
}
klog.V(4).Infof("Registered Plugin %q", name)
plugins[name] = plugin
return nil
}
func InitializeFSContext(context *fs.Context) error {
pluginsLock.Lock()
defer pluginsLock.Unlock()
for name, plugin := range plugins {
err := plugin.InitializeFSContext(context)
if err != nil {
klog.V(5).Infof("Initialization of the %s context failed: %v", name, err)
return err
}
}
return nil
}
func InitializePlugins(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics MetricSet) []watcher.ContainerWatcher {
pluginsLock.Lock()
defer pluginsLock.Unlock()
containerWatchers := []watcher.ContainerWatcher{}
for name, plugin := range plugins {
watcher, err := plugin.Register(factory, fsInfo, includedMetrics)
if err != nil {
klog.Infof("Registration of the %s container factory failed: %v", name, err)
} else {
klog.Infof("Registration of the %s container factory successfully", name)
}
if watcher != nil {
containerWatchers = append(containerWatchers, watcher)
}
}
return containerWatchers
}
// TODO(vmarmol): Consider not making this global.
// Global list of factories.
var (
factories = map[watcher.ContainerWatchSource][]ContainerHandlerFactory{}
factoriesLock sync.RWMutex
)
// Register a ContainerHandlerFactory. These should be registered from least general to most general
// as they will be asked in order whether they can handle a particular container.
func RegisterContainerHandlerFactory(factory ContainerHandlerFactory, watchTypes []watcher.ContainerWatchSource) {
factoriesLock.Lock()
defer factoriesLock.Unlock()
for _, watchType := range watchTypes {
factories[watchType] = append(factories[watchType], factory)
}
}
// Returns whether there are any container handler factories registered.
func HasFactories() bool {
factoriesLock.Lock()
defer factoriesLock.Unlock()
return len(factories) != 0
}
// Create a new ContainerHandler for the specified container.
func NewContainerHandler(name string, watchType watcher.ContainerWatchSource, metadataEnvAllowList []string, inHostNamespace bool) (ContainerHandler, bool, error) {
factoriesLock.RLock()
defer factoriesLock.RUnlock()
// Create the ContainerHandler with the first factory that supports it.
// Note that since RawContainerHandler can support a wide range of paths,
// it's evaluated last just to make sure if any other ContainerHandler
// can support it.
for _, factory := range GetReorderedFactoryList(watchType) {
canHandle, canAccept, err := factory.CanHandleAndAccept(name)
if err != nil {
klog.V(4).Infof("Error trying to work out if we can handle %s: %v", name, err)
}
if canHandle {
if !canAccept {
klog.V(3).Infof("Factory %q can handle container %q, but ignoring.", factory, name)
return nil, false, nil
}
klog.V(3).Infof("Using factory %q for container %q", factory, name)
handle, err := factory.NewContainerHandler(name, metadataEnvAllowList, inHostNamespace)
return handle, canAccept, err
}
klog.V(4).Infof("Factory %q was unable to handle container %q", factory, name)
}
return nil, false, fmt.Errorf("no known factory can handle creation of container")
}
// Clear the known factories.
func ClearContainerHandlerFactories() {
factoriesLock.Lock()
defer factoriesLock.Unlock()
factories = map[watcher.ContainerWatchSource][]ContainerHandlerFactory{}
}
func DebugInfo() map[string][]string {
factoriesLock.RLock()
defer factoriesLock.RUnlock()
// Get debug information for all factories.
out := make(map[string][]string)
for _, factoriesSlice := range factories {
for _, factory := range factoriesSlice {
for k, v := range factory.DebugInfo() {
out[k] = v
}
}
}
return out
}
// GetReorderedFactoryList returns the list of ContainerHandlerFactory where the
// RawContainerHandler is always the last element.
func GetReorderedFactoryList(watchType watcher.ContainerWatchSource) []ContainerHandlerFactory {
ContainerHandlerFactoryList := make([]ContainerHandlerFactory, 0, len(factories))
var rawFactory ContainerHandlerFactory
for _, v := range factories[watchType] {
if v != nil {
if v.String() == "raw" {
rawFactory = v
continue
}
ContainerHandlerFactoryList = append(ContainerHandlerFactoryList, v)
}
}
if rawFactory != nil {
ContainerHandlerFactoryList = append(ContainerHandlerFactoryList, rawFactory)
}
return ContainerHandlerFactoryList
}

View File

@ -0,0 +1,917 @@
// Copyright 2018 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 libcontainer
import (
"bufio"
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"os"
"path"
"regexp"
"strconv"
"strings"
"time"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
"k8s.io/klog/v2"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
info "github.com/google/cadvisor/info/v1"
)
var (
referencedResetInterval = flag.Uint64("referenced_reset_interval", 0,
"Reset interval for referenced bytes (container_referenced_bytes metric), number of measurement cycles after which referenced bytes are cleared, if set to 0 referenced bytes are never cleared (default: 0)")
smapsFilePathPattern = "/proc/%d/smaps"
clearRefsFilePathPattern = "/proc/%d/clear_refs"
referencedRegexp = regexp.MustCompile(`Referenced:\s*([0-9]+)\s*kB`)
)
type Handler struct {
cgroupManager cgroups.Manager
rootFs string
pid int
includedMetrics container.MetricSet
// pidMetricsCache holds CPU scheduler stats for existing processes (map key is PID) between calls to schedulerStatsFromProcs.
pidMetricsCache map[int]*info.CpuSchedstat
// pidMetricsSaved holds accumulated CPU scheduler stats for processes that no longer exist.
pidMetricsSaved info.CpuSchedstat
cycles uint64
}
func NewHandler(cgroupManager cgroups.Manager, rootFs string, pid int, includedMetrics container.MetricSet) *Handler {
return &Handler{
cgroupManager: cgroupManager,
rootFs: rootFs,
pid: pid,
includedMetrics: includedMetrics,
pidMetricsCache: make(map[int]*info.CpuSchedstat),
}
}
// Get cgroup and networking stats of the specified container
func (h *Handler) GetStats() (*info.ContainerStats, error) {
ignoreStatsError := false
if cgroups.IsCgroup2UnifiedMode() {
// On cgroup v2 the root cgroup stats have been introduced in recent kernel versions,
// so not all kernel versions have all the data. This means that stat fetching can fail
// due to lacking cgroup stat files, but that some data is provided.
if h.cgroupManager.Path("") == fs2.UnifiedMountpoint {
ignoreStatsError = true
}
}
cgroupStats, err := h.cgroupManager.GetStats()
if err != nil {
if !ignoreStatsError {
return nil, err
}
klog.V(4).Infof("Ignoring errors when gathering stats for root cgroup since some controllers don't have stats on the root cgroup: %v", err)
}
stats := newContainerStats(cgroupStats, h.includedMetrics)
if h.includedMetrics.Has(container.ProcessSchedulerMetrics) {
stats.Cpu.Schedstat, err = h.schedulerStatsFromProcs()
if err != nil {
klog.V(4).Infof("Unable to get Process Scheduler Stats: %v", err)
}
}
if h.includedMetrics.Has(container.ReferencedMemoryMetrics) {
h.cycles++
pids, err := h.cgroupManager.GetPids()
if err != nil {
klog.V(4).Infof("Could not get PIDs for container %d: %v", h.pid, err)
} else {
stats.ReferencedMemory, err = referencedBytesStat(pids, h.cycles, *referencedResetInterval)
if err != nil {
klog.V(4).Infof("Unable to get referenced bytes: %v", err)
}
}
}
// If we know the pid then get network stats from /proc/<pid>/net/dev
if h.pid > 0 {
if h.includedMetrics.Has(container.NetworkUsageMetrics) {
netStats, err := networkStatsFromProc(h.rootFs, h.pid)
if err != nil {
klog.V(4).Infof("Unable to get network stats from pid %d: %v", h.pid, err)
} else {
stats.Network.Interfaces = append(stats.Network.Interfaces, netStats...)
}
}
if h.includedMetrics.Has(container.NetworkTcpUsageMetrics) {
t, err := tcpStatsFromProc(h.rootFs, h.pid, "net/tcp")
if err != nil {
klog.V(4).Infof("Unable to get tcp stats from pid %d: %v", h.pid, err)
} else {
stats.Network.Tcp = t
}
t6, err := tcpStatsFromProc(h.rootFs, h.pid, "net/tcp6")
if err != nil {
klog.V(4).Infof("Unable to get tcp6 stats from pid %d: %v", h.pid, err)
} else {
stats.Network.Tcp6 = t6
}
}
if h.includedMetrics.Has(container.NetworkAdvancedTcpUsageMetrics) {
ta, err := advancedTCPStatsFromProc(h.rootFs, h.pid, "net/netstat", "net/snmp")
if err != nil {
klog.V(4).Infof("Unable to get advanced tcp stats from pid %d: %v", h.pid, err)
} else {
stats.Network.TcpAdvanced = ta
}
}
if h.includedMetrics.Has(container.NetworkUdpUsageMetrics) {
u, err := udpStatsFromProc(h.rootFs, h.pid, "net/udp")
if err != nil {
klog.V(4).Infof("Unable to get udp stats from pid %d: %v", h.pid, err)
} else {
stats.Network.Udp = u
}
u6, err := udpStatsFromProc(h.rootFs, h.pid, "net/udp6")
if err != nil {
klog.V(4).Infof("Unable to get udp6 stats from pid %d: %v", h.pid, err)
} else {
stats.Network.Udp6 = u6
}
}
}
// some process metrics are per container ( number of processes, number of
// file descriptors etc.) and not required a proper container's
// root PID (systemd services don't have the root PID atm)
if h.includedMetrics.Has(container.ProcessMetrics) {
path, ok := common.GetControllerPath(h.cgroupManager.GetPaths(), "cpu", cgroups.IsCgroup2UnifiedMode())
if !ok {
klog.V(4).Infof("Could not find cgroups CPU for container %d", h.pid)
} else {
stats.Processes, err = processStatsFromProcs(h.rootFs, path, h.pid)
if err != nil {
klog.V(4).Infof("Unable to get Process Stats: %v", err)
}
}
// if include processes metrics, just set threads metrics if exist, and has no relationship with cpu path
setThreadsStats(cgroupStats, stats)
}
// For backwards compatibility.
if len(stats.Network.Interfaces) > 0 {
stats.Network.InterfaceStats = stats.Network.Interfaces[0]
}
return stats, nil
}
func parseUlimit(value string) (int64, error) {
num, err := strconv.ParseInt(value, 10, 64)
if err != nil {
if strings.EqualFold(value, "unlimited") {
// -1 implies unlimited except for priority and nice; man limits.conf
num = -1
} else {
// Value is not a number or "unlimited"; return an error
return 0, fmt.Errorf("unable to parse limit: %s", value)
}
}
return num, nil
}
func processLimitsFile(fileData string) []info.UlimitSpec {
const maxOpenFilesLinePrefix = "Max open files"
limits := strings.Split(fileData, "\n")
ulimits := make([]info.UlimitSpec, 0, len(limits))
for _, lim := range limits {
// Skip any headers/footers
if strings.HasPrefix(lim, "Max open files") {
// Remove line prefix
ulimit, err := processMaxOpenFileLimitLine(
"max_open_files",
lim[len(maxOpenFilesLinePrefix):],
)
if err == nil {
ulimits = append(ulimits, ulimit)
}
}
}
return ulimits
}
// Any caller of processMaxOpenFileLimitLine must ensure that the name prefix is already removed from the limit line.
// with the "Max open files" prefix.
func processMaxOpenFileLimitLine(name, line string) (info.UlimitSpec, error) {
// Remove any leading whitespace
line = strings.TrimSpace(line)
// Split on whitespace
fields := strings.Fields(line)
if len(fields) != 3 {
return info.UlimitSpec{}, fmt.Errorf("unable to parse max open files line: %s", line)
}
// The first field is the soft limit, the second is the hard limit
soft, err := parseUlimit(fields[0])
if err != nil {
return info.UlimitSpec{}, err
}
hard, err := parseUlimit(fields[1])
if err != nil {
return info.UlimitSpec{}, err
}
return info.UlimitSpec{
Name: name,
SoftLimit: soft,
HardLimit: hard,
}, nil
}
func processRootProcUlimits(rootFs string, rootPid int) []info.UlimitSpec {
filePath := path.Join(rootFs, "/proc", strconv.Itoa(rootPid), "limits")
out, err := os.ReadFile(filePath)
if err != nil {
klog.V(4).Infof("error while listing directory %q to read ulimits: %v", filePath, err)
return []info.UlimitSpec{}
}
return processLimitsFile(string(out))
}
func processStatsFromProcs(rootFs string, cgroupPath string, rootPid int) (info.ProcessStats, error) {
var fdCount, socketCount uint64
filePath := path.Join(cgroupPath, "cgroup.procs")
out, err := os.ReadFile(filePath)
if err != nil {
return info.ProcessStats{}, fmt.Errorf("couldn't open cpu cgroup procs file %v : %v", filePath, err)
}
pids := strings.Split(string(out), "\n")
// EOL is also treated as a new line while reading "cgroup.procs" file with os.ReadFile.
// The last value is an empty string "". Ex: pids = ["22", "1223", ""]
// Trim the last value
if len(pids) != 0 && pids[len(pids)-1] == "" {
pids = pids[:len(pids)-1]
}
for _, pid := range pids {
dirPath := path.Join(rootFs, "/proc", pid, "fd")
fds, err := os.ReadDir(dirPath)
if err != nil {
klog.V(4).Infof("error while listing directory %q to measure fd count: %v", dirPath, err)
continue
}
fdCount += uint64(len(fds))
for _, fd := range fds {
fdPath := path.Join(dirPath, fd.Name())
linkName, err := os.Readlink(fdPath)
if err != nil {
klog.V(4).Infof("error while reading %q link: %v", fdPath, err)
continue
}
if strings.HasPrefix(linkName, "socket") {
socketCount++
}
}
}
processStats := info.ProcessStats{
ProcessCount: uint64(len(pids)),
FdCount: fdCount,
SocketCount: socketCount,
}
if rootPid > 0 {
processStats.Ulimits = processRootProcUlimits(rootFs, rootPid)
}
return processStats, nil
}
func (h *Handler) schedulerStatsFromProcs() (info.CpuSchedstat, error) {
pids, err := h.cgroupManager.GetAllPids()
if err != nil {
return info.CpuSchedstat{}, fmt.Errorf("Could not get PIDs for container %d: %w", h.pid, err)
}
alivePids := make(map[int]struct{}, len(pids))
for _, pid := range pids {
f, err := os.Open(path.Join(h.rootFs, "proc", strconv.Itoa(pid), "schedstat"))
if err != nil {
return info.CpuSchedstat{}, fmt.Errorf("couldn't open scheduler statistics for process %d: %v", pid, err)
}
defer f.Close()
contents, err := io.ReadAll(f)
if err != nil {
return info.CpuSchedstat{}, fmt.Errorf("couldn't read scheduler statistics for process %d: %v", pid, err)
}
alivePids[pid] = struct{}{}
rawMetrics := bytes.Split(bytes.TrimRight(contents, "\n"), []byte(" "))
if len(rawMetrics) != 3 {
return info.CpuSchedstat{}, fmt.Errorf("unexpected number of metrics in schedstat file for process %d", pid)
}
cacheEntry, ok := h.pidMetricsCache[pid]
if !ok {
cacheEntry = &info.CpuSchedstat{}
h.pidMetricsCache[pid] = cacheEntry
}
for i, rawMetric := range rawMetrics {
metric, err := strconv.ParseUint(string(rawMetric), 10, 64)
if err != nil {
return info.CpuSchedstat{}, fmt.Errorf("parsing error while reading scheduler statistics for process: %d: %v", pid, err)
}
switch i {
case 0:
cacheEntry.RunTime = metric
case 1:
cacheEntry.RunqueueTime = metric
case 2:
cacheEntry.RunPeriods = metric
}
}
}
schedstats := h.pidMetricsSaved // copy
for p, v := range h.pidMetricsCache {
schedstats.RunPeriods += v.RunPeriods
schedstats.RunqueueTime += v.RunqueueTime
schedstats.RunTime += v.RunTime
if _, alive := alivePids[p]; !alive {
// PID p is gone: accumulate its stats ...
h.pidMetricsSaved.RunPeriods += v.RunPeriods
h.pidMetricsSaved.RunqueueTime += v.RunqueueTime
h.pidMetricsSaved.RunTime += v.RunTime
// ... and remove its cache entry, to prevent
// pidMetricsCache from growing.
delete(h.pidMetricsCache, p)
}
}
return schedstats, nil
}
// referencedBytesStat gets and clears referenced bytes
// see: https://github.com/brendangregg/wss#wsspl-referenced-page-flag
func referencedBytesStat(pids []int, cycles uint64, resetInterval uint64) (uint64, error) {
referencedKBytes, err := getReferencedKBytes(pids)
if err != nil {
return uint64(0), err
}
err = clearReferencedBytes(pids, cycles, resetInterval)
if err != nil {
return uint64(0), err
}
return referencedKBytes * 1024, nil
}
func getReferencedKBytes(pids []int) (uint64, error) {
referencedKBytes := uint64(0)
readSmapsContent := false
foundMatch := false
for _, pid := range pids {
smapsFilePath := fmt.Sprintf(smapsFilePathPattern, pid)
smapsContent, err := os.ReadFile(smapsFilePath)
if err != nil {
klog.V(5).Infof("Cannot read %s file, err: %s", smapsFilePath, err)
if os.IsNotExist(err) {
continue // smaps file does not exists for all PIDs
}
return 0, err
}
readSmapsContent = true
allMatches := referencedRegexp.FindAllSubmatch(smapsContent, -1)
if len(allMatches) == 0 {
klog.V(5).Infof("Not found any information about referenced bytes in %s file", smapsFilePath)
continue // referenced bytes may not exist in smaps file
}
for _, matches := range allMatches {
if len(matches) != 2 {
return 0, fmt.Errorf("failed to match regexp in output: %s", string(smapsContent))
}
foundMatch = true
referenced, err := strconv.ParseUint(string(matches[1]), 10, 64)
if err != nil {
return 0, err
}
referencedKBytes += referenced
}
}
if len(pids) != 0 {
if !readSmapsContent {
klog.Warningf("Cannot read smaps files for any PID from %s", "CONTAINER")
} else if !foundMatch {
klog.Warningf("Not found any information about referenced bytes in smaps files for any PID from %s", "CONTAINER")
}
}
return referencedKBytes, nil
}
func clearReferencedBytes(pids []int, cycles uint64, resetInterval uint64) error {
if resetInterval == 0 {
return nil
}
if cycles%resetInterval == 0 {
for _, pid := range pids {
clearRefsFilePath := fmt.Sprintf(clearRefsFilePathPattern, pid)
clerRefsFile, err := os.OpenFile(clearRefsFilePath, os.O_WRONLY, 0o644)
if err != nil {
// clear_refs file may not exist for all PIDs
continue
}
_, err = clerRefsFile.WriteString("1\n")
if err != nil {
return err
}
err = clerRefsFile.Close()
if err != nil {
return err
}
}
}
return nil
}
func networkStatsFromProc(rootFs string, pid int) ([]info.InterfaceStats, error) {
netStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), "/net/dev")
ifaceStats, err := scanInterfaceStats(netStatsFile)
if err != nil {
return []info.InterfaceStats{}, fmt.Errorf("couldn't read network stats: %v", err)
}
return ifaceStats, nil
}
var ignoredDevicePrefixes = []string{"lo", "veth", "docker", "nerdctl"}
func isIgnoredDevice(ifName string) bool {
for _, prefix := range ignoredDevicePrefixes {
if strings.HasPrefix(strings.ToLower(ifName), prefix) {
return true
}
}
return false
}
func scanInterfaceStats(netStatsFile string) ([]info.InterfaceStats, error) {
file, err := os.Open(netStatsFile)
if err != nil {
return nil, fmt.Errorf("failure opening %s: %v", netStatsFile, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
// Discard header lines
for i := 0; i < 2; i++ {
if b := scanner.Scan(); !b {
return nil, scanner.Err()
}
}
stats := []info.InterfaceStats{}
for scanner.Scan() {
line := scanner.Text()
line = strings.Replace(line, ":", "", -1)
fields := strings.Fields(line)
// If the format of the line is invalid then don't trust any of the stats
// in this file.
if len(fields) != 17 {
return nil, fmt.Errorf("invalid interface stats line: %v", line)
}
devName := fields[0]
if isIgnoredDevice(devName) {
continue
}
i := info.InterfaceStats{
Name: devName,
}
statFields := append(fields[1:5], fields[9:13]...)
statPointers := []*uint64{
&i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped,
&i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped,
}
err := setInterfaceStatValues(statFields, statPointers)
if err != nil {
return nil, fmt.Errorf("cannot parse interface stats (%v): %v", err, line)
}
stats = append(stats, i)
}
return stats, nil
}
func setInterfaceStatValues(fields []string, pointers []*uint64) error {
for i, v := range fields {
val, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return err
}
*pointers[i] = val
}
return nil
}
func tcpStatsFromProc(rootFs string, pid int, file string) (info.TcpStat, error) {
tcpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file)
tcpStats, err := scanTCPStats(tcpStatsFile)
if err != nil {
return tcpStats, fmt.Errorf("couldn't read tcp stats: %v", err)
}
return tcpStats, nil
}
func advancedTCPStatsFromProc(rootFs string, pid int, file1, file2 string) (info.TcpAdvancedStat, error) {
var advancedStats info.TcpAdvancedStat
var err error
netstatFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file1)
err = scanAdvancedTCPStats(&advancedStats, netstatFile)
if err != nil {
return advancedStats, err
}
snmpFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file2)
err = scanAdvancedTCPStats(&advancedStats, snmpFile)
if err != nil {
return advancedStats, err
}
return advancedStats, nil
}
func scanAdvancedTCPStats(advancedStats *info.TcpAdvancedStat, advancedTCPStatsFile string) error {
data, err := os.ReadFile(advancedTCPStatsFile)
if err != nil {
return fmt.Errorf("failure opening %s: %v", advancedTCPStatsFile, err)
}
reader := strings.NewReader(string(data))
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)
advancedTCPStats := make(map[string]interface{})
for scanner.Scan() {
nameParts := strings.Split(scanner.Text(), " ")
scanner.Scan()
valueParts := strings.Split(scanner.Text(), " ")
// Remove trailing :. and ignore non-tcp
protocol := nameParts[0][:len(nameParts[0])-1]
if protocol != "TcpExt" && protocol != "Tcp" {
continue
}
if len(nameParts) != len(valueParts) {
return fmt.Errorf("mismatch field count mismatch in %s: %s",
advancedTCPStatsFile, protocol)
}
for i := 1; i < len(nameParts); i++ {
if strings.Contains(valueParts[i], "-") {
vInt64, err := strconv.ParseInt(valueParts[i], 10, 64)
if err != nil {
return fmt.Errorf("decode value: %s to int64 error: %s", valueParts[i], err)
}
advancedTCPStats[nameParts[i]] = vInt64
} else {
vUint64, err := strconv.ParseUint(valueParts[i], 10, 64)
if err != nil {
return fmt.Errorf("decode value: %s to uint64 error: %s", valueParts[i], err)
}
advancedTCPStats[nameParts[i]] = vUint64
}
}
}
b, err := json.Marshal(advancedTCPStats)
if err != nil {
return err
}
err = json.Unmarshal(b, advancedStats)
if err != nil {
return err
}
return scanner.Err()
}
func scanTCPStats(tcpStatsFile string) (info.TcpStat, error) {
var stats info.TcpStat
data, err := os.ReadFile(tcpStatsFile)
if err != nil {
return stats, fmt.Errorf("failure opening %s: %v", tcpStatsFile, err)
}
tcpStateMap := map[string]uint64{
"01": 0, // ESTABLISHED
"02": 0, // SYN_SENT
"03": 0, // SYN_RECV
"04": 0, // FIN_WAIT1
"05": 0, // FIN_WAIT2
"06": 0, // TIME_WAIT
"07": 0, // CLOSE
"08": 0, // CLOSE_WAIT
"09": 0, // LAST_ACK
"0A": 0, // LISTEN
"0B": 0, // CLOSING
}
reader := strings.NewReader(string(data))
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)
// Discard header line
if b := scanner.Scan(); !b {
return stats, scanner.Err()
}
for scanner.Scan() {
line := scanner.Text()
state := strings.Fields(line)
// TCP state is the 4th field.
// Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
tcpState := state[3]
_, ok := tcpStateMap[tcpState]
if !ok {
return stats, fmt.Errorf("invalid TCP stats line: %v", line)
}
tcpStateMap[tcpState]++
}
stats = info.TcpStat{
Established: tcpStateMap["01"],
SynSent: tcpStateMap["02"],
SynRecv: tcpStateMap["03"],
FinWait1: tcpStateMap["04"],
FinWait2: tcpStateMap["05"],
TimeWait: tcpStateMap["06"],
Close: tcpStateMap["07"],
CloseWait: tcpStateMap["08"],
LastAck: tcpStateMap["09"],
Listen: tcpStateMap["0A"],
Closing: tcpStateMap["0B"],
}
return stats, nil
}
func udpStatsFromProc(rootFs string, pid int, file string) (info.UdpStat, error) {
var err error
var udpStats info.UdpStat
udpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file)
r, err := os.Open(udpStatsFile)
if err != nil {
return udpStats, fmt.Errorf("failure opening %s: %v", udpStatsFile, err)
}
udpStats, err = scanUDPStats(r)
if err != nil {
return udpStats, fmt.Errorf("couldn't read udp stats: %v", err)
}
return udpStats, nil
}
func scanUDPStats(r io.Reader) (info.UdpStat, error) {
var stats info.UdpStat
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanLines)
// Discard header line
if b := scanner.Scan(); !b {
return stats, scanner.Err()
}
listening := uint64(0)
dropped := uint64(0)
rxQueued := uint64(0)
txQueued := uint64(0)
for scanner.Scan() {
line := scanner.Text()
// Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
listening++
fs := strings.Fields(line)
if len(fs) != 13 {
continue
}
rx, tx := uint64(0), uint64(0)
fmt.Sscanf(fs[4], "%X:%X", &rx, &tx)
rxQueued += rx
txQueued += tx
d, err := strconv.Atoi(string(fs[12]))
if err != nil {
continue
}
dropped += uint64(d)
}
stats = info.UdpStat{
Listen: listening,
Dropped: dropped,
RxQueued: rxQueued,
TxQueued: txQueued,
}
return stats, nil
}
func (h *Handler) GetProcesses() ([]int, error) {
pids, err := h.cgroupManager.GetPids()
if err != nil {
return nil, err
}
return pids, nil
}
// Convert libcontainer stats to info.ContainerStats.
func setCPUStats(s *cgroups.Stats, ret *info.ContainerStats, withPerCPU bool) {
ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode
ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode
ret.Cpu.Usage.Total = s.CpuStats.CpuUsage.TotalUsage
ret.Cpu.CFS.Periods = s.CpuStats.ThrottlingData.Periods
ret.Cpu.CFS.ThrottledPeriods = s.CpuStats.ThrottlingData.ThrottledPeriods
ret.Cpu.CFS.ThrottledTime = s.CpuStats.ThrottlingData.ThrottledTime
if !withPerCPU {
return
}
if len(s.CpuStats.CpuUsage.PercpuUsage) == 0 {
// libcontainer's 'GetStats' can leave 'PercpuUsage' nil if it skipped the
// cpuacct subsystem.
return
}
ret.Cpu.Usage.PerCpu = s.CpuStats.CpuUsage.PercpuUsage
}
func setDiskIoStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.DiskIo.IoServiceBytes = diskStatsCopy(s.BlkioStats.IoServiceBytesRecursive)
ret.DiskIo.IoServiced = diskStatsCopy(s.BlkioStats.IoServicedRecursive)
ret.DiskIo.IoQueued = diskStatsCopy(s.BlkioStats.IoQueuedRecursive)
ret.DiskIo.Sectors = diskStatsCopy(s.BlkioStats.SectorsRecursive)
ret.DiskIo.IoServiceTime = diskStatsCopy(s.BlkioStats.IoServiceTimeRecursive)
ret.DiskIo.IoWaitTime = diskStatsCopy(s.BlkioStats.IoWaitTimeRecursive)
ret.DiskIo.IoMerged = diskStatsCopy(s.BlkioStats.IoMergedRecursive)
ret.DiskIo.IoTime = diskStatsCopy(s.BlkioStats.IoTimeRecursive)
}
func setMemoryStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.Usage = s.MemoryStats.Usage.Usage
ret.Memory.MaxUsage = s.MemoryStats.Usage.MaxUsage
ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt
ret.Memory.KernelUsage = s.MemoryStats.KernelUsage.Usage
if cgroups.IsCgroup2UnifiedMode() {
ret.Memory.Cache = s.MemoryStats.Stats["file"]
ret.Memory.RSS = s.MemoryStats.Stats["anon"]
ret.Memory.Swap = s.MemoryStats.SwapUsage.Usage - s.MemoryStats.Usage.Usage
ret.Memory.MappedFile = s.MemoryStats.Stats["file_mapped"]
} else if s.MemoryStats.UseHierarchy {
ret.Memory.Cache = s.MemoryStats.Stats["total_cache"]
ret.Memory.RSS = s.MemoryStats.Stats["total_rss"]
ret.Memory.Swap = s.MemoryStats.Stats["total_swap"]
ret.Memory.MappedFile = s.MemoryStats.Stats["total_mapped_file"]
} else {
ret.Memory.Cache = s.MemoryStats.Stats["cache"]
ret.Memory.RSS = s.MemoryStats.Stats["rss"]
ret.Memory.Swap = s.MemoryStats.Stats["swap"]
ret.Memory.MappedFile = s.MemoryStats.Stats["mapped_file"]
}
if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
ret.Memory.ContainerData.Pgfault = v
ret.Memory.HierarchicalData.Pgfault = v
}
if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
ret.Memory.ContainerData.Pgmajfault = v
ret.Memory.HierarchicalData.Pgmajfault = v
}
inactiveFileKeyName := "total_inactive_file"
if cgroups.IsCgroup2UnifiedMode() {
inactiveFileKeyName = "inactive_file"
}
activeFileKeyName := "total_active_file"
if cgroups.IsCgroup2UnifiedMode() {
activeFileKeyName = "active_file"
}
if v, ok := s.MemoryStats.Stats[activeFileKeyName]; ok {
ret.Memory.TotalActiveFile = v
}
workingSet := ret.Memory.Usage
if v, ok := s.MemoryStats.Stats[inactiveFileKeyName]; ok {
ret.Memory.TotalInactiveFile = v
if workingSet < v {
workingSet = 0
} else {
workingSet -= v
}
}
ret.Memory.WorkingSet = workingSet
}
func setCPUSetStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.CpuSet.MemoryMigrate = s.CPUSetStats.MemoryMigrate
}
func getNumaStats(memoryStats map[uint8]uint64) map[uint8]uint64 {
stats := make(map[uint8]uint64, len(memoryStats))
for node, usage := range memoryStats {
stats[node] = usage
}
return stats
}
func setMemoryNumaStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.ContainerData.NumaStats.File = getNumaStats(s.MemoryStats.PageUsageByNUMA.File.Nodes)
ret.Memory.ContainerData.NumaStats.Anon = getNumaStats(s.MemoryStats.PageUsageByNUMA.Anon.Nodes)
ret.Memory.ContainerData.NumaStats.Unevictable = getNumaStats(s.MemoryStats.PageUsageByNUMA.Unevictable.Nodes)
ret.Memory.HierarchicalData.NumaStats.File = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.File.Nodes)
ret.Memory.HierarchicalData.NumaStats.Anon = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.Anon.Nodes)
ret.Memory.HierarchicalData.NumaStats.Unevictable = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.Unevictable.Nodes)
}
func setHugepageStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Hugetlb = make(map[string]info.HugetlbStats)
for k, v := range s.HugetlbStats {
ret.Hugetlb[k] = info.HugetlbStats{
Usage: v.Usage,
MaxUsage: v.MaxUsage,
Failcnt: v.Failcnt,
}
}
}
// read from pids path not cpu
func setThreadsStats(s *cgroups.Stats, ret *info.ContainerStats) {
if s != nil {
ret.Processes.ThreadsCurrent = s.PidsStats.Current
ret.Processes.ThreadsMax = s.PidsStats.Limit
}
}
func newContainerStats(cgroupStats *cgroups.Stats, includedMetrics container.MetricSet) *info.ContainerStats {
ret := &info.ContainerStats{
Timestamp: time.Now(),
}
if s := cgroupStats; s != nil {
setCPUStats(s, ret, includedMetrics.Has(container.PerCpuUsageMetrics))
if includedMetrics.Has(container.DiskIOMetrics) {
setDiskIoStats(s, ret)
}
setMemoryStats(s, ret)
if includedMetrics.Has(container.MemoryNumaMetrics) {
setMemoryNumaStats(s, ret)
}
if includedMetrics.Has(container.HugetlbUsageMetrics) {
setHugepageStats(s, ret)
}
if includedMetrics.Has(container.CPUSetMetrics) {
setCPUSetStats(s, ret)
}
}
return ret
}

View File

@ -0,0 +1,170 @@
// 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.
package libcontainer
import (
"fmt"
info "github.com/google/cadvisor/info/v1"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/google/cadvisor/container"
fs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
fs2 "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
configs "github.com/opencontainers/runc/libcontainer/configs"
"k8s.io/klog/v2"
)
// GetCgroupSubsystems returns information about the cgroup subsystems that are
// of interest as a map of cgroup controllers to their mount points.
// For example, "cpu" -> "/sys/fs/cgroup/cpu".
//
// The incudeMetrics arguments specifies which metrics are requested,
// and is used to filter out some cgroups and their mounts. If nil,
// all supported cgroup subsystems are included.
//
// For cgroup v2, includedMetrics argument is unused, the only map key is ""
// (empty string), and the value is the unified cgroup mount point.
func GetCgroupSubsystems(includedMetrics container.MetricSet) (map[string]string, error) {
if cgroups.IsCgroup2UnifiedMode() {
return map[string]string{"": fs2.UnifiedMountpoint}, nil
}
// Get all cgroup mounts.
allCgroups, err := cgroups.GetCgroupMounts(true)
if err != nil {
return nil, err
}
return getCgroupSubsystemsHelper(allCgroups, includedMetrics)
}
func getCgroupSubsystemsHelper(allCgroups []cgroups.Mount, includedMetrics container.MetricSet) (map[string]string, error) {
if len(allCgroups) == 0 {
return nil, fmt.Errorf("failed to find cgroup mounts")
}
// Trim the mounts to only the subsystems we care about.
mountPoints := make(map[string]string, len(allCgroups))
for _, mount := range allCgroups {
for _, subsystem := range mount.Subsystems {
if !needSubsys(subsystem, includedMetrics) {
continue
}
if _, ok := mountPoints[subsystem]; ok {
// duplicate mount for this subsystem; use the first one we saw
klog.V(5).Infof("skipping %s, already using mount at %s", mount.Mountpoint, mountPoints[subsystem])
continue
}
mountPoints[subsystem] = mount.Mountpoint
}
}
return mountPoints, nil
}
// A map of cgroup subsystems we support listing (should be the minimal set
// we need stats from) to a respective MetricKind.
var supportedSubsystems = map[string]container.MetricKind{
"cpu": container.CpuUsageMetrics,
"cpuacct": container.CpuUsageMetrics,
"memory": container.MemoryUsageMetrics,
"hugetlb": container.HugetlbUsageMetrics,
"pids": container.ProcessMetrics,
"cpuset": container.CPUSetMetrics,
"blkio": container.DiskIOMetrics,
"io": container.DiskIOMetrics,
"devices": "",
"perf_event": container.PerfMetrics,
}
// Check if this cgroup subsystem/controller is of use.
func needSubsys(name string, metrics container.MetricSet) bool {
// Check if supported.
metric, supported := supportedSubsystems[name]
if !supported {
return false
}
// Check if needed.
if metrics == nil || metric == "" {
return true
}
return metrics.Has(metric)
}
func diskStatsCopy0(major, minor uint64) *info.PerDiskStats {
disk := info.PerDiskStats{
Major: major,
Minor: minor,
}
disk.Stats = make(map[string]uint64)
return &disk
}
type diskKey struct {
Major uint64
Minor uint64
}
func diskStatsCopy1(diskStat map[diskKey]*info.PerDiskStats) []info.PerDiskStats {
i := 0
stat := make([]info.PerDiskStats, len(diskStat))
for _, disk := range diskStat {
stat[i] = *disk
i++
}
return stat
}
func diskStatsCopy(blkioStats []cgroups.BlkioStatEntry) (stat []info.PerDiskStats) {
if len(blkioStats) == 0 {
return
}
diskStat := make(map[diskKey]*info.PerDiskStats)
for i := range blkioStats {
major := blkioStats[i].Major
minor := blkioStats[i].Minor
key := diskKey{
Major: major,
Minor: minor,
}
diskp, ok := diskStat[key]
if !ok {
diskp = diskStatsCopy0(major, minor)
diskStat[key] = diskp
}
op := blkioStats[i].Op
if op == "" {
op = "Count"
}
diskp.Stats[op] = blkioStats[i].Value
}
return diskStatsCopy1(diskStat)
}
func NewCgroupManager(name string, paths map[string]string) (cgroups.Manager, error) {
config := &configs.Cgroup{
Name: name,
Resources: &configs.Resources{},
}
if cgroups.IsCgroup2UnifiedMode() {
path := paths[""]
return fs2.NewManager(config, path)
}
return fs.NewManager(config, paths)
}

View File

@ -0,0 +1,114 @@
// 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.
package raw
import (
"flag"
"fmt"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
watch "github.com/google/cadvisor/watcher"
"k8s.io/klog/v2"
)
var (
DockerOnly = flag.Bool("docker_only", false, "Only report docker containers in addition to root stats")
disableRootCgroupStats = flag.Bool("disable_root_cgroup_stats", false, "Disable collecting root Cgroup stats")
)
type rawFactory struct {
// Factory for machine information.
machineInfoFactory info.MachineInfoFactory
// Information about the cgroup subsystems.
cgroupSubsystems map[string]string
// Information about mounted filesystems.
fsInfo fs.FsInfo
// Watcher for inotify events.
watcher *common.InotifyWatcher
// List of metrics to be included.
includedMetrics map[container.MetricKind]struct{}
// List of raw container cgroup path prefix whitelist.
rawPrefixWhiteList []string
}
func (f *rawFactory) String() string {
return "raw"
}
func (f *rawFactory) NewContainerHandler(name string, metadataEnvAllowList []string, inHostNamespace bool) (container.ContainerHandler, error) {
rootFs := "/"
if !inHostNamespace {
rootFs = "/rootfs"
}
return newRawContainerHandler(name, f.cgroupSubsystems, f.machineInfoFactory, f.fsInfo, f.watcher, rootFs, f.includedMetrics)
}
// The raw factory can handle any container. If --docker_only is set to true, non-docker containers are ignored except for "/" and those whitelisted by raw_cgroup_prefix_whitelist flag.
func (f *rawFactory) CanHandleAndAccept(name string) (bool, bool, error) {
if name == "/" {
return true, true, nil
}
if *DockerOnly && f.rawPrefixWhiteList[0] == "" {
return true, false, nil
}
for _, prefix := range f.rawPrefixWhiteList {
if strings.HasPrefix(name, prefix) {
return true, true, nil
}
}
return true, false, nil
}
func (f *rawFactory) DebugInfo() map[string][]string {
return common.DebugInfo(f.watcher.GetWatches())
}
func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics map[container.MetricKind]struct{}, rawPrefixWhiteList []string) error {
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics)
if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err)
}
if len(cgroupSubsystems) == 0 {
return fmt.Errorf("failed to find supported cgroup mounts for the raw factory")
}
watcher, err := common.NewInotifyWatcher()
if err != nil {
return err
}
klog.V(1).Infof("Registering Raw factory")
factory := &rawFactory{
machineInfoFactory: machineInfoFactory,
fsInfo: fsInfo,
cgroupSubsystems: cgroupSubsystems,
watcher: watcher,
includedMetrics: includedMetrics,
rawPrefixWhiteList: rawPrefixWhiteList,
}
container.RegisterContainerHandlerFactory(factory, []watch.ContainerWatchSource{watch.Raw})
return nil
}

View File

@ -0,0 +1,304 @@
// 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.
// Handler for "raw" containers.
package raw
import (
"fmt"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/machine"
"github.com/opencontainers/runc/libcontainer/cgroups"
"k8s.io/klog/v2"
)
type rawContainerHandler struct {
// Name of the container for this handler.
name string
machineInfoFactory info.MachineInfoFactory
// Absolute path to the cgroup hierarchies of this container.
// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
cgroupPaths map[string]string
fsInfo fs.FsInfo
externalMounts []common.Mount
includedMetrics container.MetricSet
libcontainerHandler *libcontainer.Handler
}
func isRootCgroup(name string) bool {
return name == "/"
}
func newRawContainerHandler(name string, cgroupSubsystems map[string]string, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, watcher *common.InotifyWatcher, rootFs string, includedMetrics container.MetricSet) (container.ContainerHandler, error) {
cHints, err := common.GetContainerHintsFromFile(*common.ArgContainerHints)
if err != nil {
return nil, err
}
cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems, name)
cgroupManager, err := libcontainer.NewCgroupManager(name, cgroupPaths)
if err != nil {
return nil, err
}
var externalMounts []common.Mount
for _, container := range cHints.AllHosts {
if name == container.FullName {
externalMounts = container.Mounts
break
}
}
pid := 0
if isRootCgroup(name) {
pid = 1
// delete pids from cgroup paths because /sys/fs/cgroup/pids/pids.current not exist
delete(cgroupPaths, "pids")
}
handler := libcontainer.NewHandler(cgroupManager, rootFs, pid, includedMetrics)
return &rawContainerHandler{
name: name,
machineInfoFactory: machineInfoFactory,
cgroupPaths: cgroupPaths,
fsInfo: fsInfo,
externalMounts: externalMounts,
includedMetrics: includedMetrics,
libcontainerHandler: handler,
}, nil
}
func (h *rawContainerHandler) ContainerReference() (info.ContainerReference, error) {
// We only know the container by its one name.
return info.ContainerReference{
Name: h.name,
}, nil
}
func (h *rawContainerHandler) GetRootNetworkDevices() ([]info.NetInfo, error) {
nd := []info.NetInfo{}
if isRootCgroup(h.name) {
mi, err := h.machineInfoFactory.GetMachineInfo()
if err != nil {
return nd, err
}
return mi.NetworkDevices, nil
}
return nd, nil
}
// Nothing to start up.
func (h *rawContainerHandler) Start() {}
// Nothing to clean up.
func (h *rawContainerHandler) Cleanup() {}
func (h *rawContainerHandler) GetSpec() (info.ContainerSpec, error) {
const hasNetwork = false
hasFilesystem := isRootCgroup(h.name) || len(h.externalMounts) > 0
spec, err := common.GetSpec(h.cgroupPaths, h.machineInfoFactory, hasNetwork, hasFilesystem)
if err != nil {
return spec, err
}
if isRootCgroup(h.name) {
// Check physical network devices for root container.
nd, err := h.GetRootNetworkDevices()
if err != nil {
return spec, err
}
spec.HasNetwork = spec.HasNetwork || len(nd) != 0
// Get memory and swap limits of the running machine
memLimit, err := machine.GetMachineMemoryCapacity()
if err != nil {
klog.Warningf("failed to obtain memory limit for machine container")
spec.HasMemory = false
} else {
spec.Memory.Limit = uint64(memLimit)
// Spec is marked to have memory only if the memory limit is set
spec.HasMemory = true
}
swapLimit, err := machine.GetMachineSwapCapacity()
if err != nil {
klog.Warningf("failed to obtain swap limit for machine container")
} else {
spec.Memory.SwapLimit = uint64(swapLimit)
}
}
return spec, nil
}
func fsToFsStats(fs *fs.Fs) info.FsStats {
inodes := uint64(0)
inodesFree := uint64(0)
hasInodes := fs.InodesFree != nil
if hasInodes {
inodes = *fs.Inodes
inodesFree = *fs.InodesFree
}
return info.FsStats{
Device: fs.Device,
Type: fs.Type.String(),
Limit: fs.Capacity,
Usage: fs.Capacity - fs.Free,
HasInodes: hasInodes,
Inodes: inodes,
InodesFree: inodesFree,
Available: fs.Available,
ReadsCompleted: fs.DiskStats.ReadsCompleted,
ReadsMerged: fs.DiskStats.ReadsMerged,
SectorsRead: fs.DiskStats.SectorsRead,
ReadTime: fs.DiskStats.ReadTime,
WritesCompleted: fs.DiskStats.WritesCompleted,
WritesMerged: fs.DiskStats.WritesMerged,
SectorsWritten: fs.DiskStats.SectorsWritten,
WriteTime: fs.DiskStats.WriteTime,
IoInProgress: fs.DiskStats.IoInProgress,
IoTime: fs.DiskStats.IoTime,
WeightedIoTime: fs.DiskStats.WeightedIoTime,
}
}
func (h *rawContainerHandler) getFsStats(stats *info.ContainerStats) error {
var filesystems []fs.Fs
var err error
// Early exist if no disk metrics are to be collected.
if !h.includedMetrics.Has(container.DiskUsageMetrics) && !h.includedMetrics.Has(container.DiskIOMetrics) {
return nil
}
// Get Filesystem information only for the root cgroup.
if isRootCgroup(h.name) {
filesystems, err = h.fsInfo.GetGlobalFsInfo()
if err != nil {
return err
}
} else {
if len(h.externalMounts) > 0 {
mountSet := make(map[string]struct{})
for _, mount := range h.externalMounts {
mountSet[mount.HostDir] = struct{}{}
}
filesystems, err = h.fsInfo.GetFsInfoForPath(mountSet)
if err != nil {
return err
}
}
}
if h.includedMetrics.Has(container.DiskUsageMetrics) {
for i := range filesystems {
fs := filesystems[i]
stats.Filesystem = append(stats.Filesystem, fsToFsStats(&fs))
}
}
if h.includedMetrics.Has(container.DiskIOMetrics) {
common.AssignDeviceNamesToDiskStats(&fsNamer{fs: filesystems, factory: h.machineInfoFactory}, &stats.DiskIo)
}
return nil
}
func (h *rawContainerHandler) GetStats() (*info.ContainerStats, error) {
if *disableRootCgroupStats && isRootCgroup(h.name) {
return nil, nil
}
stats, err := h.libcontainerHandler.GetStats()
if err != nil {
return stats, err
}
// Get filesystem stats.
err = h.getFsStats(stats)
if err != nil {
return stats, err
}
return stats, nil
}
func (h *rawContainerHandler) GetCgroupPath(resource string) (string, error) {
var res string
if !cgroups.IsCgroup2UnifiedMode() {
res = resource
}
path, ok := h.cgroupPaths[res]
if !ok {
return "", fmt.Errorf("could not find path for resource %q for container %q", resource, h.name)
}
return path, nil
}
func (h *rawContainerHandler) GetContainerLabels() map[string]string {
return map[string]string{}
}
func (h *rawContainerHandler) GetContainerIPAddress() string {
// the IP address for the raw container corresponds to the system ip address.
return "127.0.0.1"
}
func (h *rawContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
return common.ListContainers(h.name, h.cgroupPaths, listType)
}
func (h *rawContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
return h.libcontainerHandler.GetProcesses()
}
func (h *rawContainerHandler) Exists() bool {
return common.CgroupExists(h.cgroupPaths)
}
func (h *rawContainerHandler) Type() container.ContainerType {
return container.ContainerTypeRaw
}
type fsNamer struct {
fs []fs.Fs
factory info.MachineInfoFactory
info common.DeviceNamer
}
func (n *fsNamer) DeviceName(major, minor uint64) (string, bool) {
for _, info := range n.fs {
if uint64(info.Major) == major && uint64(info.Minor) == minor {
return info.Device, true
}
}
if n.info == nil {
mi, err := n.factory.GetMachineInfo()
if err != nil {
return "", false
}
n.info = (*common.MachineInfoNamer)(mi)
}
return n.info.DeviceName(major, minor)
}

View File

@ -0,0 +1,243 @@
// 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.
// Package container defines types for sub-container events and also
// defines an interface for container operation handlers.
package raw
import (
"fmt"
"os"
"path"
"strings"
inotify "k8s.io/utils/inotify"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/watcher"
"k8s.io/klog/v2"
)
type rawContainerWatcher struct {
// Absolute path to the root of the cgroup hierarchies
cgroupPaths map[string]string
// Inotify event watcher.
watcher *common.InotifyWatcher
// Signal for watcher thread to stop.
stopWatcher chan error
}
func NewRawContainerWatcher(includedMetrics container.MetricSet) (watcher.ContainerWatcher, error) {
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics)
if err != nil {
return nil, fmt.Errorf("failed to get cgroup subsystems: %v", err)
}
if len(cgroupSubsystems) == 0 {
return nil, fmt.Errorf("failed to find supported cgroup mounts for the raw factory")
}
watcher, err := common.NewInotifyWatcher()
if err != nil {
return nil, err
}
rawWatcher := &rawContainerWatcher{
cgroupPaths: cgroupSubsystems,
watcher: watcher,
stopWatcher: make(chan error),
}
return rawWatcher, nil
}
func (w *rawContainerWatcher) Start(events chan watcher.ContainerEvent) error {
// Watch this container (all its cgroups) and all subdirectories.
watched := make([]string, 0)
for _, cgroupPath := range w.cgroupPaths {
_, err := w.watchDirectory(events, cgroupPath, "/")
if err != nil {
for _, watchedCgroupPath := range watched {
_, removeErr := w.watcher.RemoveWatch("/", watchedCgroupPath)
if removeErr != nil {
klog.Warningf("Failed to remove inotify watch for %q with error: %v", watchedCgroupPath, removeErr)
}
}
return err
}
watched = append(watched, cgroupPath)
}
// Process the events received from the kernel.
go func() {
for {
select {
case event := <-w.watcher.Event():
err := w.processEvent(event, events)
if err != nil {
klog.Warningf("Error while processing event (%+v): %v", event, err)
}
case err := <-w.watcher.Error():
klog.Warningf("Error while watching %q: %v", "/", err)
case <-w.stopWatcher:
err := w.watcher.Close()
if err == nil {
w.stopWatcher <- err
return
}
}
}
}()
return nil
}
func (w *rawContainerWatcher) Stop() error {
// Rendezvous with the watcher thread.
w.stopWatcher <- nil
return <-w.stopWatcher
}
// Watches the specified directory and all subdirectories. Returns whether the path was
// already being watched and an error (if any).
func (w *rawContainerWatcher) watchDirectory(events chan watcher.ContainerEvent, dir string, containerName string) (bool, error) {
// Don't watch .mount cgroups because they never have containers as sub-cgroups. A single container
// can have many .mount cgroups associated with it which can quickly exhaust the inotify watches on a node.
if strings.HasSuffix(containerName, ".mount") {
return false, nil
}
alreadyWatching, err := w.watcher.AddWatch(containerName, dir)
if err != nil {
return alreadyWatching, err
}
// Remove the watch if further operations failed.
cleanup := true
defer func() {
if cleanup {
_, err := w.watcher.RemoveWatch(containerName, dir)
if err != nil {
klog.Warningf("Failed to remove inotify watch for %q: %v", dir, err)
}
}
}()
// TODO(vmarmol): We should re-do this once we're done to ensure directories were not added in the meantime.
// Watch subdirectories as well.
entries, err := os.ReadDir(dir)
if err != nil {
return alreadyWatching, err
}
for _, entry := range entries {
if entry.IsDir() {
entryPath := path.Join(dir, entry.Name())
subcontainerName := path.Join(containerName, entry.Name())
alreadyWatchingSubDir, err := w.watchDirectory(events, entryPath, subcontainerName)
if err != nil {
klog.Errorf("Failed to watch directory %q: %v", entryPath, err)
if os.IsNotExist(err) {
// The directory may have been removed before watching. Try to watch the other
// subdirectories. (https://github.com/kubernetes/kubernetes/issues/28997)
continue
}
return alreadyWatching, err
}
// since we already missed the creation event for this directory, publish an event here.
if !alreadyWatchingSubDir {
go func() {
events <- watcher.ContainerEvent{
EventType: watcher.ContainerAdd,
Name: subcontainerName,
WatchSource: watcher.Raw,
}
}()
}
}
}
cleanup = false
return alreadyWatching, nil
}
func (w *rawContainerWatcher) processEvent(event *inotify.Event, events chan watcher.ContainerEvent) error {
// Convert the inotify event type to a container create or delete.
var eventType watcher.ContainerEventType
switch {
case (event.Mask & inotify.InCreate) > 0:
eventType = watcher.ContainerAdd
case (event.Mask & inotify.InDelete) > 0:
eventType = watcher.ContainerDelete
case (event.Mask & inotify.InMovedFrom) > 0:
eventType = watcher.ContainerDelete
case (event.Mask & inotify.InMovedTo) > 0:
eventType = watcher.ContainerAdd
default:
// Ignore other events.
return nil
}
// Derive the container name from the path name.
var containerName string
for _, mount := range w.cgroupPaths {
mountLocation := path.Clean(mount) + "/"
if strings.HasPrefix(event.Name, mountLocation) {
containerName = event.Name[len(mountLocation)-1:]
break
}
}
if containerName == "" {
return fmt.Errorf("unable to detect container from watch event on directory %q", event.Name)
}
// Maintain the watch for the new or deleted container.
switch eventType {
case watcher.ContainerAdd:
// New container was created, watch it.
alreadyWatched, err := w.watchDirectory(events, event.Name, containerName)
if err != nil {
return err
}
// Only report container creation once.
if alreadyWatched {
return nil
}
case watcher.ContainerDelete:
// Container was deleted, stop watching for it.
lastWatched, err := w.watcher.RemoveWatch(containerName, event.Name)
if err != nil {
return err
}
// Only report container deletion once.
if !lastWatched {
return nil
}
default:
return fmt.Errorf("unknown event type %v", eventType)
}
// Deliver the event.
events <- watcher.ContainerEvent{
EventType: eventType,
Name: containerName,
WatchSource: watcher.Raw,
}
return nil
}

View File

@ -0,0 +1,59 @@
// 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 systemd
import (
"fmt"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/watcher"
"k8s.io/klog/v2"
)
type systemdFactory struct{}
func (f *systemdFactory) String() string {
return "systemd"
}
func (f *systemdFactory) NewContainerHandler(name string, metadataEnvAllowList []string, inHostNamespace bool) (container.ContainerHandler, error) {
return nil, fmt.Errorf("Not yet supported")
}
func (f *systemdFactory) CanHandleAndAccept(name string) (bool, bool, error) {
// on systemd using devicemapper each mount into the container has an associated cgroup that we ignore.
// for details on .mount units: http://man7.org/linux/man-pages/man5/systemd.mount.5.html
if strings.HasSuffix(name, ".mount") {
return true, false, nil
}
klog.V(5).Infof("%s not handled by systemd handler", name)
return false, false, nil
}
func (f *systemdFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
// Register registers the systemd container factory.
func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) error {
klog.V(1).Infof("Registering systemd factory")
factory := &systemdFactory{}
container.RegisterContainerHandlerFactory(factory, []watcher.ContainerWatchSource{watcher.Raw})
return nil
}

View File

@ -0,0 +1,30 @@
// Copyright 2019 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.
// The install package registers systemd.NewPlugin() as the "systemd" container provider when imported
package install
import (
"k8s.io/klog/v2"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/systemd"
)
func init() {
err := container.RegisterPlugin("systemd", systemd.NewPlugin())
if err != nil {
klog.Fatalf("Failed to register systemd plugin: %v", err)
}
}

View File

@ -0,0 +1,38 @@
// Copyright 2019 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 systemd
import (
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/watcher"
)
// NewPlugin returns an implementation of container.Plugin suitable for passing to container.RegisterPlugin()
func NewPlugin() container.Plugin {
return &plugin{}
}
type plugin struct{}
func (p *plugin) InitializeFSContext(context *fs.Context) error {
return nil
}
func (p *plugin) Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) (watcher.ContainerWatcher, error) {
err := Register(factory, fsInfo, includedMetrics)
return nil, err
}