Skip to content

Commit a09cdda

Browse files
committed
Add ulimit metrics
1 parent 35004ab commit a09cdda

File tree

6 files changed

+152
-2
lines changed

6 files changed

+152
-2
lines changed

container/libcontainer/handler.go

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"io/ioutil"
2222
"os"
2323
"path"
24+
"regexp"
2425
"strconv"
2526
"strings"
2627
"time"
@@ -44,6 +45,8 @@ type Handler struct {
4445
pidMetricsCache map[int]*info.CpuSchedstat
4546
}
4647

48+
var whitelistedUlimits = [...]string{"max_open_files"}
49+
4750
func NewHandler(cgroupManager cgroups.Manager, rootFs string, pid int, includedMetrics container.MetricSet) *Handler {
4851
return &Handler{
4952
cgroupManager: cgroupManager,
@@ -125,7 +128,7 @@ func (h *Handler) GetStats() (*info.ContainerStats, error) {
125128
if !ok {
126129
klog.V(4).Infof("Could not find cgroups CPU for container %d", h.pid)
127130
} else {
128-
stats.Processes, err = processStatsFromProcs(h.rootFs, path)
131+
stats.Processes, err = processStatsFromProcs(h.rootFs, path, h.pid)
129132
if err != nil {
130133
klog.V(4).Infof("Unable to get Process Stats: %v", err)
131134
}
@@ -143,7 +146,76 @@ func (h *Handler) GetStats() (*info.ContainerStats, error) {
143146
return stats, nil
144147
}
145148

146-
func processStatsFromProcs(rootFs string, cgroupPath string) (info.ProcessStats, error) {
149+
func parseUlimit(value string) (int64, error) {
150+
num, err := strconv.ParseInt(value, 10, 64)
151+
if err != nil {
152+
if strings.EqualFold(value, "unlimited") {
153+
// -1 implies unlimited except for priority and nice; man limits.conf
154+
num = -1
155+
} else {
156+
// Value is not a number or "unlimited"; return an error
157+
return 0, fmt.Errorf("unable to parse limit: %s", value)
158+
}
159+
}
160+
return num, nil
161+
}
162+
163+
func isUlimitWhitelisted(name string) bool {
164+
for _, whitelist := range whitelistedUlimits {
165+
if name == whitelist {
166+
return true
167+
}
168+
}
169+
return false
170+
}
171+
172+
func processLimitsFile(fileData string) []info.UlimitSpec {
173+
limits := strings.Split(fileData, "\n")
174+
ulimits := make([]info.UlimitSpec, 0, len(limits))
175+
for _, lim := range limits {
176+
// Skip any headers/footers
177+
if strings.HasPrefix(lim, "Max") {
178+
179+
// Line format: Max open files 16384 16384 files
180+
fields := regexp.MustCompile("[\\s]{2,}").Split(lim, -1)
181+
name := strings.Replace(strings.ToLower(strings.TrimSpace(fields[0])), " ", "_", -1)
182+
183+
found := isUlimitWhitelisted(name)
184+
if !found {
185+
continue
186+
}
187+
188+
soft := strings.TrimSpace(fields[1])
189+
soft_num, soft_err := parseUlimit(soft)
190+
191+
hard := strings.TrimSpace(fields[2])
192+
hard_num, hard_err := parseUlimit(hard)
193+
194+
// Omit metric if there were any parsing errors
195+
if soft_err == nil && hard_err == nil {
196+
ulimitSpec := info.UlimitSpec{
197+
Name: name,
198+
SoftLimit: int64(soft_num),
199+
HardLimit: int64(hard_num),
200+
}
201+
ulimits = append(ulimits, ulimitSpec)
202+
}
203+
}
204+
}
205+
return ulimits
206+
}
207+
208+
func processRootProcUlimits(rootFs string, rootPid int) []info.UlimitSpec {
209+
filePath := path.Join(rootFs, "/proc", strconv.Itoa(rootPid), "limits")
210+
out, err := ioutil.ReadFile(filePath)
211+
if err != nil {
212+
klog.V(4).Infof("error while listing directory %q to read ulimits: %v", filePath, err)
213+
return []info.UlimitSpec{}
214+
}
215+
return processLimitsFile(string(out))
216+
}
217+
218+
func processStatsFromProcs(rootFs string, cgroupPath string, rootPid int) (info.ProcessStats, error) {
147219
var fdCount, socketCount uint64
148220
filePath := path.Join(cgroupPath, "cgroup.procs")
149221
out, err := ioutil.ReadFile(filePath)
@@ -180,11 +252,13 @@ func processStatsFromProcs(rootFs string, cgroupPath string) (info.ProcessStats,
180252
}
181253
}
182254
}
255+
ulimits := processRootProcUlimits(rootFs, rootPid)
183256

184257
processStats := info.ProcessStats{
185258
ProcessCount: uint64(len(pids)),
186259
FdCount: fdCount,
187260
SocketCount: socketCount,
261+
Ulimits: ulimits,
188262
}
189263

190264
return processStats, nil

container/libcontainer/handler_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package libcontainer
1616

1717
import (
1818
"os"
19+
"reflect"
1920
"testing"
2021

2122
info "github.com/google/cadvisor/info/v1"
@@ -175,3 +176,42 @@ func TestSetProcessesStats(t *testing.T) {
175176
}
176177

177178
}
179+
180+
func TestParseLimitsFile(t *testing.T) {
181+
var testData = []struct {
182+
limitLine string
183+
expected []info.UlimitSpec
184+
}{
185+
{
186+
"Limit Soft Limit Hard Limit Units \n",
187+
[]info.UlimitSpec{},
188+
},
189+
{
190+
"Max open files 8192 8192 files \n",
191+
[]info.UlimitSpec{{Name: "max_open_files", SoftLimit: 8192, HardLimit: 8192}},
192+
},
193+
{
194+
"Max open files 85899345920 85899345920 files \n",
195+
[]info.UlimitSpec{{Name: "max_open_files", SoftLimit: 85899345920, HardLimit: 85899345920}},
196+
},
197+
{
198+
"Max open files gibberish1 8192 files \n",
199+
[]info.UlimitSpec{},
200+
},
201+
{
202+
"Max open files 8192 0xbaddata files \n",
203+
[]info.UlimitSpec{},
204+
},
205+
{
206+
"Max stack size 8192 8192 files \n",
207+
[]info.UlimitSpec{},
208+
},
209+
}
210+
211+
for _, testItem := range testData {
212+
actual := processLimitsFile(testItem.limitLine)
213+
if reflect.DeepEqual(actual, testItem.expected) == false {
214+
t.Fatalf("Parsed ulimit doesn't match expected values for line: %s", testItem.limitLine)
215+
}
216+
}
217+
}

info/v1/container.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,12 @@ type AcceleratorStats struct {
564564
DutyCycle uint64 `json:"duty_cycle"`
565565
}
566566

567+
type UlimitSpec struct {
568+
Name string `json:"name"`
569+
SoftLimit int64 `json:"soft_limit"`
570+
HardLimit int64 `json:"hard_limit"`
571+
}
572+
567573
type ProcessStats struct {
568574
// Number of processes
569575
ProcessCount uint64 `json:"process_count"`
@@ -579,6 +585,9 @@ type ProcessStats struct {
579585

580586
// Maxium number of threads allowed in container
581587
ThreadsMax uint64 `json:"threads_max,omitempty"`
588+
589+
// Ulimits for the top-level container process
590+
Ulimits []UlimitSpec `json:"ulimits,omitempty"`
582591
}
583592

584593
type ContainerStats struct {

metrics/prometheus.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,23 @@ func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc, includedMetri
10791079
}
10801080
},
10811081
},
1082+
{
1083+
name: "container_ulimits_soft",
1084+
help: "Soft ulimit values for the container root process. Unlimited if -1, except priority and nice",
1085+
valueType: prometheus.GaugeValue,
1086+
extraLabels: []string{"ulimit"},
1087+
getValues: func(s *info.ContainerStats) metricValues {
1088+
values := make(metricValues, 0, len(s.Processes.Ulimits))
1089+
for _, ulimit := range s.Processes.Ulimits {
1090+
values = append(values, metricValue{
1091+
value: float64(ulimit.SoftLimit),
1092+
labels: []string{ulimit.Name},
1093+
timestamp: s.Timestamp,
1094+
})
1095+
}
1096+
return values
1097+
},
1098+
},
10821099
}...)
10831100

10841101
}

