Skip to content

Commit 8ad77b4

Browse files
committed
better handling of variing amount of fields in /proc/stat (#407)
Turns out we can't rely on all fields always being available, so we need to be a little more defensive when parsing /proc/stat fixes #406 closes #407
1 parent 04f0eda commit 8ad77b4

File tree

3 files changed

+37
-14
lines changed

3 files changed

+37
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* Added some randomness to time between requests to APM Server (#426)
1010
* Fixed an issue with custom user models in Django using non-string usernames (#397, #398)
1111
* Fixed an issue with sending kubernetes metadata to the API (#401, #402)
12+
* Fixed an issue with parsing /proc/stat in RHEL/centos 6 (#406, #407)
1213
* Added copyright header to all files, and a CI check (#429)
1314

1415
## v4.1.0

elasticapm/metrics/sets/cpu_linux.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
MEM_STATS = "/proc/meminfo"
4040
PROC_STATS = "/proc/self/stat"
4141

42+
CPU_FIELDS = ("user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice")
43+
4244
whitespace_re = re.compile(r"\s+")
4345

4446

@@ -84,11 +86,22 @@ def read_system_stats(self):
8486
with open(self.sys_stats_file, "r") as pidfile:
8587
for line in pidfile:
8688
if line.startswith("cpu "):
87-
user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice = map(
88-
int, whitespace_re.split(line)[1:-1]
89+
fields = whitespace_re.split(line)[1:-1]
90+
num_fields = len(fields)
91+
# Not all fields are available on all platforms (e.g. RHEL 6 does not provide steal, guest, and
92+
# guest_nice. If a field is missing, we default to 0
93+
f = {field: int(fields[i]) if i < num_fields else 0 for i, field in enumerate(CPU_FIELDS)}
94+
stats["cpu_total"] = float(
95+
f["user"]
96+
+ f["nice"]
97+
+ f["system"]
98+
+ f["idle"]
99+
+ f["iowait"]
100+
+ f["irq"]
101+
+ f["softirq"]
102+
+ f["steal"]
89103
)
90-
stats["cpu_total"] = float(user + nice + system + idle + iowait + irq + softirq + steal)
91-
stats["cpu_usage"] = stats["cpu_total"] - (idle + iowait)
104+
stats["cpu_usage"] = stats["cpu_total"] - (f["idle"] + f["iowait"])
92105
break
93106
with open(self.memory_stats_file, "r") as memfile:
94107
for line in memfile:

tests/metrics/cpu_linux_tests.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,11 @@
3939

4040

4141
TEMPLATE_PROC_STAT_SELF = """32677 (python) R 5333 32677 5333 34822 32677 4194304 13815 176 2 0 {utime} {stime} 0 0 20 0 7 0 6010710 3686981632 11655 18446744073709551615 94580847771648 94580849947501 140732830512176 0 0 0 0 16781312 134217730 0 0 0 17 1 0 0 6 0 0 94580850578256 94580851016824 94580875862016 140732830518932 140732830518950 140732830518950 140732830523339 0"""
42-
TEMPLATE_PROC_STAT = """cpu {user} 2037 278561 {idle} 15536 0 178811 0 0 0
42+
43+
44+
TEMPLATE_PROC_STAT_DEBIAN = """cpu {user} 2037 278561 {idle} 15536 0 178811 0 0 0
4345
cpu0 189150 166 34982 1081369 2172 0 73586 0 0 0
4446
cpu1 190286 201 35110 637359 1790 0 41941 0 0 0
45-
cpu2 191041 229 34494 636805 1782 0 21446 0 0 0
46-
cpu3 192144 309 34143 635312 1808 0 16014 0 0 0
47-
cpu4 190204 348 33770 636288 1880 0 9717 0 0 0
48-
cpu5 188740 333 33741 638328 1899 0 6939 0 0 0
49-
cpu6 188475 245 34417 635169 2323 0 3987 0 0 0
50-
cpu7 184451 204 37902 638971 1879 0 5178 0 0 0
5147
intr 60591079 11 12496 0 0 0 0 0 0 1 27128 0 0 474144 0 0 0 0 0 0 0 0 0 0
5248
ctxt 215687788
5349
btime 1544981001
@@ -56,6 +52,18 @@
5652
procs_blocked 1
5753
softirq 61270136 2508862 17040144 383 7315609 6627 56 23515 19184746 49637 15140557
5854
"""
55+
56+
57+
TEMPLATE_PROC_STAT_RHEL = """cpu {user} 2037 278561 {idle} 15536 0 178811
58+
cpu0 259246 7001 60190 34250993 137517 772 0
59+
intr 354133732 347209999 2272 0 4 4 0 0 3 1 1249247 0 0 80143 0 422626 5169433
60+
ctxt 12547729
61+
btime 1093631447
62+
processes 130523
63+
procs_running 1
64+
procs_blocked 0"""
65+
66+
5967
TEMPLATE_PROC_MEMINFO = """MemTotal: 16164884 kB
6068
MemFree: 359184 kB
6169
MemAvailable: 6514428 kB
@@ -67,13 +75,14 @@
6775
"""
6876

6977

70-
def test_cpu_mem_from_proc(tmpdir):
78+
@pytest.mark.parametrize("proc_stat_template", [TEMPLATE_PROC_STAT_DEBIAN, TEMPLATE_PROC_STAT_RHEL])
79+
def test_cpu_mem_from_proc(proc_stat_template, tmpdir):
7180
proc_stat_self = os.path.join(tmpdir.strpath, "self-stat")
7281
proc_stat = os.path.join(tmpdir.strpath, "stat")
7382
proc_meminfo = os.path.join(tmpdir.strpath, "meminfo")
7483

7584
for path, content in (
76-
(proc_stat, TEMPLATE_PROC_STAT.format(user=0, idle=0)),
85+
(proc_stat, proc_stat_template.format(user=0, idle=0)),
7786
(proc_stat_self, TEMPLATE_PROC_STAT_SELF.format(utime=0, stime=0)),
7887
(proc_meminfo, TEMPLATE_PROC_MEMINFO),
7988
):
@@ -84,7 +93,7 @@ def test_cpu_mem_from_proc(tmpdir):
8493
)
8594

8695
for path, content in (
87-
(proc_stat, TEMPLATE_PROC_STAT.format(user=400000, idle=600000)),
96+
(proc_stat, proc_stat_template.format(user=400000, idle=600000)),
8897
(proc_stat_self, TEMPLATE_PROC_STAT_SELF.format(utime=100000, stime=100000)),
8998
(proc_meminfo, TEMPLATE_PROC_MEMINFO),
9099
):

0 commit comments

Comments
 (0)