1""" 2Macros relating to VM Pageout Scan. 3""" 4from core.cvalue import sizeof, value 5import json 6from memory import PrettyPrintDictionary 7from process import GetTaskSummary 8import re 9from scheduler import GetRecentTimestamp 10from typing import Iterable, Optional 11from xnu import header, lldb_command, kern, xnudebug_test 12 13 14class VmPageoutStats(Iterable): 15 def __init__(self, num_samples: Optional[int] = None): 16 self.num_samples = num_samples 17 self.stats = kern.globals.vm_pageout_stats 18 self.num_stats = sizeof(self.stats) // sizeof(self.stats[0]) 19 self.now = kern.globals.vm_pageout_stat_now 20 21 def __iter__(self): 22 self.samples_iterated = 0 23 self.index = self.now - 1 if self.now > 0 else self.num_stats - 1 24 return self 25 26 ## Iterate stats in reverse chronological order 27 def __next__(self): 28 if self.index == self.now: 29 raise StopIteration 30 31 if (self.num_samples is not None and 32 self.samples_iterated == self.num_samples): 33 raise StopIteration 34 35 self.samples_iterated += 1 36 37 if self.index == 0: 38 self.index = self.num_stats - 1 39 else: 40 self.index -= 1 41 42 return self 43 44 def page_counts(self) -> str: 45 stat = self.stats[self.index] 46 return (f'{stat.vm_page_active_count:12d} ' 47 f'{stat.vm_page_inactive_count:12d} ' 48 f'{stat.vm_page_speculative_count:12d} ' 49 f'{stat.vm_page_anonymous_count:12d} ' 50 f'{stat.vm_page_free_count:12d} ' 51 f'{stat.vm_page_wire_count:12d} ' 52 f'{stat.vm_page_compressor_count:12d} ' 53 f'{stat.vm_page_pages_compressed:12d} ' 54 f'{stat.vm_page_pageable_internal_count:12d} ' 55 f'{stat.vm_page_pageable_external_count:12d} ' 56 f'{stat.vm_page_realtime_count:12d} ' 57 f'{stat.vm_page_xpmapped_external_count:12d}') 58 59 def page_stats(self) -> str: 60 stat = self.stats[self.index] 61 return (f'{stat.considered:12d} ' 62 f'{stat.pages_grabbed:12d} ' 63 f'{stat.pages_freed:12d} ' 64 f'{stat.pages_compressed:12d} ' 65 f'{stat.pages_evicted:12d} ' 66 f'{stat.pages_purged:12d} ' 67 f'{stat.skipped_external:12d} ' 68 f'{stat.skipped_internal:12d} ' 69 f'{stat.freed_speculative:12d} ' 70 f'{stat.freed_internal:12d} ' 71 f'{stat.freed_external:12d} ' 72 f'{stat.freed_cleaned:12d} ' 73 f'{stat.freed_internal:12d} ' 74 f'{stat.freed_external:12d} ' 75 f'{stat.inactive_referenced:12d} ' 76 f'{stat.inactive_nolock:12d} ' 77 f'{stat.reactivation_limit_exceeded:12d} ' 78 f'{stat.throttled_internal_q:12d} ' 79 f'{stat.throttled_external_q:12d} ' 80 f'{stat.forcereclaimed_sharedcache:12d} ' 81 f'{stat.forcereclaimed_realtime:12d} ' 82 f'{stat.protected_sharedcache:12d} ' 83 f'{stat.protected_realtime:12d}') 84 85# Macro: showvmpagehistory 86 87@header(f"{'active':>12s} " 88 f"{'inactive':>12s} " 89 f"{'speculative':>12s} " 90 f"{'anonymous':>12s} " 91 f"{'free':>12s} " 92 f"{'wired':>12s} " 93 f"{'compressor':>12s} " 94 f"{'compressed':>12s} " 95 f"{'pageable_int':>12s} " 96 f"{'pageable_ext':>12s} " 97 f"{'realtime':>12s} " 98 f"{'xpmapped_ext':>12s}") 99@lldb_command('showvmpageouthistory', 'S:') 100def ShowVMPageoutHistory(cmd_args=None, cmd_options={}): 101 ''' 102 Dump a recent history of VM page dispostions in reverse chronological order. 103 104 usage: showvmpagehistory [-S samples] 105 106 -S n Show only `n` most recent samples (samples are collect at 1 Hz) 107 ''' 108 num_samples = int(cmd_options['-S']) if '-S' in cmd_options else None 109 110 print(ShowVMPageoutHistory.header) 111 for stat in VmPageoutStats(num_samples): 112 print(stat.page_counts()) 113 114@xnudebug_test('test_vmpageouthistory') 115def TestMemstats(kernel_target, config, lldb_obj, isConnected ): 116 """ Test the functionality of showvmpageouthistory command 117 returns 118 - False on failure 119 - True on success 120 """ 121 if not isConnected: 122 print("Target is not connected. Cannot test memstats") 123 return False 124 res = lldb.SBCommandReturnObject() 125 lldb_obj.debugger.GetCommandInterpreter().HandleCommand("showvmpageouthistory", res) 126 result = res.GetOutput() 127 if len(result.splitlines()) < 2: 128 print("Result has fewer than two lines") 129 return False 130 for line in result.splitlines(): 131 matches = re.findall(r'(\d+|[\w_]+)', line) 132 if len(matches) < 12: 133 print("Line has fewer than 12 elements!") 134 print(line) 135 return False 136 return True 137 138# EndMacro: showvmpageouthistory 139 140# Macro: showvmpageoutstats 141 142@header(f"{'considered':>12s} " 143 f"{'grabbed':>12s} " 144 f"{'freed':>12s} " 145 f"{'compressed':>12s} " 146 f"{'evicted':>12s} " 147 f"{'purged':>12s} " 148 f"{'skipped_ext':>12s} " 149 f"{'skipped_int':>12s} " 150 f"{'freed_spec':>12s} " 151 f"{'freed_int':>12s} " 152 f"{'freed_ext':>12s} " 153 f"{'cleaned':>12s} " 154 f"{'cleaned_ext':>12s} " 155 f"{'cleaned_int':>12s} " 156 f"{'inact_ref':>12s} " 157 f"{'inact_lck':>12s} " 158 f"{'react_lim':>12s} " 159 f"{'thrtl_int':>12s} " 160 f"{'thrtl_ext':>12s} " 161 f"{'forced_sc':>12s} " 162 f"{'forced_rt':>12s} " 163 f"{'prot_sc':>12s} " 164 f"{'prot_rt':>12s}") 165@lldb_command('showvmpageoutstats', 'S:') 166def ShowVMPageoutStats(cmd_args=None, cmd_options={}): 167 ''' 168 Dump a recent history of VM pageout statistics in reverse chronological order. 169 170 usage: showvmpageoutstats [-S samples] 171 172 -S n Show only `n` most recent samples (samples are collect at 1 Hz) 173 ''' 174 num_samples = int(cmd_options['-S']) if '-S' in cmd_options else None 175 print(ShowVMPageoutStats.header) 176 for stat in VmPageoutStats(num_samples): 177 print(stat.page_stats()) 178 179@xnudebug_test('test_vmpageoutstats') 180def TestMemstats(kernel_target, config, lldb_obj, isConnected ): 181 """ Test the functionality of showvmpageoutstats command 182 returns 183 - False on failure 184 - True on success 185 """ 186 if not isConnected: 187 print("Target is not connected. Cannot test showmvmpageoutstats") 188 return False 189 res = lldb.SBCommandReturnObject() 190 lldb_obj.debugger.GetCommandInterpreter().HandleCommand("showvmpageoutstats", res) 191 result = res.GetOutput() 192 if len(result.splitlines()) < 2: 193 print("Result has fewer than two lines") 194 return False 195 for line in result.splitlines(): 196 matches = re.findall(r'(\d|[\w_]+)', line) 197 if len(matches) < 23: 198 print("Line has fewer than 23 elements!") 199 print(line) 200 return False 201 return True 202 203# EndMacro: showvmpageoutstats 204 205