github.com/elastic/gosigar@v0.14.3/cgroup/reader.go (about)

     1  package cgroup
     2  
     3  import (
     4  	"path/filepath"
     5  )
     6  
     7  // Stats contains metrics and limits from each of the cgroup subsystems.
     8  type Stats struct {
     9  	Metadata
    10  	CPU           *CPUSubsystem           `json:"cpu"`
    11  	CPUAccounting *CPUAccountingSubsystem `json:"cpuacct"`
    12  	Memory        *MemorySubsystem        `json:"memory"`
    13  	BlockIO       *BlockIOSubsystem       `json:"blkio"`
    14  }
    15  
    16  // Metadata contains metadata associated with cgroup stats.
    17  type Metadata struct {
    18  	ID   string `json:"id,omitempty"`   // ID of the cgroup.
    19  	Path string `json:"path,omitempty"` // Path to the cgroup relative to the cgroup subsystem's mountpoint.
    20  }
    21  
    22  type mount struct {
    23  	subsystem  string // Subsystem name (e.g. cpuacct).
    24  	mountpoint string // Mountpoint of the subsystem (e.g. /cgroup/cpuacct).
    25  	path       string // Relative path to the cgroup (e.g. /docker/<id>).
    26  	id         string // ID of the cgroup.
    27  	fullPath   string // Absolute path to the cgroup. It's the mountpoint joined with the path.
    28  }
    29  
    30  // Reader reads cgroup metrics and limits.
    31  type Reader struct {
    32  	// Mountpoint of the root filesystem. Defaults to / if not set. This can be
    33  	// useful for example if you mount / as /rootfs inside of a container.
    34  	rootfsMountpoint         string
    35  	ignoreRootCgroups        bool // Ignore a cgroup when its path is "/".
    36  	cgroupsHierarchyOverride string
    37  	cgroupMountpoints        map[string]string // Mountpoints for each subsystem (e.g. cpu, cpuacct, memory, blkio).
    38  }
    39  
    40  // ReaderOptions holds options for NewReaderOptions.
    41  type ReaderOptions struct {
    42  	// RootfsMountpoint holds the mountpoint of the root filesystem.
    43  	//
    44  	// If unspecified, "/" is assumed.
    45  	RootfsMountpoint string
    46  
    47  	// IgnoreRootCgroups ignores cgroup subsystem with the path "/".
    48  	IgnoreRootCgroups bool
    49  
    50  	// CgroupsHierarchyOverride is an optional path override for cgroup
    51  	// subsystem paths. If non-empty, this will be used instead of the
    52  	// paths specified in /proc/<pid>/cgroup.
    53  	//
    54  	// This should be set to "/" when running within a Docker container,
    55  	// where the paths in /proc/<pid>/cgroup do not correspond to any
    56  	// paths under /sys/fs/cgroup.
    57  	CgroupsHierarchyOverride string
    58  }
    59  
    60  // NewReader creates and returns a new Reader.
    61  func NewReader(rootfsMountpoint string, ignoreRootCgroups bool) (*Reader, error) {
    62  	return NewReaderOptions(ReaderOptions{
    63  		RootfsMountpoint:  rootfsMountpoint,
    64  		IgnoreRootCgroups: ignoreRootCgroups,
    65  	})
    66  }
    67  
    68  // NewReaderOptions creates and returns a new Reader with the given options.
    69  func NewReaderOptions(opts ReaderOptions) (*Reader, error) {
    70  	if opts.RootfsMountpoint == "" {
    71  		opts.RootfsMountpoint = "/"
    72  	}
    73  
    74  	// Determine what subsystems are supported by the kernel.
    75  	subsystems, err := SupportedSubsystems(opts.RootfsMountpoint)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	// Locate the mountpoints of those subsystems.
    81  	mountpoints, err := SubsystemMountpoints(opts.RootfsMountpoint, subsystems)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	return &Reader{
    87  		rootfsMountpoint:         opts.RootfsMountpoint,
    88  		ignoreRootCgroups:        opts.IgnoreRootCgroups,
    89  		cgroupsHierarchyOverride: opts.CgroupsHierarchyOverride,
    90  		cgroupMountpoints:        mountpoints,
    91  	}, nil
    92  }
    93  
    94  // GetStatsForProcess returns cgroup metrics and limits associated with a process.
    95  func (r *Reader) GetStatsForProcess(pid int) (*Stats, error) {
    96  	// Read /proc/[pid]/cgroup to get the paths to the cgroup metrics.
    97  	paths, err := ProcessCgroupPaths(r.rootfsMountpoint, pid)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	// Build the full path for the subsystems we are interested in.
   103  	mounts := map[string]mount{}
   104  	for _, interestedSubsystem := range []string{"blkio", "cpu", "cpuacct", "memory"} {
   105  		path, found := paths[interestedSubsystem]
   106  		if !found {
   107  			continue
   108  		}
   109  
   110  		if path == "/" && r.ignoreRootCgroups {
   111  			continue
   112  		}
   113  
   114  		subsystemMount, found := r.cgroupMountpoints[interestedSubsystem]
   115  		if !found {
   116  			continue
   117  		}
   118  
   119  		id := filepath.Base(path)
   120  		if r.cgroupsHierarchyOverride != "" {
   121  			path = r.cgroupsHierarchyOverride
   122  		}
   123  		mounts[interestedSubsystem] = mount{
   124  			subsystem:  interestedSubsystem,
   125  			mountpoint: subsystemMount,
   126  			id:         id,
   127  			path:       path,
   128  			fullPath:   filepath.Join(subsystemMount, path),
   129  		}
   130  	}
   131  
   132  	stats := Stats{Metadata: getCommonCgroupMetadata(mounts)}
   133  
   134  	// Collect stats from each cgroup subsystem associated with the task.
   135  	if mount, found := mounts["blkio"]; found {
   136  		stats.BlockIO = &BlockIOSubsystem{}
   137  		err := stats.BlockIO.get(mount.fullPath)
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  		stats.BlockIO.Metadata.ID = mount.id
   142  		stats.BlockIO.Metadata.Path = mount.path
   143  	}
   144  	if mount, found := mounts["cpu"]; found {
   145  		stats.CPU = &CPUSubsystem{}
   146  		err := stats.CPU.get(mount.fullPath)
   147  		if err != nil {
   148  			return nil, err
   149  		}
   150  		stats.CPU.Metadata.ID = mount.id
   151  		stats.CPU.Metadata.Path = mount.path
   152  	}
   153  	if mount, found := mounts["cpuacct"]; found {
   154  		stats.CPUAccounting = &CPUAccountingSubsystem{}
   155  		err := stats.CPUAccounting.get(mount.fullPath)
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  		stats.CPUAccounting.Metadata.ID = mount.id
   160  		stats.CPUAccounting.Metadata.Path = mount.path
   161  	}
   162  	if mount, found := mounts["memory"]; found {
   163  		stats.Memory = &MemorySubsystem{}
   164  		err := stats.Memory.get(mount.fullPath)
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  		stats.Memory.Metadata.ID = mount.id
   169  		stats.Memory.Metadata.Path = mount.path
   170  	}
   171  
   172  	// Return nil if no metrics were collected.
   173  	if stats.BlockIO == nil && stats.CPU == nil && stats.CPUAccounting == nil && stats.Memory == nil {
   174  		return nil, nil
   175  	}
   176  
   177  	return &stats, nil
   178  }
   179  
   180  // getCommonCgroupMetadata returns Metadata containing the cgroup path and ID
   181  // iff all subsystems share a common path and ID. This is common for
   182  // containerized processes. If there is no common path and ID then the returned
   183  // values are empty strings.
   184  func getCommonCgroupMetadata(mounts map[string]mount) Metadata {
   185  	var path string
   186  	for _, m := range mounts {
   187  		if path == "" {
   188  			path = m.path
   189  		} else if path != m.path {
   190  			// All paths are not the same.
   191  			return Metadata{}
   192  		}
   193  	}
   194  
   195  	return Metadata{Path: path, ID: filepath.Base(path)}
   196  }