-
Notifications
You must be signed in to change notification settings - Fork 375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initialize CPU usage #1725
Initialize CPU usage #1725
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,13 +61,9 @@ def _get_file_contents(self, file_name): | |
:return: Entire contents of the file | ||
:rtype: str | ||
""" | ||
|
||
parameter_file = self._get_cgroup_file(file_name) | ||
|
||
try: | ||
return fileutil.read_file(parameter_file) | ||
except Exception: | ||
raise | ||
return fileutil.read_file(parameter_file) | ||
|
||
def _get_parameters(self, parameter_name, first_line_only=False): | ||
""" | ||
|
@@ -118,76 +114,78 @@ def is_active(self): | |
|
||
class CpuCgroup(CGroup): | ||
def __init__(self, name, cgroup_path): | ||
""" | ||
Initialize _data collection for the Cpu controller. User must call update() before attempting to get | ||
any useful metrics. | ||
|
||
:return: CpuCgroup | ||
""" | ||
super(CpuCgroup, self).__init__(name, cgroup_path, "cpu") | ||
|
||
self._osutil = get_osutil() | ||
self._current_cpu_total = 0 | ||
self._previous_cpu_total = 0 | ||
self._current_system_cpu = self._osutil.get_total_cpu_ticks_since_boot() | ||
self._previous_system_cpu = 0 | ||
self._previous_cgroup_cpu = None | ||
self._previous_system_cpu = None | ||
self._current_cgroup_cpu = None | ||
self._current_system_cpu = None | ||
|
||
def __str__(self): | ||
return "cgroup: Name: {0}, cgroup_path: {1}; Controller: {2}".format( | ||
self.name, self.path, self.controller | ||
) | ||
|
||
def _get_current_cpu_total(self): | ||
def _get_cpu_ticks(self, allow_no_such_file_or_directory_error=False): | ||
""" | ||
Compute the number of USER_HZ of CPU time (user and system) consumed by this cgroup since boot. | ||
Returns the number of USER_HZ of CPU time (user and system) consumed by this cgroup. | ||
|
||
:return: int | ||
If allow_no_such_file_or_directory_error is set to True and cpuacct.stat does not exist the function | ||
returns 0; this is useful when the function can be called before the cgroup has been created. | ||
""" | ||
cpu_total = 0 | ||
try: | ||
cpu_stat = self._get_file_contents('cpuacct.stat') | ||
except Exception as e: | ||
if isinstance(e, (IOError, OSError)) and e.errno == errno.ENOENT: | ||
if not isinstance(e, (IOError, OSError)) or e.errno != errno.ENOENT: | ||
raise CGroupsException("Failed to read cpuacct.stat: {0}".format(ustr(e))) | ||
if not allow_no_such_file_or_directory_error: | ||
raise e | ||
raise CGroupsException("Exception while attempting to read {0}".format("cpuacct.stat"), e) | ||
cpu_stat = None | ||
|
||
cpu_ticks = 0 | ||
|
||
if cpu_stat is not None: | ||
match = re_user_system_times.match(cpu_stat) | ||
if not match: | ||
raise CGroupsException("The contents of {0} are invalid: {1}".format(self._get_cgroup_file('cpuacct.stat'), cpu_stat)) | ||
cpu_ticks = int(match.groups()[0]) + int(match.groups()[1]) | ||
|
||
return cpu_ticks | ||
|
||
if cpu_stat: | ||
m = re_user_system_times.match(cpu_stat) | ||
if m: | ||
cpu_total = int(m.groups()[0]) + int(m.groups()[1]) | ||
return cpu_total | ||
def _cpu_usage_initialized(self): | ||
return self._current_cgroup_cpu is not None and self._current_system_cpu is not None | ||
|
||
def _update_cpu_data(self): | ||
def initialize_cpu_usage(self): | ||
""" | ||
Update all raw _data required to compute metrics of interest. The intent is to call update() once, then | ||
call the various get_*() methods which use this _data, which we've collected exactly once. | ||
Sets the initial values of CPU usage. This function must be invoked before calling get_cpu_usage(). | ||
""" | ||
self._previous_cpu_total = self._current_cpu_total | ||
self._previous_system_cpu = self._current_system_cpu | ||
self._current_cpu_total = self._get_current_cpu_total() | ||
if self._cpu_usage_initialized(): | ||
raise CGroupsException("initialize_cpu_usage() should be invoked only once") | ||
self._current_cgroup_cpu = self._get_cpu_ticks(allow_no_such_file_or_directory_error=True) | ||
self._current_system_cpu = self._osutil.get_total_cpu_ticks_since_boot() | ||
|
||
def _get_cpu_percent(self): | ||
def get_cpu_usage(self): | ||
""" | ||
Compute the percent CPU time used by this cgroup over the elapsed time since the last time this instance was | ||
update()ed. If the cgroup fully consumed 2 cores on a 4 core system, return 200. | ||
Computes the CPU used by the cgroup since the last call to this function. | ||
|
||
The usage is measured as a percentage of utilization of all cores in the system. For example, | ||
using 1 core at 100% on a 4-core system would be reported as 25%. | ||
|
||
:return: CPU usage in percent of a single core | ||
:rtype: float | ||
NOTE: initialize_cpu_usage() must be invoked before calling get_cpu_usage() | ||
""" | ||
cpu_delta = self._current_cpu_total - self._previous_cpu_total | ||
system_delta = max(1, self._current_system_cpu - self._previous_system_cpu) | ||
if not self._cpu_usage_initialized(): | ||
raise CGroupsException("initialize_cpu_usage() must be invoked before the first call to get_cpu_usage()") | ||
|
||
return round(float(cpu_delta * self._osutil.get_processor_cores() * 100) / float(system_delta), 3) | ||
self._previous_cgroup_cpu = self._current_cgroup_cpu | ||
self._previous_system_cpu = self._current_system_cpu | ||
self._current_cgroup_cpu = self._get_cpu_ticks() | ||
self._current_system_cpu = self._osutil.get_total_cpu_ticks_since_boot() | ||
|
||
def get_cpu_usage(self): | ||
""" | ||
Collects and return the cpu usage. | ||
cgroup_delta = self._current_cgroup_cpu - self._previous_cgroup_cpu | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need the absolute value here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update to my comment: I assume that since we care about the ratio of the deltas, and assuming that if the cgroup delta is negative, then the system one will be, they will cancel each other out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The CPU usage is measured in ticks. More recent values would never be less than older values so the difference (both for cgroup and system) would always be positive (or zero). |
||
system_delta = max(1, self._current_system_cpu - self._previous_system_cpu) | ||
|
||
:rtype: float | ||
""" | ||
self._update_cpu_data() | ||
return self._get_cpu_percent() | ||
return round(100.0 * float(cgroup_delta) / float(system_delta), 3) | ||
|
||
|
||
class MemoryCgroup(CGroup): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call
initialize_cpu_usage
within the__init__
itself? Asking for my knowledge.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The get*cgroup functions instantiate cgroups that may never be tracked... I thought it was not worth initializing the cpu usage for those (especially since the actual cgroups may not exist on disk yet --- if we initialize on init then we would need to ensure the cgroups exist before calling the getter).