diff --git a/changelogs/fragments/52896-gather_facts-fix_negative_free_value.yml b/changelogs/fragments/52896-gather_facts-fix_negative_free_value.yml new file mode 100644 index 00000000000..86f04997913 --- /dev/null +++ b/changelogs/fragments/52896-gather_facts-fix_negative_free_value.yml @@ -0,0 +1,2 @@ +minor_changes: +- Add better parsing for gathering facts about free memory in Mac OS (https://github.com/ansible/ansible/pull/52917). diff --git a/lib/ansible/module_utils/facts/hardware/darwin.py b/lib/ansible/module_utils/facts/hardware/darwin.py index 79d58cff4e8..bd7ebd488eb 100644 --- a/lib/ansible/module_utils/facts/hardware/darwin.py +++ b/lib/ansible/module_utils/facts/hardware/darwin.py @@ -17,8 +17,8 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +from ansible.module_utils.common.process import get_bin_path from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector - from ansible.module_utils.facts.sysctl import get_sysctl @@ -84,13 +84,39 @@ class DarwinHardware(Hardware): return cpu_facts def get_memory_facts(self): - memory_facts = {} + memory_facts = { + 'memtotal_mb': int(self.sysctl['hw.memsize']) // 1024 // 1024 + } - memory_facts['memtotal_mb'] = int(self.sysctl['hw.memsize']) // 1024 // 1024 - - rc, out, err = self.module.run_command("sysctl hw.usermem") + total_used = 0 + page_size = 4096 + vm_stat_command = get_bin_path('vm_stat') + rc, out, err = self.module.run_command(vm_stat_command) if rc == 0: - memory_facts['memfree_mb'] = int(out.splitlines()[-1].split()[1]) // 1024 // 1024 + # Free = Total - (Wired + active + inactive) + # Get a generator of tuples from the command output so we can later + # turn it into a dictionary + memory_stats = (line.rstrip('.').split(':', 1) for line in out.splitlines()) + + # Strip extra left spaces from the value + memory_stats = dict((k, v.lstrip()) for k, v in memory_stats) + + for k, v in memory_stats.items(): + try: + memory_stats[k] = int(v) + except ValueError as ve: + # Most values convert cleanly to integer values but if the field does + # not convert to an integer, just leave it alone. + pass + + if memory_stats.get('Pages wired down'): + total_used += memory_stats['Pages wired down'] * page_size + if memory_stats.get('Pages active'): + total_used += memory_stats['Pages active'] * page_size + if memory_stats.get('Pages inactive'): + total_used += memory_stats['Pages inactive'] * page_size + + memory_facts['memfree_mb'] = memory_facts['memtotal_mb'] - (total_used // 1024 // 1024) return memory_facts