1209b8e3eSBram Moolenaar" Tests for memory usage. 2209b8e3eSBram Moolenaar 3*b0f94c1fSBram Moolenaarif !has('terminal') 4*b0f94c1fSBram Moolenaar throw 'Skipped, terminal feature missing' 5*b0f94c1fSBram Moolenaarendif 6*b0f94c1fSBram Moolenaarif has('gui_running') 7*b0f94c1fSBram Moolenaar throw 'Skipped, does not work in GUI' 8*b0f94c1fSBram Moolenaarendif 9*b0f94c1fSBram Moolenaarif $ASAN_OPTIONS !=# '' 10209b8e3eSBram Moolenaar " Skip tests on Travis CI ASAN build because it's difficult to estimate 11209b8e3eSBram Moolenaar " memory usage. 12*b0f94c1fSBram Moolenaar throw 'Skipped, does not work with ASAN' 13209b8e3eSBram Moolenaarendif 14209b8e3eSBram Moolenaar 15209b8e3eSBram Moolenaarsource shared.vim 16209b8e3eSBram Moolenaar 17209b8e3eSBram Moolenaarfunc s:pick_nr(str) abort 18209b8e3eSBram Moolenaar return substitute(a:str, '[^0-9]', '', 'g') * 1 19209b8e3eSBram Moolenaarendfunc 20209b8e3eSBram Moolenaar 21209b8e3eSBram Moolenaarif has('win32') 22209b8e3eSBram Moolenaar if !executable('wmic') 23*b0f94c1fSBram Moolenaar throw 'Skipped, wmic program missing' 24209b8e3eSBram Moolenaar endif 25209b8e3eSBram Moolenaar func s:memory_usage(pid) abort 26209b8e3eSBram Moolenaar let cmd = printf('wmic process where processid=%d get WorkingSetSize', a:pid) 27209b8e3eSBram Moolenaar return s:pick_nr(system(cmd)) / 1024 28209b8e3eSBram Moolenaar endfunc 29209b8e3eSBram Moolenaarelseif has('unix') 30209b8e3eSBram Moolenaar if !executable('ps') 31*b0f94c1fSBram Moolenaar throw 'Skipped, ps program missing' 32209b8e3eSBram Moolenaar endif 33209b8e3eSBram Moolenaar func s:memory_usage(pid) abort 34209b8e3eSBram Moolenaar return s:pick_nr(system('ps -o rss= -p ' . a:pid)) 35209b8e3eSBram Moolenaar endfunc 36209b8e3eSBram Moolenaarelse 37*b0f94c1fSBram Moolenaar throw 'Skipped, not win32 or unix' 38209b8e3eSBram Moolenaarendif 39209b8e3eSBram Moolenaar 40209b8e3eSBram Moolenaar" Wait for memory usage to level off. 41209b8e3eSBram Moolenaarfunc s:monitor_memory_usage(pid) abort 42209b8e3eSBram Moolenaar let proc = {} 43209b8e3eSBram Moolenaar let proc.pid = a:pid 44209b8e3eSBram Moolenaar let proc.hist = [] 45209b8e3eSBram Moolenaar let proc.max = 0 46209b8e3eSBram Moolenaar 47209b8e3eSBram Moolenaar func proc.op() abort 48209b8e3eSBram Moolenaar " Check the last 200ms. 49209b8e3eSBram Moolenaar let val = s:memory_usage(self.pid) 50f7e47af7SBram Moolenaar if self.max < val 51209b8e3eSBram Moolenaar let self.max = val 52209b8e3eSBram Moolenaar endif 53209b8e3eSBram Moolenaar call add(self.hist, val) 54209b8e3eSBram Moolenaar if len(self.hist) < 20 55209b8e3eSBram Moolenaar return 0 56209b8e3eSBram Moolenaar endif 57209b8e3eSBram Moolenaar let sample = remove(self.hist, 0) 58209b8e3eSBram Moolenaar return len(uniq([sample] + self.hist)) == 1 59209b8e3eSBram Moolenaar endfunc 60209b8e3eSBram Moolenaar 61209b8e3eSBram Moolenaar call WaitFor({-> proc.op()}, 10000) 62f7e47af7SBram Moolenaar return {'last': get(proc.hist, -1), 'max': proc.max} 63209b8e3eSBram Moolenaarendfunc 64209b8e3eSBram Moolenaar 65209b8e3eSBram Moolenaarlet s:term_vim = {} 66209b8e3eSBram Moolenaar 67209b8e3eSBram Moolenaarfunc s:term_vim.start(...) abort 68209b8e3eSBram Moolenaar let self.buf = term_start([GetVimProg()] + a:000) 69209b8e3eSBram Moolenaar let self.job = term_getjob(self.buf) 70209b8e3eSBram Moolenaar call WaitFor({-> job_status(self.job) ==# 'run'}) 71209b8e3eSBram Moolenaar let self.pid = job_info(self.job).process 72209b8e3eSBram Moolenaarendfunc 73209b8e3eSBram Moolenaar 74209b8e3eSBram Moolenaarfunc s:term_vim.stop() abort 75209b8e3eSBram Moolenaar call term_sendkeys(self.buf, ":qall!\<CR>") 76209b8e3eSBram Moolenaar call WaitFor({-> job_status(self.job) ==# 'dead'}) 77209b8e3eSBram Moolenaar exe self.buf . 'bwipe!' 78209b8e3eSBram Moolenaarendfunc 79209b8e3eSBram Moolenaar 80209b8e3eSBram Moolenaarfunc s:vim_new() abort 81209b8e3eSBram Moolenaar return copy(s:term_vim) 82209b8e3eSBram Moolenaarendfunc 83209b8e3eSBram Moolenaar 84209b8e3eSBram Moolenaarfunc Test_memory_func_capture_vargs() 85209b8e3eSBram Moolenaar " Case: if a local variable captures a:000, funccall object will be free 86209b8e3eSBram Moolenaar " just after it finishes. 87209b8e3eSBram Moolenaar let testfile = 'Xtest.vim' 88209b8e3eSBram Moolenaar call writefile([ 89209b8e3eSBram Moolenaar \ 'func s:f(...)', 90209b8e3eSBram Moolenaar \ ' let x = a:000', 91209b8e3eSBram Moolenaar \ 'endfunc', 92209b8e3eSBram Moolenaar \ 'for _ in range(10000)', 93209b8e3eSBram Moolenaar \ ' call s:f(0)', 94209b8e3eSBram Moolenaar \ 'endfor', 95209b8e3eSBram Moolenaar \ ], testfile) 96209b8e3eSBram Moolenaar 97209b8e3eSBram Moolenaar let vim = s:vim_new() 98209b8e3eSBram Moolenaar call vim.start('--clean', '-c', 'set noswapfile', testfile) 99209b8e3eSBram Moolenaar let before = s:monitor_memory_usage(vim.pid).last 100209b8e3eSBram Moolenaar 101209b8e3eSBram Moolenaar call term_sendkeys(vim.buf, ":so %\<CR>") 102209b8e3eSBram Moolenaar call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 103209b8e3eSBram Moolenaar let after = s:monitor_memory_usage(vim.pid) 104209b8e3eSBram Moolenaar 105209b8e3eSBram Moolenaar " Estimate the limit of max usage as 2x initial usage. 1065d508dd3SBram Moolenaar " The lower limit can fluctuate a bit, use 97%. 1075d508dd3SBram Moolenaar call assert_inrange(before * 97 / 100, 2 * before, after.max) 1083a731ee0SBram Moolenaar 1093a731ee0SBram Moolenaar " In this case, garbage collecting is not needed. 1105d508dd3SBram Moolenaar " The value might fluctuate a bit, allow for 3% tolerance below and 5% above. 1115d508dd3SBram Moolenaar " Based on various test runs. 112f7e47af7SBram Moolenaar let lower = after.last * 97 / 100 1135d508dd3SBram Moolenaar let upper = after.last * 105 / 100 114f7e47af7SBram Moolenaar call assert_inrange(lower, upper, after.max) 115209b8e3eSBram Moolenaar 116209b8e3eSBram Moolenaar call vim.stop() 117209b8e3eSBram Moolenaar call delete(testfile) 118209b8e3eSBram Moolenaarendfunc 119209b8e3eSBram Moolenaar 120209b8e3eSBram Moolenaarfunc Test_memory_func_capture_lvars() 121209b8e3eSBram Moolenaar " Case: if a local variable captures l: dict, funccall object will not be 122209b8e3eSBram Moolenaar " free until garbage collector runs, but after that memory usage doesn't 123209b8e3eSBram Moolenaar " increase so much even when rerun Xtest.vim since system memory caches. 124209b8e3eSBram Moolenaar let testfile = 'Xtest.vim' 125209b8e3eSBram Moolenaar call writefile([ 126209b8e3eSBram Moolenaar \ 'func s:f()', 127209b8e3eSBram Moolenaar \ ' let x = l:', 128209b8e3eSBram Moolenaar \ 'endfunc', 129209b8e3eSBram Moolenaar \ 'for _ in range(10000)', 130209b8e3eSBram Moolenaar \ ' call s:f()', 131209b8e3eSBram Moolenaar \ 'endfor', 132209b8e3eSBram Moolenaar \ ], 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 1516b6f7aaeSBram Moolenaar " otherwise the test fails sometimes. 15208cda65dSBram Moolenaar let lower = before * 8 / 10 1536b6f7aaeSBram Moolenaar let upper = (after.max + (after.last - before)) * 12 / 10 154f7e47af7SBram Moolenaar call assert_inrange(lower, upper, last) 155209b8e3eSBram Moolenaar 156209b8e3eSBram Moolenaar call vim.stop() 157209b8e3eSBram Moolenaar call delete(testfile) 158209b8e3eSBram Moolenaarendfunc 159