forked from elastic/gosigar
-
Notifications
You must be signed in to change notification settings - Fork 0
/
reader.go
162 lines (142 loc) · 4.76 KB
/
reader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package cgroup
import (
"path/filepath"
)
// Stats contains metrics and limits from each of the cgroup subsystems.
type Stats struct {
Metadata
CPU *CPUSubsystem `json:"cpu"`
CPUAccounting *CPUAccountingSubsystem `json:"cpuacct"`
Memory *MemorySubsystem `json:"memory"`
BlockIO *BlockIOSubsystem `json:"blkio"`
}
// Metadata contains metadata associated with cgroup stats.
type Metadata struct {
ID string `json:"id,omitempty"` // ID of the cgroup.
Path string `json:"path,omitempty"` // Path to the cgroup relative to the cgroup subsystem's mountpoint.
}
type mount struct {
subsystem string // Subsystem name (e.g. cpuacct).
mountpoint string // Mountpoint of the subsystem (e.g. /cgroup/cpuacct).
path string // Relative path to the cgroup (e.g. /docker/<id>).
id string // ID of the cgroup.
fullPath string // Absolute path to the cgroup. It's the mountpoint joined with the path.
}
// Reader reads cgroup metrics and limits.
type Reader struct {
// Mountpoint of the root filesystem. Defaults to / if not set. This can be
// useful for example if you mount / as /rootfs inside of a container.
rootfsMountpoint string
ignoreRootCgroups bool // Ignore a cgroup when its path is "/".
cgroupMountpoints map[string]string // Mountpoints for each subsystem (e.g. cpu, cpuacct, memory, blkio).
}
// NewReader creates and returns a new Reader.
func NewReader(rootfsMountpoint string, ignoreRootCgroups bool) (*Reader, error) {
if rootfsMountpoint == "" {
rootfsMountpoint = "/"
}
// Determine what subsystems are supported by the kernel.
subsystems, err := SupportedSubsystems(rootfsMountpoint)
if err != nil {
return nil, err
}
// Locate the mountpoints of those subsystems.
mountpoints, err := SubsystemMountpoints(rootfsMountpoint, subsystems)
if err != nil {
return nil, err
}
return &Reader{
rootfsMountpoint: rootfsMountpoint,
ignoreRootCgroups: ignoreRootCgroups,
cgroupMountpoints: mountpoints,
}, nil
}
// GetStatsForProcess returns cgroup metrics and limits associated with a process.
func (r *Reader) GetStatsForProcess(pid int) (*Stats, error) {
// Read /proc/[pid]/cgroup to get the paths to the cgroup metrics.
paths, err := ProcessCgroupPaths(r.rootfsMountpoint, pid)
if err != nil {
return nil, err
}
// Build the full path for the subsystems we are interested in.
mounts := map[string]mount{}
for _, interestedSubsystem := range []string{"blkio", "cpu", "cpuacct", "memory"} {
path, found := paths[interestedSubsystem]
if !found {
continue
}
if path == "/" && r.ignoreRootCgroups {
continue
}
subsystemMount, found := r.cgroupMountpoints[interestedSubsystem]
if !found {
continue
}
mounts[interestedSubsystem] = mount{
subsystem: interestedSubsystem,
mountpoint: subsystemMount,
path: path,
id: filepath.Base(path),
fullPath: filepath.Join(subsystemMount, path),
}
}
stats := Stats{Metadata: getCommonCgroupMetadata(mounts)}
// Collect stats from each cgroup subsystem associated with the task.
if mount, found := mounts["blkio"]; found {
stats.BlockIO = &BlockIOSubsystem{}
err := stats.BlockIO.get(mount.fullPath)
if err != nil {
return nil, err
}
stats.BlockIO.Metadata.ID = mount.id
stats.BlockIO.Metadata.Path = mount.path
}
if mount, found := mounts["cpu"]; found {
stats.CPU = &CPUSubsystem{}
err := stats.CPU.get(mount.fullPath)
if err != nil {
return nil, err
}
stats.CPU.Metadata.ID = mount.id
stats.CPU.Metadata.Path = mount.path
}
if mount, found := mounts["cpuacct"]; found {
stats.CPUAccounting = &CPUAccountingSubsystem{}
err := stats.CPUAccounting.get(mount.fullPath)
if err != nil {
return nil, err
}
stats.CPUAccounting.Metadata.ID = mount.id
stats.CPUAccounting.Metadata.Path = mount.path
}
if mount, found := mounts["memory"]; found {
stats.Memory = &MemorySubsystem{}
err := stats.Memory.get(mount.fullPath)
if err != nil {
return nil, err
}
stats.Memory.Metadata.ID = mount.id
stats.Memory.Metadata.Path = mount.path
}
// Return nil if no metrics were collected.
if stats.BlockIO == nil && stats.CPU == nil && stats.CPUAccounting == nil && stats.Memory == nil {
return nil, nil
}
return &stats, nil
}
// getCommonCgroupMetadata returns Metadata containing the cgroup path and ID
// iff all subsystems share a common path and ID. This is common for
// containerized processes. If there is no common path and ID then the returned
// values are empty strings.
func getCommonCgroupMetadata(mounts map[string]mount) Metadata {
var path string
for _, m := range mounts {
if path == "" {
path = m.path
} else if path != m.path {
// All paths are not the same.
return Metadata{}
}
}
return Metadata{Path: path, ID: filepath.Base(path)}
}