.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "Test::MemoryGrowth 3pm" .TH Test::MemoryGrowth 3pm "2022-12-13" "perl v5.36.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" "Test::MemoryGrowth" \- assert that code does not cause growth in memory usage .SH "SYNOPSIS" .IX Header "SYNOPSIS" .Vb 2 \& use Test::More; \& use Test::MemoryGrowth; \& \& use Some::Class; \& \& no_growth { \& my $obj = Some::Class\->new; \& } \*(AqConstructing Some::Class does not grow memory\*(Aq; \& \& my $obj = Some::Class\->new; \& no_growth { \& $obj\->do_thing; \& } \*(AqSome::Class\->do_thing does not grow memory\*(Aq; \& \& \& #### This test will fail #### \& my @list; \& no_growth { \& push @list, "Hello world"; \& } \*(Aqpushing to an array does not grow memory\*(Aq; \& \& done_testing; .Ve .SH "DESCRIPTION" .IX Header "DESCRIPTION" This module provides a function to check that a given block of code does not result in the process consuming extra memory once it has finished. Despite the name of this module it does not, in the strictest sense of the word, test for a memory leak: that term is specifically applied to cases where memory has been allocated but all record of it has been lost, so it cannot possibly be reclaimed. While the method employed by this module can detect such bugs, it can also detect cases where memory is still referenced and reachable, but the usage has grown more than would be expected or necessary. .PP The block of code will be run a large number of times (by default 10,000), and the difference in memory usage by the process before and after is compared. If the memory usage has now increased by more than one byte per call, then the test fails. .PP In order to give the code a chance to load initial resources it needs, it will be run a few times first (by default 10); giving it a chance to load files, AUTOLOADs, caches, or any other information that it requires. Any extra memory usage here will not count against it. .PP This simple method is not a guaranteed indicator of the absence of memory resource bugs from a piece of code; it has the possibility to fail in both a false-negative and a false-positive way. .IP "False Negative" 4 .IX Item "False Negative" It is possible that a piece of code causes memory usage growth that this module does not detect. Because it only detects memory growth of at least one byte per call, it cannot detect cases of linear memory growth at lower rates than this. Most memory usage growth comes either from Perl-level or C\-level bugs where memory objects are created at every call and not reclaimed again. (These are either genuine memory leaks, or needless allocations of objects that are stored somewhere and never reclaimed). It is unlikely such a bug would result in a growth rate smaller than one byte per call. .Sp A second failure case comes from the fact that memory usage is taken from the Operating System's measure of the process's Virtual Memory size, so as to be able to detect memory usage growth in C libraries or XS-level wrapping code, as well as Perl functions. Because Perl does not aggressively return unused memory to the Operating System, it is possible that a piece of code could use un-allocated but un-reclaimed memory to grow into; resulting in an increase in its requirements despite not requesting extra memory from the Operating System. .IP "False Positive" 4 .IX Item "False Positive" It is possible that the test will claim that a function grows in memory, when the behaviour is in fact perfectly normal for the code in question. For example, the code could simply be some function whose behaviour is required to store extra state; for example, adding a new item into a list. In this case it is in fact expected that the memory usage of the process will increase. .PP By careful use of this test module, false indications can be minimised. By splitting tests across many test scripts, each one can be started in a new process state, where most of the memory assigned from the Operating System is in use by Perl, so anything extra that the code requires will have to request more. This should reduce the false negative indications. .PP By keeping in mind that the module simply measures the change in allocated memory size, false positives can be minimised, by not attempting to assert that certain pieces of code do not grow in memory, when in fact it would be expected that they do. .SS "Devel::Gladiator Integration" .IX Subsection "Devel::Gladiator Integration" \&\fISince version 0.04.\fR .PP If Devel::Gladiator is installed, this test module will use it as a second potential source of detecting memory growth. A walk of the Perl memory heap is taken before running the code, in order to count the number of every kind of object present. This is then compared to a second count taken afterwards. Any object types that have increased by at least one per call are reported. .PP For example, the output might contain the following extra lines of diagnostic output: .PP .Vb 7 \& # Growths in arena object counts: \& # ARRAY 1735 \-> 11735 (1.00 per call) \& # HASH 459 \-> 10459 (1.00 per call) \& # REF 1387 \-> 21387 (2.00 per call) \& # REF\-ARRAY 163 \-> 10163 (1.00 per call) \& # REF\-HASH 66 \-> 10066 (1.00 per call) \& # WithContainerSlots 10 \-> 10010 (1.00 per call) .Ve .SS "Devel::MAT Integration" .IX Subsection "Devel::MAT Integration" If Devel::MAT is installed, this test module will use it to dump the state of the memory after a failure. It will create a \fI.pmat\fR file named the same as the unit test, but with the trailing \fI.t\fR suffix replaced with \&\fI\-TEST.pmat\fR where \f(CW\*(C`TEST\*(C'\fR is the number of the test that failed (in case there was more than one). It will then run the code under test one more time, before writing another file whose name is suffixed with \fI\-TEST\-after.pmat\fR. This pair of files may be useful for differential analysis. .SH "FUNCTIONS" .IX Header "FUNCTIONS" .SS "no_growth" .IX Subsection "no_growth" .Vb 1 \& no_growth { CODE } %opts, $name .Ve .PP Assert that the code block does not consume extra memory. .PP Takes the following named arguments: .IP "calls => \s-1INT\s0" 8 .IX Item "calls => INT" The number of times to call the code during growth testing. .IP "burn_in => \s-1INT\s0" 8 .IX Item "burn_in => INT" The number of times to call the code initially, before watching for memory usage. .SH "TODO" .IX Header "TODO" .IP "\(bu" 8 Don't be Linux Specific .Sp Currently, this module uses a very Linux-specific method of determining process memory usage (namely, by inspecting \fI/proc/self/status\fR). This should really be fixed to some OS-neutral abstraction. Currently I am unaware of a simple portable mechanism to query this. Patches very much welcome. :) .SH "AUTHOR" .IX Header "AUTHOR" Paul Evans