mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-13 03:51:10 +00:00
500 lines
23 KiB
Python
500 lines
23 KiB
Python
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
from tools.tests.test_client_zram_focus import SCRIPT, function_source, run_bash
|
|
|
|
|
|
class MemoryPressureTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.directory.cleanup)
|
|
self.root = Path(self.directory.name)
|
|
self.memory()
|
|
|
|
def memory(self, available=1024, swap_free=4096, total=7168, swap_total=6144, psi='0.00'):
|
|
(self.root / 'meminfo').write_text(
|
|
f'MemTotal: {total * 1024} kB\nMemAvailable: {available * 1024} kB\n'
|
|
f'SwapTotal: {swap_total * 1024} kB\nSwapFree: {swap_free * 1024} kB\n')
|
|
(self.root / 'pressure').write_text(f'some avg10={psi} avg60=0.00 avg300=0.00 total=1234\n')
|
|
|
|
def run_policy(self, body):
|
|
functions = '\n'.join(function_source(name) for name in (
|
|
'bl_memory_read', 'bl_memory_tight', 'bl_memory_critical', 'bl_client_can_pageout',
|
|
'bl_client_can_readback', 'bl_client_pressure', 'bl_client_pressure_result', 'bl_zram_has_room'))
|
|
functions = functions.replace('/proc/meminfo', str(self.root / 'meminfo'))
|
|
functions = functions.replace('/proc/pressure/memory', str(self.root / 'pressure'))
|
|
harness = '''
|
|
set -u
|
|
_bl_pids=(123) _bl_game=1 _bl_packed=-1 _bl_pressure_at=-1 _bl_pressure_backoff=30
|
|
rss=$((384 * 1024)) client_swap=$((512 * 1024)) reclaimed=$((128 * 1024))
|
|
step() { :; }
|
|
bl_zram_usage() { _bl_rss=$rss _bl_anon=${anon:-$rss} _bl_swap=$client_swap; }
|
|
bl_zram_send() { echo "send:$1:$3"; rss=$((rss - reclaimed)); _bl_pageout_complete=${pageout_complete:-1}; }
|
|
''' + functions + '\nbl_memory_read\n' + body
|
|
result = run_bash('NOW=0\n' + harness.replace('SECONDS', 'NOW'))
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertEqual('', result.stderr)
|
|
return result.stdout.splitlines()
|
|
|
|
def test_pressure_reclaims_before_the_focus_delay_and_respects_cooldown(self):
|
|
self.assertEqual(['send:1:client', 'send:1:client'], self.run_policy('''
|
|
SECONDS=0; bl_client_pressure
|
|
SECONDS=5; bl_client_pressure
|
|
SECONDS=29; bl_client_pressure
|
|
SECONDS=30; bl_client_pressure
|
|
'''))
|
|
|
|
def test_no_game_or_disabled_client_compression_does_not_reclaim(self):
|
|
self.assertEqual([], self.run_policy('''
|
|
_bl_game=0; bl_client_pressure
|
|
_bl_game=1 BL_ZRAM_CLIENT=0; bl_client_pressure
|
|
'''))
|
|
|
|
def test_high_available_memory_does_not_reclaim_even_with_stalls(self):
|
|
self.memory(available=4096, psi='12.34')
|
|
self.assertEqual([], self.run_policy('bl_client_pressure'))
|
|
|
|
def test_psi_reclaims_with_moderate_headroom(self):
|
|
self.memory(available=2048, psi='01.23')
|
|
self.assertEqual(['send:1:client'], self.run_policy('bl_client_pressure'))
|
|
|
|
def test_missing_psi_still_uses_memory_headroom(self):
|
|
(self.root / 'pressure').unlink()
|
|
self.assertEqual(['send:1:client'], self.run_policy('bl_client_pressure'))
|
|
|
|
def test_missing_meminfo_never_reclaims_or_prefetches(self):
|
|
(self.root / 'meminfo').write_text('SwapTotal: 6291456 kB\nSwapFree: 4194304 kB\n')
|
|
self.assertEqual(['no'], self.run_policy('''
|
|
bl_client_pressure
|
|
if bl_client_can_readback; then echo yes; else echo no; fi
|
|
'''))
|
|
|
|
def test_missing_available_memory_does_not_trigger_pressure(self):
|
|
p = self.root / 'meminfo'
|
|
p.write_text('\n'.join(line for line in p.read_text().splitlines() if not line.startswith('MemAvailable:')))
|
|
self.assertEqual([], self.run_policy('bl_client_pressure'))
|
|
|
|
def test_swap_reserve_includes_the_clients_resident_memory(self):
|
|
self.memory(swap_free=1536)
|
|
self.assertEqual(['no'], self.run_policy('''
|
|
bl_client_pressure
|
|
if bl_client_can_pageout; then echo yes; else echo no; fi
|
|
'''))
|
|
|
|
def test_client_swap_budget_excludes_file_backed_memory(self):
|
|
self.memory(swap_free=1536)
|
|
self.assertEqual(['send:1:client'], self.run_policy('''
|
|
rss=$((2048 * 1024)) anon=$((256 * 1024))
|
|
bl_client_pressure
|
|
'''))
|
|
|
|
def test_without_swap_automatic_pageout_is_disabled(self):
|
|
self.memory(swap_total=0, swap_free=0)
|
|
self.assertEqual([], self.run_policy('bl_client_pressure'))
|
|
|
|
def test_recent_periodic_pageout_prevents_an_immediate_pressure_repeat(self):
|
|
self.assertEqual([], self.run_policy('_bl_packed=$SECONDS; bl_client_pressure'))
|
|
|
|
def test_ineffective_reclaim_backs_off_and_success_resets_it(self):
|
|
self.assertEqual(['send:1:client', 'backoff:60', 'send:1:client', 'backoff:30'], self.run_policy('''
|
|
reclaimed=0
|
|
SECONDS=0; bl_client_pressure
|
|
echo "backoff:$_bl_pressure_backoff"
|
|
SECONDS=30; bl_client_pressure
|
|
reclaimed=$((128 * 1024))
|
|
SECONDS=60; bl_client_pressure
|
|
echo "backoff:$_bl_pressure_backoff"
|
|
'''))
|
|
|
|
def test_readback_requires_room_for_the_swapped_client_plus_headroom(self):
|
|
self.memory(available=2048)
|
|
self.assertEqual(['yes', 'no'], self.run_policy('''
|
|
if bl_client_can_readback; then echo yes; else echo no; fi
|
|
client_swap=$((1024 * 1024))
|
|
if bl_client_can_readback; then echo yes; else echo no; fi
|
|
'''))
|
|
|
|
def test_unfinished_pageout_does_not_increase_the_ineffective_reclaim_backoff(self):
|
|
self.assertEqual(['send:1:client', 'backoff:30'], self.run_policy('''
|
|
reclaimed=0 pageout_complete=0
|
|
SECONDS=0; bl_client_pressure
|
|
echo "backoff:$_bl_pressure_backoff"
|
|
'''))
|
|
|
|
def test_pageout_completion_requires_a_finished_ack_from_every_live_target(self):
|
|
proc = self.root / 'proc'
|
|
proc.mkdir()
|
|
for pid in (123, 456):
|
|
(proc / str(pid)).mkdir()
|
|
source = function_source('bl_zram_wait').replace('/proc/', str(proc) + '/')
|
|
source = source.replace('/tmp/.bl-zram.ack.', str(self.root / 'ack.'))
|
|
for pending in ('done', 'running', 'not_started'):
|
|
with self.subTest(pending=pending):
|
|
(self.root / 'ack.123').write_text('1')
|
|
if pending != 'not_started':
|
|
(self.root / 'ack.456').write_text('1' if pending == 'done' else '')
|
|
result = run_bash('nap() { SECONDS=$((SECONDS + 1)); }\n' + source +
|
|
'\nSECONDS=0; bl_zram_wait 123 456; echo "$_bl_pageout_complete"')
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertEqual('1' if pending == 'done' else '0', result.stdout.strip())
|
|
|
|
def test_pressure_prevents_prefetch_even_if_the_client_would_fit(self):
|
|
self.memory(available=2048, psi='2.00')
|
|
self.assertEqual(['no'], self.run_policy('if bl_client_can_readback; then echo yes; else echo no; fi'))
|
|
|
|
|
|
class SessionPressureTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.directory.cleanup)
|
|
self.root = Path(self.directory.name)
|
|
self.memory()
|
|
|
|
def memory(self, available=1024, swap_free=6144, total=7168, swap_total=8192, psi='0.00'):
|
|
(self.root / 'meminfo').write_text(
|
|
f'MemTotal: {total * 1024} kB\nMemAvailable: {available * 1024} kB\n'
|
|
f'SwapTotal: {swap_total * 1024} kB\nSwapFree: {swap_free * 1024} kB\n')
|
|
(self.root / 'pressure').write_text(f'some avg10={psi} avg60=0.00 avg300=0.00 total=1234\n')
|
|
|
|
def run_policy(self, body):
|
|
functions = '\n'.join(function_source(name) for name in (
|
|
'bl_memory_read', 'bl_memory_tight', 'bl_memory_critical',
|
|
'bl_session_pressure', 'bl_zram_has_room', 'bl_watch_nap', 'bl_zram_pick', 'bl_tier_note', 'bl_tier_readback'))
|
|
functions = functions.replace('/proc/meminfo', str(self.root / 'meminfo'))
|
|
functions = functions.replace('/proc/pressure/memory', str(self.root / 'pressure'))
|
|
harness = '''
|
|
set -u
|
|
_bl_game=1 _bl_tier_at=-1 _bl_tier_backoff=30 _bl_tier_pushed=0 _bl_tier_back=0 _bl_tier_targets=() _bl_tier_swaps=() _bl_tier_starts=() _bl_pids=(999)
|
|
_bl_pressure_at=-1 _bl_packed=-1
|
|
rss=$((1536 * 1024)) swap=0 pushed=$((1024 * 1024)) alive="999 321 654" started=100 complete=1
|
|
step() { :; }
|
|
bl_proc_start() { _bl_start=; case " $alive " in *" $1 "*) _bl_start=$started; return 0 ;; esac; return 1; }
|
|
nap() { echo "nap:$1"; }
|
|
bl_session_others() { _bl_session=(321 654); }
|
|
bl_zram_usage() { _bl_rss=$rss _bl_anon=${anon:-$rss} _bl_swap=$swap; }
|
|
bl_zram_send() { echo "send:$1:$3"; rss=$((rss - pushed)); swap=$((swap + pushed)); _bl_pageout_complete=$complete; }
|
|
''' + functions + '\nbl_memory_read\n' + body
|
|
result = run_bash('NOW=0\n' + harness.replace('SECONDS', 'NOW'))
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertEqual('', result.stderr)
|
|
return result.stdout.splitlines()
|
|
|
|
def test_low_headroom_compresses_the_game_session_at_most_every_30_seconds(self):
|
|
self.assertEqual(['send:1:session', 'send:1:session'], self.run_policy('''
|
|
SECONDS=0; bl_session_pressure
|
|
SECONDS=10; bl_session_pressure
|
|
SECONDS=29; bl_session_pressure
|
|
SECONDS=30; bl_session_pressure
|
|
'''))
|
|
|
|
def test_pressure_policy_is_independent_of_ram_size(self):
|
|
for total, expected in ((11264, ['send:1:session']), (12288, ['send:1:session']), (16384, ['send:1:session'])):
|
|
with self.subTest(total=total):
|
|
self.memory(total=total)
|
|
self.assertEqual(expected, self.run_policy('bl_session_pressure'))
|
|
|
|
def test_large_device_with_real_headroom_does_not_reclaim(self):
|
|
self.memory(total=16384, available=5120, psi='12.34')
|
|
self.assertEqual([], self.run_policy('bl_session_pressure'))
|
|
|
|
def test_large_device_with_psi_and_moderate_headroom_reclaims(self):
|
|
self.memory(total=16384, available=3072, psi='1.23')
|
|
self.assertEqual(['send:1:session'], self.run_policy('bl_session_pressure'))
|
|
|
|
def test_no_game_does_not_compress_the_session(self):
|
|
self.assertEqual([], self.run_policy('_bl_game=0; bl_session_pressure'))
|
|
|
|
def test_enough_available_memory_does_not_compress_even_with_stalls(self):
|
|
self.memory(available=3072, psi='5.00')
|
|
self.assertEqual([], self.run_policy('bl_session_pressure'))
|
|
|
|
def test_stalls_with_moderate_headroom_compress_before_the_hard_limit(self):
|
|
self.memory(available=2048, psi='1.50')
|
|
self.assertEqual(['send:1:session'], self.run_policy('bl_session_pressure'))
|
|
|
|
def test_moderate_headroom_without_stalls_waits(self):
|
|
self.memory(available=2048)
|
|
self.assertEqual([], self.run_policy('bl_session_pressure'))
|
|
|
|
def test_same_second_memory_read_preserves_the_pressure_baseline(self):
|
|
self.memory(available=2048)
|
|
meminfo = self.root / 'meminfo'
|
|
self.assertEqual(['1048576:524288:2097152'], self.run_policy(f'''
|
|
SECONDS=100
|
|
_bl_sample_at=100 _bl_sample_available=2097152 _bl_growth=0 _bl_fall=524288
|
|
sed -i 's/MemAvailable: 2097152 kB/MemAvailable: 1048576 kB/' {meminfo}
|
|
bl_memory_read
|
|
echo "$_bl_available:$_bl_fall:$_bl_sample_available"
|
|
'''))
|
|
|
|
def test_game_reading_back_most_of_a_pageout_skips_and_doubles_the_wait(self):
|
|
self.assertEqual(['send:1:session', 'backoff:60', 'send:1:session', 'backoff:60',
|
|
'send:1:session', 'backoff:30'], self.run_policy('''
|
|
pushed=$((512 * 1024))
|
|
SECONDS=0; bl_session_pressure
|
|
swap=$((swap / 4))
|
|
SECONDS=30; bl_session_pressure
|
|
echo "backoff:$_bl_tier_backoff"
|
|
SECONDS=60; bl_session_pressure
|
|
SECONDS=90; bl_session_pressure
|
|
echo "backoff:$_bl_tier_backoff"
|
|
SECONDS=150; bl_session_pressure
|
|
echo "backoff:$_bl_tier_backoff"
|
|
'''))
|
|
|
|
def test_repeated_read_back_keeps_doubling_up_to_five_minutes(self):
|
|
self.assertEqual(['backoff:300'], self.run_policy('''
|
|
_bl_tier_backoff=240 _bl_tier_at=0 _bl_tier_targets=(999) _bl_tier_swaps=($((1024 * 1024))) _bl_tier_starts=(100) _bl_tier_pushed=$((1024 * 1024)) swap=0
|
|
SECONDS=240; bl_session_pressure
|
|
echo "backoff:$_bl_tier_backoff"
|
|
'''))
|
|
|
|
def test_nearly_out_of_memory_ignores_the_backoff_but_keeps_30_seconds(self):
|
|
self.memory(available=512)
|
|
self.assertEqual(['send:1:session', 'send:1:session'], self.run_policy('''
|
|
_bl_tier_backoff=300
|
|
SECONDS=0; bl_session_pressure
|
|
SECONDS=20; bl_session_pressure
|
|
SECONDS=30; bl_session_pressure
|
|
'''))
|
|
|
|
def test_nearly_out_of_memory_still_backs_off_when_the_game_reads_its_pageout_back(self):
|
|
self.memory(available=512)
|
|
self.assertEqual(['send:1:session', 'backoff:60', 'send:1:session'], self.run_policy('''
|
|
SECONDS=0; bl_session_pressure
|
|
swap=0
|
|
SECONDS=30; bl_session_pressure
|
|
echo "backoff:$_bl_tier_backoff"
|
|
SECONDS=59; bl_session_pressure
|
|
SECONDS=60; bl_session_pressure
|
|
SECONDS=90; bl_session_pressure
|
|
'''))
|
|
|
|
def test_nearly_out_of_memory_retries_a_skipped_pageout_after_30_seconds(self):
|
|
self.memory(available=512)
|
|
self.assertEqual(['send:1:session', 'send:1:session'], self.run_policy('''
|
|
SECONDS=0; bl_session_pressure
|
|
swap=0
|
|
SECONDS=30; bl_session_pressure
|
|
_bl_tier_backoff=300
|
|
saved=$rss; rss=$((128 * 1024))
|
|
SECONDS=330; bl_session_pressure
|
|
rss=$saved
|
|
SECONDS=359; bl_session_pressure
|
|
SECONDS=360; bl_session_pressure
|
|
'''))
|
|
|
|
def test_small_swap_headroom_skips_the_pageout(self):
|
|
self.memory(swap_free=1536, swap_total=2048)
|
|
self.assertEqual([], self.run_policy('bl_session_pressure'))
|
|
|
|
def test_file_backed_resident_memory_does_not_consume_the_swap_budget(self):
|
|
self.memory(swap_free=3088, swap_total=6144)
|
|
self.assertEqual(['send:1:session'], self.run_policy('''
|
|
rss=$((4096 * 1024)) anon=$((1024 * 1024))
|
|
bl_session_pressure
|
|
'''))
|
|
|
|
def test_anonymous_memory_and_pending_client_still_need_the_full_swap_reserve(self):
|
|
self.memory(swap_free=3088, swap_total=6144)
|
|
self.assertEqual([], self.run_policy('''
|
|
rss=$((4096 * 1024)) anon=$((2048 * 1024))
|
|
bl_session_pressure
|
|
'''))
|
|
self.assertEqual([], self.run_policy('''
|
|
rss=$((4096 * 1024)) anon=$((1536 * 1024)) _bl_client_reserve=$((512 * 1024))
|
|
bl_session_pressure
|
|
'''))
|
|
|
|
def test_small_session_is_left_alone(self):
|
|
self.assertEqual([], self.run_policy('rss=$((128 * 1024)); bl_session_pressure'))
|
|
|
|
def test_watch_polls_every_second_near_the_limit_and_stops_when_critical(self):
|
|
self.memory(available=2560)
|
|
self.assertEqual(['nap:1', 'nap:1', 'nap:1', 'nap:1', 'nap:1'], self.run_policy('bl_watch_nap'))
|
|
self.memory(available=1024)
|
|
self.assertEqual(['nap:1'], self.run_policy('_bl_available=$((2560 * 1024)); bl_watch_nap'))
|
|
self.memory(available=4096)
|
|
self.assertEqual(['nap:5'], self.run_policy('bl_watch_nap'))
|
|
self.assertEqual(['nap:5'], self.run_policy('_bl_game=0; bl_watch_nap'))
|
|
|
|
def test_session_push_preserves_client_timestamps(self):
|
|
self.assertEqual(['send:1:session', 'pids:321 654', 'client:-1:-1'], self.run_policy('''
|
|
SECONDS=0; bl_session_pressure
|
|
bl_zram_pick session; echo "pids:${_pids[*]}"
|
|
echo "client:$_bl_pressure_at:$_bl_packed"
|
|
'''))
|
|
|
|
def test_launcher_compression_turned_off_keeps_the_launcher_out(self):
|
|
self.assertEqual(['pids:321 654', 'client:-1:-1'], self.run_policy('''
|
|
BL_ZRAM_CLIENT=0
|
|
SECONDS=0; bl_session_pressure
|
|
bl_zram_pick session; echo "pids:${_pids[*]}"
|
|
echo "client:$_bl_pressure_at:$_bl_packed"
|
|
''')[1:])
|
|
|
|
def test_low_ram_devices_compress_with_a_quarter_of_memory_left(self):
|
|
self.memory(available=1700)
|
|
self.assertEqual(['send:1:session'], self.run_policy('bl_session_pressure'))
|
|
self.memory(available=1900)
|
|
self.assertEqual([], self.run_policy('bl_session_pressure'))
|
|
|
|
|
|
def test_a_compressed_process_that_exits_is_not_counted_as_read_back(self):
|
|
self.assertEqual(['send:1:session', 'send:1:session', 'backoff:30'], self.run_policy('''
|
|
SECONDS=0; bl_session_pressure
|
|
alive=""; swap=0
|
|
SECONDS=30; bl_session_pressure
|
|
echo "backoff:$_bl_tier_backoff"
|
|
'''))
|
|
|
|
def test_a_process_that_starts_after_a_pageout_is_not_in_its_read_back(self):
|
|
self.assertEqual(['send:1:session', 'targets:321 654', 'send:1:session', 'backoff:30'], self.run_policy('''
|
|
SECONDS=0; bl_session_pressure
|
|
echo "targets:${_bl_tier_targets[*]}"
|
|
alive="999 1234"
|
|
SECONDS=30; bl_session_pressure
|
|
echo "backoff:$_bl_tier_backoff"
|
|
'''))
|
|
|
|
def test_an_incomplete_pageout_does_not_restart_the_launcher_wait(self):
|
|
self.assertEqual(['send:1:session', 'client:-1:-1'], self.run_policy('''
|
|
complete=0
|
|
SECONDS=0; bl_session_pressure
|
|
echo "client:$_bl_pressure_at:$_bl_packed"
|
|
'''))
|
|
|
|
|
|
def test_a_reused_process_id_is_not_counted_as_read_back(self):
|
|
self.assertEqual(['send:1:session', 'send:1:session', 'backoff:30'], self.run_policy('''
|
|
SECONDS=0; bl_session_pressure
|
|
started=200; swap=0
|
|
SECONDS=30; bl_session_pressure
|
|
echo "backoff:$_bl_tier_backoff"
|
|
'''))
|
|
|
|
def test_process_start_comes_from_stat_and_zombies_have_none(self):
|
|
stat = self.root / 'stat'
|
|
source = function_source('bl_proc_start').replace('/proc/$1/stat', str(stat))
|
|
fields = ' '.join(['0'] * 17)
|
|
for state, expected in (('S', ['ok:4242']), ('Z', ['gone:'])):
|
|
with self.subTest(state=state):
|
|
stat.write_text(f'77 (Wow (arm64).exe) {state} 1 {fields} 4242 99 0\n')
|
|
result = run_bash(source + '\nif bl_proc_start 77; then echo "ok:$_bl_start"; else echo "gone:$_bl_start"; fi')
|
|
self.assertEqual(expected, result.stdout.split())
|
|
stat.unlink()
|
|
result = run_bash(source + '\nbl_proc_start 77 || echo gone')
|
|
self.assertEqual(['gone'], result.stdout.split())
|
|
|
|
|
|
class ReclaimDispatchTest(unittest.TestCase):
|
|
def run_cycle(self, client=1, game=1, anonymous=384, complete=1, reclaimed=128, body=None, available=1024, fall=0, tick=0):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
(root / 'meminfo').write_text(
|
|
f'MemTotal: 7340032 kB\nMemAvailable: {available * 1024} kB\n'
|
|
'SwapTotal: 6291456 kB\nSwapFree: 4194304 kB\n')
|
|
(root / 'pressure').write_text('some avg10=0.00 avg60=0.00 avg300=0.00 total=0\n')
|
|
names = ('bl_memory_read', 'bl_memory_tight', 'bl_memory_critical',
|
|
'bl_zram_has_room', 'bl_client_can_pageout', 'bl_client_pressure',
|
|
'bl_client_pressure_result', 'bl_client_pressure_finish',
|
|
'bl_session_pressure', 'bl_zram_send', 'bl_zram_finish', 'bl_zram_pick')
|
|
source = '\n'.join(function_source(name) for name in names)
|
|
source = source.replace('/proc/meminfo', str(root / 'meminfo'))
|
|
source = source.replace('/proc/pressure/memory', str(root / 'pressure'))
|
|
source = source.replace('/tmp/.bl-zram', str(root / 'ctl'))
|
|
monitor = SCRIPT.read_text()
|
|
start = monitor.index(' _bl_preserved_growth=$_bl_growth', monitor.index('while kill -0 $$'))
|
|
stop = monitor.index(' if [ "$_bl_game" = 1 ] && bl_game_focused', start)
|
|
cycle = monitor[start:stop] if body is None else body
|
|
script = f'''
|
|
set -u
|
|
BL_ZRAM_CLIENT={client} _bl_game={game} _bl_pids=(123) _bl_others=(321)
|
|
_bl_pressure_at=-1 _bl_pressure_backoff=30 _bl_packed=-1
|
|
_bl_tier_at=-1 _bl_tier_backoff=30 _bl_tier_pushed=0
|
|
_bl_client_reserve=0 client_rss=393216 client_anon={anonymous * 1024}
|
|
step() {{ :; }}
|
|
bl_session_others() {{ _bl_session=(321); }}
|
|
bl_zram_scan() {{ _bl_pids=(456); }}
|
|
nap() {{ echo "nap:$*:at:$NOW"; }}
|
|
bl_zram_usage() {{
|
|
if [ "$1" = 123 ]; then
|
|
_bl_rss=$client_rss _bl_anon=$client_anon _bl_swap=0
|
|
else
|
|
_bl_rss=3145728 _bl_anon=1572864 _bl_swap=0
|
|
fi
|
|
}}
|
|
kill() {{ echo "signal:$*:at:$NOW"; NOW=$((NOW + {tick})); }}
|
|
bl_zram_wait() {{
|
|
echo "wait:$*:at:$NOW"
|
|
NOW=$((NOW + 12))
|
|
_bl_pageout_complete={complete}
|
|
[ "$1" != 123 ] || client_rss=$((client_rss - {reclaimed * 1024}))
|
|
}}
|
|
bl_tier_note() {{ :; }}
|
|
''' + source + f'''
|
|
bl_memory_read
|
|
_bl_fall={fall * 1024}
|
|
''' + cycle + '''
|
|
echo "client:$_bl_pressure_backoff:${_bl_client_reserve:-0}:${_bl_client_pending:-0}"
|
|
'''
|
|
result = run_bash('NOW=0\n' + script.replace('SECONDS', 'NOW'))
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertEqual('', result.stderr)
|
|
return result.stdout.splitlines()
|
|
|
|
def test_both_requests_are_sent_before_either_wait(self):
|
|
self.assertEqual(['signal:-URG 123:at:0', 'signal:-URG 321:at:0',
|
|
'wait:321:at:0', 'wait:123:at:12', 'client:30:0:0'], self.run_cycle())
|
|
|
|
def test_disabled_client_is_never_signalled_or_waited_for(self):
|
|
self.assertEqual(['signal:-URG 321:at:0', 'wait:321:at:0', 'client:30:0:0'],
|
|
self.run_cycle(client=0))
|
|
|
|
def test_no_game_requests_nothing(self):
|
|
self.assertEqual(['client:30:0:0'], self.run_cycle(game=0))
|
|
|
|
def test_a_client_with_no_anonymous_pages_still_finishes_its_request(self):
|
|
self.assertEqual(['signal:-URG 123:at:0', 'signal:-URG 321:at:0',
|
|
'wait:321:at:0', 'wait:123:at:12', 'client:30:0:0'],
|
|
self.run_cycle(anonymous=0))
|
|
|
|
def test_falling_headroom_survives_a_tick_between_requests(self):
|
|
self.assertEqual(['signal:-URG 123:at:0', 'signal:-URG 321:at:1',
|
|
'wait:321:at:2', 'wait:123:at:14', 'client:30:0:0'],
|
|
self.run_cycle(available=2304, fall=512, tick=1))
|
|
|
|
def test_deferred_ineffective_reclaim_still_doubles_the_backoff(self):
|
|
self.assertEqual('client:60:0:0', self.run_cycle(reclaimed=0)[-1])
|
|
|
|
def test_deferred_incomplete_reclaim_does_not_double_the_backoff(self):
|
|
self.assertEqual('client:30:0:0', self.run_cycle(reclaimed=0, complete=0)[-1])
|
|
|
|
def test_explicit_combined_send_keeps_its_synchronous_wait(self):
|
|
self.assertEqual(['signal:-URG 321 123:at:0', 'wait:321 123:at:0', 'client:30:0:0'],
|
|
self.run_cycle(body='bl_zram_send 1 cap game\n'))
|
|
self.assertEqual(['signal:-URG 321:at:0', 'wait:321:at:0', 'client:30:0:0'],
|
|
self.run_cycle(client=0, body='bl_zram_send 1 cap game\n'))
|
|
|
|
def test_readback_keeps_its_synchronous_delay(self):
|
|
self.assertEqual(['signal:-URG 123:at:0', 'nap:5:at:0', 'client:30:0:0'],
|
|
self.run_cycle(body='bl_zram_send 0 readback client\n'))
|
|
|
|
def test_usage_sums_anonymous_rss_and_falls_back_per_process(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
for pid, contents in ((123, 'VmRSS: 2000 kB\nRssAnon: 300 kB\nVmSwap: 100 kB\n'),
|
|
(321, 'VmRSS: 4000 kB\nVmSwap: 200 kB\n'),
|
|
(654, 'VmRSS: 1000 kB\nRssAnon: 0 kB\n')):
|
|
(root / str(pid)).mkdir()
|
|
(root / str(pid) / 'status').write_text(contents)
|
|
source = function_source('bl_zram_usage').replace('/proc/', str(root) + '/')
|
|
result = run_bash(source + '\nbl_zram_usage 123 321 654 > /dev/null; echo "$_bl_rss:$_bl_anon:$_bl_swap"')
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertEqual('7000:4300:300', result.stdout.strip())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|