1209b8e3eSBram Moolenaar" Tests for memory usage.
2209b8e3eSBram Moolenaar
3b46fecd3SBram Moolenaarsource check.vim
4b46fecd3SBram MoolenaarCheckFeature terminal
58c5a278fSBram MoolenaarCheckNotGui
6b46fecd3SBram Moolenaar
797202d95SBram Moolenaar" Skip tests on Travis CI ASAN build because it's difficult to estimate memory
897202d95SBram Moolenaar" usage.
997202d95SBram MoolenaarCheckNotAsan
10209b8e3eSBram Moolenaar
11209b8e3eSBram Moolenaarsource shared.vim
12209b8e3eSBram Moolenaar
13209b8e3eSBram Moolenaarfunc s:pick_nr(str) abort
14209b8e3eSBram Moolenaar  return substitute(a:str, '[^0-9]', '', 'g') * 1
15209b8e3eSBram Moolenaarendfunc
16209b8e3eSBram Moolenaar
17209b8e3eSBram Moolenaarif has('win32')
18209b8e3eSBram Moolenaar  if !executable('wmic')
19b46fecd3SBram Moolenaar    throw 'Skipped: wmic program missing'
20209b8e3eSBram Moolenaar  endif
21209b8e3eSBram Moolenaar  func s:memory_usage(pid) abort
22209b8e3eSBram Moolenaar    let cmd = printf('wmic process where processid=%d get WorkingSetSize', a:pid)
23209b8e3eSBram Moolenaar    return s:pick_nr(system(cmd)) / 1024
24209b8e3eSBram Moolenaar  endfunc
25209b8e3eSBram Moolenaarelseif has('unix')
26209b8e3eSBram Moolenaar  if !executable('ps')
27b46fecd3SBram Moolenaar    throw 'Skipped: ps program missing'
28209b8e3eSBram Moolenaar  endif
29209b8e3eSBram Moolenaar  func s:memory_usage(pid) abort
30209b8e3eSBram Moolenaar    return s:pick_nr(system('ps -o rss= -p ' . a:pid))
31209b8e3eSBram Moolenaar  endfunc
32209b8e3eSBram Moolenaarelse
33b46fecd3SBram Moolenaar  throw 'Skipped: not win32 or unix'
34209b8e3eSBram Moolenaarendif
35209b8e3eSBram Moolenaar
36209b8e3eSBram Moolenaar" Wait for memory usage to level off.
37209b8e3eSBram Moolenaarfunc s:monitor_memory_usage(pid) abort
38209b8e3eSBram Moolenaar  let proc = {}
39209b8e3eSBram Moolenaar  let proc.pid = a:pid
40209b8e3eSBram Moolenaar  let proc.hist = []
41209b8e3eSBram Moolenaar  let proc.max = 0
42209b8e3eSBram Moolenaar
43209b8e3eSBram Moolenaar  func proc.op() abort
44209b8e3eSBram Moolenaar    " Check the last 200ms.
45209b8e3eSBram Moolenaar    let val = s:memory_usage(self.pid)
46f7e47af7SBram Moolenaar    if self.max < val
47209b8e3eSBram Moolenaar      let self.max = val
48209b8e3eSBram Moolenaar    endif
49209b8e3eSBram Moolenaar    call add(self.hist, val)
50209b8e3eSBram Moolenaar    if len(self.hist) < 20
51209b8e3eSBram Moolenaar      return 0
52209b8e3eSBram Moolenaar    endif
53209b8e3eSBram Moolenaar    let sample = remove(self.hist, 0)
54209b8e3eSBram Moolenaar    return len(uniq([sample] + self.hist)) == 1
55209b8e3eSBram Moolenaar  endfunc
56209b8e3eSBram Moolenaar
57209b8e3eSBram Moolenaar  call WaitFor({-> proc.op()}, 10000)
58f7e47af7SBram Moolenaar  return {'last': get(proc.hist, -1), 'max': proc.max}
59209b8e3eSBram Moolenaarendfunc
60209b8e3eSBram Moolenaar
61209b8e3eSBram Moolenaarlet s:term_vim = {}
62209b8e3eSBram Moolenaar
63209b8e3eSBram Moolenaarfunc s:term_vim.start(...) abort
64209b8e3eSBram Moolenaar  let self.buf = term_start([GetVimProg()] + a:000)
65209b8e3eSBram Moolenaar  let self.job = term_getjob(self.buf)
66209b8e3eSBram Moolenaar  call WaitFor({-> job_status(self.job) ==# 'run'})
67209b8e3eSBram Moolenaar  let self.pid = job_info(self.job).process
68209b8e3eSBram Moolenaarendfunc
69209b8e3eSBram Moolenaar
70209b8e3eSBram Moolenaarfunc s:term_vim.stop() abort
71209b8e3eSBram Moolenaar  call term_sendkeys(self.buf, ":qall!\<CR>")
72209b8e3eSBram Moolenaar  call WaitFor({-> job_status(self.job) ==# 'dead'})
73209b8e3eSBram Moolenaar  exe self.buf . 'bwipe!'
74209b8e3eSBram Moolenaarendfunc
75209b8e3eSBram Moolenaar
76209b8e3eSBram Moolenaarfunc s:vim_new() abort
77209b8e3eSBram Moolenaar  return copy(s:term_vim)
78209b8e3eSBram Moolenaarendfunc
79209b8e3eSBram Moolenaar
80209b8e3eSBram Moolenaarfunc Test_memory_func_capture_vargs()
81209b8e3eSBram Moolenaar  " Case: if a local variable captures a:000, funccall object will be free
82209b8e3eSBram Moolenaar  " just after it finishes.
83209b8e3eSBram Moolenaar  let testfile = 'Xtest.vim'
84e7eb9270SBram Moolenaar  let lines =<< trim END
85e7eb9270SBram Moolenaar        func s:f(...)
86e7eb9270SBram Moolenaar          let x = a:000
87e7eb9270SBram Moolenaar        endfunc
88e7eb9270SBram Moolenaar        for _ in range(10000)
89e7eb9270SBram Moolenaar          call s:f(0)
90e7eb9270SBram Moolenaar        endfor
91e7eb9270SBram Moolenaar  END
92e7eb9270SBram Moolenaar  call writefile(lines, testfile)
93209b8e3eSBram Moolenaar
94209b8e3eSBram Moolenaar  let vim = s:vim_new()
95209b8e3eSBram Moolenaar  call vim.start('--clean', '-c', 'set noswapfile', testfile)
96209b8e3eSBram Moolenaar  let before = s:monitor_memory_usage(vim.pid).last
97209b8e3eSBram Moolenaar
98209b8e3eSBram Moolenaar  call term_sendkeys(vim.buf, ":so %\<CR>")
99209b8e3eSBram Moolenaar  call WaitFor({-> term_getcursor(vim.buf)[0] == 1})
100209b8e3eSBram Moolenaar  let after = s:monitor_memory_usage(vim.pid)
101209b8e3eSBram Moolenaar
102209b8e3eSBram Moolenaar  " Estimate the limit of max usage as 2x initial usage.
1035d508dd3SBram Moolenaar  " The lower limit can fluctuate a bit, use 97%.
1045d508dd3SBram Moolenaar  call assert_inrange(before * 97 / 100, 2 * before, after.max)
1053a731ee0SBram Moolenaar
1063a731ee0SBram Moolenaar  " In this case, garbage collecting is not needed.
1075d508dd3SBram Moolenaar  " The value might fluctuate a bit, allow for 3% tolerance below and 5% above.
1085d508dd3SBram Moolenaar  " Based on various test runs.
109f7e47af7SBram Moolenaar  let lower = after.last * 97 / 100
1105d508dd3SBram Moolenaar  let upper = after.last * 105 / 100
111f7e47af7SBram Moolenaar  call assert_inrange(lower, upper, after.max)
112209b8e3eSBram Moolenaar
113209b8e3eSBram Moolenaar  call vim.stop()
114209b8e3eSBram Moolenaar  call delete(testfile)
115209b8e3eSBram Moolenaarendfunc
116209b8e3eSBram Moolenaar
117209b8e3eSBram Moolenaarfunc Test_memory_func_capture_lvars()
118209b8e3eSBram Moolenaar  " Case: if a local variable captures l: dict, funccall object will not be
119209b8e3eSBram Moolenaar  " free until garbage collector runs, but after that memory usage doesn't
120209b8e3eSBram Moolenaar  " increase so much even when rerun Xtest.vim since system memory caches.
121209b8e3eSBram Moolenaar  let testfile = 'Xtest.vim'
122e7eb9270SBram Moolenaar  let lines =<< trim END
123e7eb9270SBram Moolenaar        func s:f()
124e7eb9270SBram Moolenaar          let x = l:
125e7eb9270SBram Moolenaar        endfunc
126e7eb9270SBram Moolenaar        for _ in range(10000)
127e7eb9270SBram Moolenaar          call s:f()
128e7eb9270SBram Moolenaar        endfor
129e7eb9270SBram Moolenaar  END
130e7eb9270SBram Moolenaar  call writefile(lines, testfile)
131209b8e3eSBram Moolenaar
132209b8e3eSBram Moolenaar  let vim = s:vim_new()
133209b8e3eSBram Moolenaar  call vim.start('--clean', '-c', 'set noswapfile', testfile)
134209b8e3eSBram Moolenaar  let before = s:monitor_memory_usage(vim.pid).last
135209b8e3eSBram Moolenaar
136209b8e3eSBram Moolenaar  call term_sendkeys(vim.buf, ":so %\<CR>")
137209b8e3eSBram Moolenaar  call WaitFor({-> term_getcursor(vim.buf)[0] == 1})
138209b8e3eSBram Moolenaar  let after = s:monitor_memory_usage(vim.pid)
139209b8e3eSBram Moolenaar
140209b8e3eSBram Moolenaar  " Rerun Xtest.vim.
141209b8e3eSBram Moolenaar  for _ in range(3)
142209b8e3eSBram Moolenaar    call term_sendkeys(vim.buf, ":so %\<CR>")
143209b8e3eSBram Moolenaar    call WaitFor({-> term_getcursor(vim.buf)[0] == 1})
144209b8e3eSBram Moolenaar    let last = s:monitor_memory_usage(vim.pid).last
145209b8e3eSBram Moolenaar  endfor
146209b8e3eSBram Moolenaar
147f7e47af7SBram Moolenaar  " The usage may be a bit less than the last value, use 80%.
1486b6f7aaeSBram Moolenaar  " Allow for 20% tolerance at the upper limit.  That's very permissive, but
1491832d12aSBram Moolenaar  " otherwise the test fails sometimes.  On Cirrus CI with FreeBSD we need to
150*6bce5856SBram Moolenaar  " be even much more permissive.
1511832d12aSBram Moolenaar  if has('bsd')
152*6bce5856SBram Moolenaar    let multiplier = 19
1531832d12aSBram Moolenaar  else
1541832d12aSBram Moolenaar    let multiplier = 12
1551832d12aSBram Moolenaar  endif
15608cda65dSBram Moolenaar  let lower = before * 8 / 10
1571832d12aSBram Moolenaar  let upper = (after.max + (after.last - before)) * multiplier / 10
158f7e47af7SBram Moolenaar  call assert_inrange(lower, upper, last)
159209b8e3eSBram Moolenaar
160209b8e3eSBram Moolenaar  call vim.stop()
161209b8e3eSBram Moolenaar  call delete(testfile)
162209b8e3eSBram Moolenaarendfunc
1636d91bcb4SBram Moolenaar
1646d91bcb4SBram Moolenaar" vim: shiftwidth=2 sts=2 expandtab
165