metrics/prometheus_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,13 @@ func (p testSubcontainersInfoProvider) SubcontainersInfo(string, *info.Container
260260
SocketCount: 3,
261261
ThreadsCurrent: 5,
262262
ThreadsMax: 100,
263+
Ulimits: []info.UlimitSpec{
264+
{
265+
Name: "max_open_files",
266+
SoftLimit: 16384,
267+
HardLimit: 16384,
268+
},
269+
},
263270
},
264271
TaskStats: info.LoadStats{
265272
NrSleeping: 50,

metrics/testdata/prometheus_metrics

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ container_threads{container_env_foo_env="prod",container_label_foo_label="bar",i
238238
# HELP container_threads_max Maximum number of threads allowed inside the container, infinity if value is zero
239239
# TYPE container_threads_max gauge
240240
container_threads_max{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 100 1395066363000
241+
# HELP container_ulimits_soft Soft ulimit values for the container root process. Unlimited if -1, except priority and nice
242+
# TYPE container_ulimits_soft gauge
243+
container_ulimits_soft{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",ulimit="max_open_files",zone_name="hello"} 16384 1395066363000
241244
# HELP machine_cpu_cores Number of CPU cores on the machine.
242245
# TYPE machine_cpu_cores gauge
243246
machine_cpu_cores 4

0 commit comments

Comments
 (0)