forked from firecracker-microvm/firecracker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
memory.py
132 lines (106 loc) · 4.01 KB
/
memory.py
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
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""Utilities for measuring memory utilization for a process."""
import time
from threading import Thread
import psutil
class MemoryUsageExceededError(Exception):
"""A custom exception containing details on excessive memory usage."""
def __init__(self, usage, threshold, *args):
"""Compose the error message containing the memory consumption."""
super().__init__(
f"Memory usage ({usage / 2**20:.2f} MiB) exceeded maximum threshold "
f"({threshold / 2**20} MiB)",
*args,
)
class MemoryMonitor(Thread):
"""Class to represent an RSS memory monitor for a Firecracker process.
The guest's memory region is skipped, as the main interest is the
VMM memory usage.
"""
# If guest memory is >3328MB, it is split in a 2nd region
X86_MEMORY_GAP_START = 3328 * 2**20
def __init__(self, vm, threshold=5 * 2**20, period_s=0.05):
"""Initialize monitor attributes."""
Thread.__init__(self)
self._vm = vm
self.threshold = threshold
self._exceeded = None
self._period_s = period_s
self._should_stop = False
self._current_rss = 0
self.daemon = True
def signal_stop(self):
"""Signal that the thread should stop."""
self._should_stop = True
def stop(self):
"""Stop the thread"""
if self.is_alive():
self.signal_stop()
self.join(timeout=1)
def run(self):
"""Thread for monitoring the RSS memory usage of a Firecracker process.
If overhead memory exceeds the maximum value, it is saved and memory
monitoring ceases. It is up to the caller to check.
"""
guest_mem_bytes = self._vm.mem_size_bytes
try:
ps = psutil.Process(self._vm.firecracker_pid)
except psutil.NoSuchProcess:
return
while not self._should_stop:
try:
mmaps = ps.memory_maps(grouped=False)
except psutil.NoSuchProcess:
return
mem_total = 0
for mmap in mmaps:
if self.is_guest_mem(mmap.size, guest_mem_bytes):
continue
mem_total += mmap.rss
self._current_rss = mem_total
if mem_total > self.threshold:
self._exceeded = ps
return
time.sleep(self._period_s)
def is_guest_mem(self, size, guest_mem_bytes):
"""
If the address is recognised as a guest memory region,
return True, otherwise return False.
"""
# If x86_64 guest memory exceeds 3328M, it will be split
# in 2 regions: 3328M and the rest. We have 3 cases here
# to recognise a guest memory region:
# - its size matches the guest memory exactly
# - its size is 3328M
# - its size is guest memory minus 3328M.
return size in (
guest_mem_bytes,
self.X86_MEMORY_GAP_START,
guest_mem_bytes - self.X86_MEMORY_GAP_START,
)
def check_samples(self):
"""Check that there are no samples over the threshold."""
if self._exceeded is not None:
raise MemoryUsageExceededError(
self._current_rss, self.threshold, self._exceeded
)
@property
def current_rss(self):
"""Obtain current RSS for Firecracker's overhead."""
# This is to ensure that the monitor has updated itself.
time.sleep(2 * self._period_s)
return self._current_rss
def __enter__(self):
"""To use it as a Context Manager
>>> mm = MemoryMonitor(vm, threshold=10*1024)
>>> with mm:
>>> # do stuff
"""
self.start()
def __exit__(self, _type, _value, _traceback):
"""Exit context"""
if self.is_alive():
self.signal_stop()
self.join(timeout=1)
self.check_samples()