1209b8e3eSBram Moolenaar" Tests for memory usage. 2209b8e3eSBram Moolenaar 3b46fecd3SBram Moolenaarsource check.vim 4b46fecd3SBram MoolenaarCheckFeature terminal 58c5a278fSBram MoolenaarCheckNotGui 6b46fecd3SBram Moolenaar 7bffc5049SBram Moolenaarif execute('version') =~# '-fsanitize=[a-z,]*\<address\>' 8209b8e3eSBram Moolenaar " Skip tests on Travis CI ASAN build because it's difficult to estimate 9209b8e3eSBram Moolenaar " memory usage. 10b46fecd3SBram Moolenaar throw 'Skipped: does not work with ASAN' 11209b8e3eSBram Moolenaarendif 12209b8e3eSBram Moolenaar 13209b8e3eSBram Moolenaarsource shared.vim 14209b8e3eSBram Moolenaar 15209b8e3eSBram Moolenaarfunc s:pick_nr(str) abort 16209b8e3eSBram Moolenaar return substitute(a:str, '[^0-9]', '', 'g') * 1 17209b8e3eSBram Moolenaarendfunc 18209b8e3eSBram Moolenaar 19209b8e3eSBram Moolenaarif has('win32') 20209b8e3eSBram Moolenaar if !executable('wmic') 21b46fecd3SBram Moolenaar throw 'Skipped: wmic program missing' 22209b8e3eSBram Moolenaar endif 23209b8e3eSBram Moolenaar func s:memory_usage(pid) abort 24209b8e3eSBram Moolenaar let cmd = printf('wmic process where processid=%d get WorkingSetSize', a:pid) 25209b8e3eSBram Moolenaar return s:pick_nr(system(cmd)) / 1024 26209b8e3eSBram Moolenaar endfunc 27209b8e3eSBram Moolenaarelseif has('unix') 28209b8e3eSBram Moolenaar if !executable('ps') 29b46fecd3SBram Moolenaar throw 'Skipped: ps program missing' 30209b8e3eSBram Moolenaar endif 31209b8e3eSBram Moolenaar func s:memory_usage(pid) abort 32209b8e3eSBram Moolenaar return s:pick_nr(system('ps -o rss= -p ' . a:pid)) 33209b8e3eSBram Moolenaar endfunc 34209b8e3eSBram Moolenaarelse 35b46fecd3SBram Moolenaar throw 'Skipped: not win32 or unix' 36209b8e3eSBram Moolenaarendif 37209b8e3eSBram Moolenaar 38209b8e3eSBram Moolenaar" Wait for memory usage to level off. 39209b8e3eSBram Moolenaarfunc s:monitor_memory_usage(pid) abort 40209b8e3eSBram Moolenaar let proc = {} 41209b8e3eSBram Moolenaar let proc.pid = a:pid 42209b8e3eSBram Moolenaar let proc.hist = [] 43209b8e3eSBram Moolenaar let proc.max = 0 44209b8e3eSBram Moolenaar 45209b8e3eSBram Moolenaar func proc.op() abort 46209b8e3eSBram Moolenaar " Check the last 200ms. 47209b8e3eSBram Moolenaar let val = s:memory_usage(self.pid) 48f7e47af7SBram Moolenaar if self.max < val 49209b8e3eSBram Moolenaar let self.max = val 50209b8e3eSBram Moolenaar endif 51209b8e3eSBram Moolenaar call add(self.hist, val) 52209b8e3eSBram Moolenaar if len(self.hist) < 20 53209b8e3eSBram Moolenaar return 0 54209b8e3eSBram Moolenaar endif 55209b8e3eSBram Moolenaar let sample = remove(self.hist, 0) 56209b8e3eSBram Moolenaar return len(uniq([sample] + self.hist)) == 1 57209b8e3eSBram Moolenaar endfunc 58209b8e3eSBram Moolenaar 59209b8e3eSBram Moolenaar call WaitFor({-> proc.op()}, 10000) 60f7e47af7SBram Moolenaar return {'last': get(proc.hist, -1), 'max': proc.max} 61209b8e3eSBram Moolenaarendfunc 62209b8e3eSBram Moolenaar 63209b8e3eSBram Moolenaarlet s:term_vim = {} 64209b8e3eSBram Moolenaar 65209b8e3eSBram Moolenaarfunc s:term_vim.start(...) abort 66209b8e3eSBram Moolenaar let self.buf = term_start([GetVimProg()] + a:000) 67209b8e3eSBram Moolenaar let self.job = term_getjob(self.buf) 68209b8e3eSBram Moolenaar call WaitFor({-> job_status(self.job) ==# 'run'}) 69209b8e3eSBram Moolenaar let self.pid = job_info(self.job).process 70209b8e3eSBram Moolenaarendfunc 71209b8e3eSBram Moolenaar 72209b8e3eSBram Moolenaarfunc s:term_vim.stop() abort 73209b8e3eSBram Moolenaar call term_sendkeys(self.buf, ":qall!\<CR>") 74209b8e3eSBram Moolenaar call WaitFor({-> job_status(self.job) ==# 'dead'}) 75209b8e3eSBram Moolenaar exe self.buf . 'bwipe!' 76209b8e3eSBram Moolenaarendfunc 77209b8e3eSBram Moolenaar 78209b8e3eSBram Moolenaarfunc s:vim_new() abort 79209b8e3eSBram Moolenaar return copy(s:term_vim) 80209b8e3eSBram Moolenaarendfunc 81209b8e3eSBram Moolenaar 82209b8e3eSBram Moolenaarfunc Test_memory_func_capture_vargs() 83209b8e3eSBram Moolenaar " Case: if a local variable captures a:000, funccall object will be free 84209b8e3eSBram Moolenaar " just after it finishes. 85209b8e3eSBram Moolenaar let testfile = 'Xtest.vim' 86e7eb9270SBram Moolenaar let lines =<< trim END 87e7eb9270SBram Moolenaar func s:f(...) 88e7eb9270SBram Moolenaar let x = a:000 89e7eb9270SBram Moolenaar endfunc 90e7eb9270SBram Moolenaar for _ in range(10000) 91e7eb9270SBram Moolenaar call s:f(0) 92e7eb9270SBram Moolenaar endfor 93e7eb9270SBram Moolenaar END 94e7eb9270SBram Moolenaar call writefile(lines, testfile) 95209b8e3eSBram Moolenaar 96209b8e3eSBram Moolenaar let vim = s:vim_new() 97209b8e3eSBram Moolenaar call vim.start('--clean', '-c', 'set noswapfile', testfile) 98209b8e3eSBram Moolenaar let before = s:monitor_memory_usage(vim.pid).last 99209b8e3eSBram Moolenaar 100209b8e3eSBram Moolenaar call term_sendkeys(vim.buf, ":so %\<CR>") 101209b8e3eSBram Moolenaar call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 102209b8e3eSBram Moolenaar let after = s:monitor_memory_usage(vim.pid) 103209b8e3eSBram Moolenaar 104209b8e3eSBram Moolenaar " Estimate the limit of max usage as 2x initial usage. 1055d508dd3SBram Moolenaar " The lower limit can fluctuate a bit, use 97%. 1065d508dd3SBram Moolenaar call assert_inrange(before * 97 / 100, 2 * before, after.max) 1073a731ee0SBram Moolenaar 1083a731ee0SBram Moolenaar " In this case, garbage collecting is not needed. 1095d508dd3SBram Moolenaar " The value might fluctuate a bit, allow for 3% tolerance below and 5% above. 1105d508dd3SBram Moolenaar " Based on various test runs. 111f7e47af7SBram Moolenaar let lower = after.last * 97 / 100 1125d508dd3SBram Moolenaar let upper = after.last * 105 / 100 113f7e47af7SBram Moolenaar call assert_inrange(lower, upper, after.max) 114209b8e3eSBram Moolenaar 115209b8e3eSBram Moolenaar call vim.stop() 116209b8e3eSBram Moolenaar call delete(testfile) 117209b8e3eSBram Moolenaarendfunc 118209b8e3eSBram Moolenaar 119209b8e3eSBram Moolenaarfunc Test_memory_func_capture_lvars() 120209b8e3eSBram Moolenaar " Case: if a local variable captures l: dict, funccall object will not be 121209b8e3eSBram Moolenaar " free until garbage collector runs, but after that memory usage doesn't 122209b8e3eSBram Moolenaar " increase so much even when rerun Xtest.vim since system memory caches. 123209b8e3eSBram Moolenaar let testfile = 'Xtest.vim' 124e7eb9270SBram Moolenaar let lines =<< trim END 125e7eb9270SBram Moolenaar func s:f() 126e7eb9270SBram Moolenaar let x = l: 127e7eb9270SBram Moolenaar endfunc 128e7eb9270SBram Moolenaar for _ in range(10000) 129e7eb9270SBram Moolenaar call s:f() 130e7eb9270SBram Moolenaar endfor 131e7eb9270SBram Moolenaar END 132e7eb9270SBram Moolenaar call writefile(lines, testfile) 133209b8e3eSBram Moolenaar 134209b8e3eSBram Moolenaar let vim = s:vim_new() 135209b8e3eSBram Moolenaar call vim.start('--clean', '-c', 'set noswapfile', testfile) 136209b8e3eSBram Moolenaar let before = s:monitor_memory_usage(vim.pid).last 137209b8e3eSBram Moolenaar 138209b8e3eSBram Moolenaar call term_sendkeys(vim.buf, ":so %\<CR>") 139209b8e3eSBram Moolenaar call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 140209b8e3eSBram Moolenaar let after = s:monitor_memory_usage(vim.pid) 141209b8e3eSBram Moolenaar 142209b8e3eSBram Moolenaar " Rerun Xtest.vim. 143209b8e3eSBram Moolenaar for _ in range(3) 144209b8e3eSBram Moolenaar call term_sendkeys(vim.buf, ":so %\<CR>") 145209b8e3eSBram Moolenaar call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 146209b8e3eSBram Moolenaar let last = s:monitor_memory_usage(vim.pid).last 147209b8e3eSBram Moolenaar endfor 148209b8e3eSBram Moolenaar 149f7e47af7SBram Moolenaar " The usage may be a bit less than the last value, use 80%. 1506b6f7aaeSBram Moolenaar " Allow for 20% tolerance at the upper limit. That's very permissive, but 1511832d12aSBram Moolenaar " otherwise the test fails sometimes. On Cirrus CI with FreeBSD we need to 1521832d12aSBram Moolenaar " be even more permissive. 1531832d12aSBram Moolenaar if has('bsd') 154*bb062c15SBram Moolenaar let multiplier = 15 1551832d12aSBram Moolenaar else 1561832d12aSBram Moolenaar let multiplier = 12 1571832d12aSBram Moolenaar endif 15808cda65dSBram Moolenaar let lower = before * 8 / 10 1591832d12aSBram Moolenaar let upper = (after.max + (after.last - before)) * multiplier / 10 160f7e47af7SBram Moolenaar call assert_inrange(lower, upper, last) 161209b8e3eSBram Moolenaar 162209b8e3eSBram Moolenaar call vim.stop() 163209b8e3eSBram Moolenaar call delete(testfile) 164209b8e3eSBram Moolenaarendfunc 165