memory management tweaks

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/ec/trunk@38766 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-MacDonald 2015-07-08 09:06:26 +00:00
parent 71c4c118c4
commit 2439001507
3 changed files with 353 additions and 224 deletions

View file

@ -1,3 +1,11 @@
2015-07-08 Richard Frith-Macdonald <rfm@gnu.org>
* EcProcess.h:
* EcProcess.m:
Allow the memory monitoring configuration to be set from the Console
via the 'memory' command. Improve the help text. Improve the info
provided in the debug/error reports.
2015-07-06 Richard Frith-Macdonald <rfm@gnu.org>
* EcProcess.h:

View file

@ -269,34 +269,41 @@ extern NSString* cmdVersion(NSString *ver);
* <desc>
* This boolean value determines whether statistics on creation
* and destruction of objects are maintained.<br />
* This may be set on the command line or in Control.plist, but
* may be overridden by using the 'Memory' command in the
* This may be set in the NSUserDefaults system or in Control.plist,
* but may be overridden by using the 'memory' command in the
* Console program.
* </desc>
* <term>EcMemoryAllowed</term>
* <desc>
* This may be used to specify the total process memory usage
* (in megabytes) before memory usage alerting may begin.<br />
* If this is not specified (or a negative or excessive value
* Memory usage 'error' reports are then generated every time
* the average (over ten minutes) memory usage exceeds a warning
* threshold (the threshold is then increased).<br />
* If this setting is not specified (or a negative or excessive value
* is specified) then memory is monitored for ten minutes and
* the threshold is set at the peak during that period plus a
* margin to allow further memory growth before warning.<br />
* The margin is at least 50KB on a test system, 5000KB on
* a non-test system.
* The minimum margin is determined by the EcMemoryIncrement and
* EcMemoryPercentage settings.<br />
* This may be set in the NSUserDefaults system or in Control.plist,
* but may be overridden by using the 'memory' command in the
* Console program.
* </desc>
* <term>EcMemoryIncrement</term>
* <desc>
* This integer value controls the (KBytes) increment (from
* current peak value) in process memory usage after which
* an alert is generated.<br />
* If this is not set (or is set to a value less than ten or
* greater than a million) then a value of five thousand is used
* unless EcMemory is set (in which case fifty is used).<br />
* If this is not set (or is set to a value less than 10KB or
* greater than 1000000KB) then a value of 5000KB is used.<br />
* Setting a higher value makes memory leak detection less
* sensitive (but reduces unnecessary alerts).<br />
* If used in conjunction with EcMemoryPercentage, the greater
* of the two allowed memory values is used.<br />
* This may be set on the command line or in Control.plist
* This may be set in the NSUserDefaults system or in Control.plist,
* but may be overridden by using the 'memory' command in the
* Console program.
* </desc>
* <term>EcMemoryMaximum</term>
* <desc>
@ -306,7 +313,10 @@ extern NSString* cmdVersion(NSString *ver);
* If the total memory usage of the process reaches this threshold,
* the -cmdQuit: method will be called with an argument of -1.<br />
* If this is not specified (or a negative value is specified)
* the process will never shut down due to excessive memory usage.
* the process will never shut down due to excessive memory usage.<br />
* This may be set in the NSUserDefaults system or in Control.plist,
* but may be overridden by using the 'memory' command in the
* Console program.
* </desc>
* <term>EcMemoryPercentage</term>
* <desc>
@ -314,14 +324,15 @@ extern NSString* cmdVersion(NSString *ver);
* threshold after which a memory usage alert is generated.<br />
* The increase is calculated as a percentage of the current
* peak memory usage value when an alert is generated.<br />
* If this is not set (or is set to a value less than one or
* greater than a thousand) then a value of ten is used unless
* EcMemory is set (in which case a value of one is used).<br />
* If this is not set (or is set to a value less than 1 or
* greater than 1000) then a value of 10 is used.<br />
* Setting a higher value make memory leak detection less
* sensitive (but reduces unnecessary alerts).<br />
* If used in conjunction with EcMemoryIncrement, the greater
* of the two allowed memory values is used.<br />
* This may be set on the command line or in Control.plist
* This may be set in the NSUserDefaults system or in Control.plist,
* but may be overridden by using the 'memory' command in the
* Console program.
* </desc>
* <term>EcRelease</term>
* <desc>
@ -330,8 +341,8 @@ extern NSString* cmdVersion(NSString *ver);
* this on has a big impact on program performance and is not
* recommended except for debugging crashes and other memory
* issues.<br />
* This may be set on the command line or in Control.plist, but
* may be overridden by using the 'release' command in the
* This may be set in the NSUserDefaults system or in Control.plist,
* but may be overridden by using the 'release' command in the
* Console program.
* </desc>
* <term>EcTesting</term>

View file

@ -1004,6 +1004,7 @@ findMode(NSDictionary* d, NSString* s)
@interface EcProcess (Private)
- (void) cmdMesgrelease: (NSArray*)msg;
- (void) cmdMesgtesting: (NSArray*)msg;
- (void) _memCheck;
- (NSString*) _moveLog: (NSString*)name to: (NSString*)sub;
- (void) _timedOut: (NSTimer*)timer;
- (void) _update: (NSMutableDictionary*)info;
@ -1261,16 +1262,20 @@ static NSString *noFiles = @"No log files to archive";
#endif
memAllowed = (uint64_t)[cmdDefs integerForKey: @"MemoryAllowed"];
#if SIZEOF_VOIDP == 4
if (memAllowed > 200000)
{
memAllowed = 0;
}
#endif
memMaximum = (uint64_t)[cmdDefs integerForKey: @"MemoryMaximum"];
#if SIZEOF_VOIDP == 4
if (memMaximum > 200000)
{
memMaximum = 0; // Disabled
}
#endif
str = [cmdDefs stringForKey: @"CoreSize"];
if (nil == str)
@ -2226,10 +2231,6 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval);
- (void) ecNewMinute: (NSCalendarDate*)when
{
BOOL memDebug = [cmdDefs boolForKey: @"Memory"];
FILE *fptr;
int i;
#ifndef __MINGW__
if (NO == cmdIsQuitting)
{
@ -2284,156 +2285,7 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval);
*/
[NSHost flushHostCache];
/* /proc/pid/statm reports the process memory size in 4KB pages
*/
fptr = fopen([[NSString stringWithFormat: @"/proc/%d/statm",
[[NSProcessInfo processInfo] processIdentifier]] UTF8String], "r");
memLast = 1;
if (NULL != fptr)
{
if (fscanf(fptr, "%"PRIu64, &memLast) != 1)
{
memLast = 1;
}
else
{
memLast *= (4 * 1024);
if (memLast <= 0) memLast = 1;
}
fclose(fptr);
}
/* Do initial population so we can work immediately.
*/
if (0 == memSlot)
{
for (i = 1; i < MEMCOUNT; i++)
{
memRoll[i] = memLast;
}
memPrev = memStrt = memLast;
ASSIGN(memTime, [NSDate date]);
}
memRoll[memSlot % MEMCOUNT] = memLast;
memSlot++;
/* Find the average usage over the last set of samples.
* Round up to a block size.
*/
memAvge = 0;
for (i = 0; i < MEMCOUNT; i++)
{
memAvge += memRoll[i];
}
memAvge /= MEMCOUNT;
/* Convert to 1KB blocks.
*/
if (memAvge % 1024)
{
memAvge = ((memAvge / 1024) + 1) * 1024;
}
/* Update peak memory usage if necessary.
*/
if (memLast > memPeak)
{
memPeak = memLast;
}
/* If we have a defined maximum memory usage for the process,
* we should shut down with a non-zero status to get a restart.
*/
if (memMaximum > 0 && memPeak > (memMaximum * 1024 * 1024))
{
if (NO == cmdIsQuitting)
{
cmdIsQuitting = YES;
[self cmdQuit: -1];
}
return;
}
/* If the average memory usage is above the warning threshold,
* we should alert and reset the threshold.
* During the first ten minutes
*/
if (memAvge > memWarn || memSlot < MEMCOUNT)
{
NSInteger inc;
NSInteger pct;
NSInteger iMax = 0;
NSInteger pMax = 0;
/* We increase the threshold for the next alert by a percentage
* of the existing usage or by a fixed increment, whichever is
* the larger.
*/
pct = [cmdDefs integerForKey: @"MemoryPercentage"];
if (pct < 1 || pct > 1000) pct = 0;
inc = [cmdDefs integerForKey: @"MemoryIncrement"];
if (inc < 10 || inc > 1000000) inc = 0;
if (0 == inc && 0 == pct)
{
if (YES == memDebug)
{
/* We want detailed memory information, so we set the next
* alerting threshold 50 KB above the current peak usage.
*/
inc = 50;
pct = 0;
}
else
{
/* We do not want detailed memory information,
* so we set the next alerting threshold from
* 5000 KB above the current peak usage,
* ensuring that only serious increases
* in usage will generate an alert.
*/
inc = 5000;
pct = 10; // Use ten percent if more than fixed increment
}
}
if (inc > 0)
{
iMax = memPeak + (inc * 1024);
}
if (pct > 0)
{
pMax = (memPeak * (100 + pct)) / 100;
}
memWarn = (iMax > pMax) ? iMax : pMax;
if (memWarn % 1024)
{
memWarn = (memWarn/1024 + 1) * 1024;
}
/* If not in the initial period, we need to generate an alert
* because the average has risen above the allowed size.
*/
if (memSlot >= MEMCOUNT)
{
uint64_t prev;
NSDate *when;
prev = memPrev;
when = AUTORELEASE(memTime);
memPrev = memAvge;
memTime = [NSDate new];
[self cmdError: @"Average memory usage grown from %"
PRIu64"KB to %"PRIu64"KB since %@",
prev/1024, memAvge/1024, when];
}
}
if (YES == memDebug)
{
[self cmdDbg: cmdDetailDbg
msg: @"Memory usage %"PRIu64"KB", memLast/1024];
}
return;
[self _memCheck];
}
- (void) ecHadIP: (NSDate*)when
@ -3237,39 +3089,47 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval);
- (void) cmdMesgmemory: (NSArray*)msg
{
if ([msg count] == 0
|| [[msg objectAtIndex: 0] caseInsensitiveCompare: @"help"]
== NSOrderedSame)
if ([msg count] == 0)
{
[self cmdPrintf: @"controls recording of memory management statistics"];
}
else
{
if ([[msg objectAtIndex: 0] caseInsensitiveCompare: @"help"]
== NSOrderedSame)
== NSOrderedSame || ([msg count] > 1
&& [[msg objectAtIndex: 1] caseInsensitiveCompare: @"help"]
== NSOrderedSame))
{
[self cmdPrintf: @"\n"];
[self cmdPrintf: @"Without parameters, the memory command is "];
[self cmdPrintf: @"used to list the changes in the numbers of "];
[self cmdPrintf: @"objects allocated since the command was "];
[self cmdPrintf: @"last issued.\n"];
[self cmdPrintf: @"With the single parameter 'all', the memory "];
[self cmdPrintf: @"command is used to list the cumulative totals "];
[self cmdPrintf: @"of objects allocated since the first time a "];
[self cmdPrintf: @"memory command was issued.\n"];
[self cmdPrintf: @"With the single parameter 'yes', the memory "];
[self cmdPrintf: @"command is used to turn on gathering of "];
[self cmdPrintf: @"memory usage statistics.\n"];
[self cmdPrintf: @"With the single parameter 'no', the memory "];
[self cmdPrintf: @"command is used to turn off gathering of "];
[self cmdPrintf: @"memory usage statistics.\n"];
[self cmdPrintf: @"With the single parameter 'default', the "];
[self cmdPrintf: @"gathering of memory usage statistics reverts "];
[self cmdPrintf: @"to the default setting.\n"];
[self cmdPrintf: @"With two parameters ('class' and a class name), "];
[self cmdPrintf: @"new instances of the class are recorded.\n"];
[self cmdPrintf: @"With two parameters ('list' and a class), "];
[self cmdPrintf: @"recorded instances of the class are reported.\n"];
[self cmdPrintf: @"\n\
Without parameters,\n\
the memory command is used to list the changes in the numbers of objects\n\
allocated since the command was last issued.\n\
With the single parameter 'all',\n\
the memory command is used to list the cumulative totals of objects\n\
allocated since the first time a memory command was issued.\n\
With the single parameter 'yes',\n\
the memory command is used to turn on gathering of memory usage statistics.\n\
With the single parameter 'no',\n\
the memory command is used to turn off gathering of memory usage statistics.\n\
With the single parameter 'default',\n\
the gathering of memory usage statistics reverts to the default setting.\n\
With two parameters ('class' and a class name),\n\
new instances of the class are recorded.\n\
With two parameters ('list' and a class),\n\
recorded instances of the class are reported.\n\
With two parameters ('allowed' and a number),\n\
the threshold for warnings about process size is set (in MB).\n\
Set to 'default' to revert to the default.\n\
With two parameters ('increment' and a number),\n\
the size increment between warnings about process size is set (in KB\n\
from 10 to 1000000). Set to 'default' to revert to the default.\n\
With two parameters ('percentage' and a number),\n\
the percentage increment between warnings about process memory size is\n\
set (from 1 to 1000). Set to 'default' to revert to the default.\n\
With two parameters ('maximum' and a number),\n\
the maximum process size (in MB) is set. On reaching the limit, the\n\
process restarts unless the limit is zero (meaning no maximum).\n\
Set to 'default' to revert to the default."];
[self cmdPrintf: @"\n"];
}
else if ([msg count] == 2)
@ -3327,40 +3187,118 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval);
}
else if ([msg count] == 3)
{
NSString *name = [msg objectAtIndex: 2];
Class c = NSClassFromString(name);
if (Nil == c)
NSString *op = [msg objectAtIndex: 1];
NSString *arg = [msg objectAtIndex: 2];
NSInteger val = [arg integerValue];
if ([op caseInsensitiveCompare: @"allowed"] == NSOrderedSame)
{
[self cmdPrintf: @"Unable to find class '%@'.\n", name];
}
else
{
NSString *op = [msg objectAtIndex: 1];
if ([op caseInsensitiveCompare: @"class"] == NSOrderedSame)
if (val <= 0)
{
GSDebugAllocationActiveRecordingObjects(c);
[self cmdPrintf: @"Recording instances of '%@'.\n", name];
}
else if ([op caseInsensitiveCompare: @"list"] == NSOrderedSame)
{
NSArray *array;
NSUInteger count;
NSUInteger index;
array = GSDebugAllocationListRecordedObjects(c);
[self cmdPrintf: @"Current instances of '%@':\n", name];
count = [array count];
for (index = 0; index < count; index++)
[cmdDefs setCommand: nil forKey: @"MemoryAllowed"];
if (0 == memAllowed)
{
[self cmdPrintf: @"%6lu %@\n",
(unsigned long)index, [array objectAtIndex: index]];
/* The threshold was set back to zero ... to be
* calculated from a ten minute baseline.
*/
memSlot = 0;
}
[self cmdPrintf: @"MemoryAllowed using default value.\n"];
}
else
{
arg = [NSString stringWithFormat: @"%"PRIu64, (uint64_t)val];
[cmdDefs setCommand: arg forKey: @"MemoryAllowed"];
[self cmdPrintf: @"MemoryAllowed set to %@MB.\n", arg];
}
memWarn = memAllowed * 1024 * 1024;
DESTROY(memTime);
[self _memCheck];
}
else if ([op caseInsensitiveCompare: @"increment"] == NSOrderedSame)
{
if (val <= 10 || val > 1000000)
{
[cmdDefs setCommand: nil forKey: @"MemoryIncrement"];
[self cmdPrintf: @"MemoryIncrement using default value.\n"];
}
else
{
arg = [NSString stringWithFormat: @"%"PRIu64, (uint64_t)val];
[cmdDefs setCommand: arg forKey: @"MemoryIncrement"];
[self cmdPrintf: @"MemoryIncrement set to %@KB.\n", arg];
}
}
else if ([op caseInsensitiveCompare: @"percentage"] == NSOrderedSame)
{
if (val <= 0 || val > 1000)
{
[cmdDefs setCommand: nil forKey: @"MemoryPercentage"];
[self cmdPrintf: @"MemoryPercentage using default value.\n"];
}
else
{
arg = [NSString stringWithFormat: @"%"PRIu64, (uint64_t)val];
[cmdDefs setCommand: arg forKey: @"MemoryPercentage"];
[self cmdPrintf: @"MemoryPercentage set to %@.\n", arg];
}
}
else if ([op caseInsensitiveCompare: @"maximum"] == NSOrderedSame)
{
if (val <= 0)
{
if ([arg caseInsensitiveCompare: @"default"] == NSOrderedSame)
{
[cmdDefs setCommand: nil forKey: @"MemoryMaximum"];
[self cmdPrintf: @"MemoryMaximum using default value.\n"];
}
else
{
[cmdDefs setCommand: @"0" forKey: @"MemoryMaximum"];
[self cmdPrintf: @"MemoryMaximum restart turned off.\n"];
}
}
else
{
[self cmdPrintf: @"Unknown memory command '%@'.\n", op];
arg = [NSString stringWithFormat: @"%"PRIu64, (uint64_t)val];
[cmdDefs setCommand: arg forKey: @"MemoryMaximum"];
[self cmdPrintf: @"MemoryMaximum set to %@MB.\n", arg];
}
}
else
{
Class c = NSClassFromString(arg);
if (Nil == c)
{
[self cmdPrintf: @"Unable to find class '%@'.\n", arg];
}
else
{
if ([op caseInsensitiveCompare: @"class"] == NSOrderedSame)
{
GSDebugAllocationActiveRecordingObjects(c);
[self cmdPrintf: @"Recording instances of '%@'.\n", arg];
}
else if ([op caseInsensitiveCompare: @"list"] == NSOrderedSame)
{
NSArray *array;
NSUInteger count;
NSUInteger index;
array = GSDebugAllocationListRecordedObjects(c);
[self cmdPrintf: @"Current instances of '%@':\n", arg];
count = [array count];
for (index = 0; index < count; index++)
{
[self cmdPrintf: @"%6lu %@\n",
(unsigned long)index, [array objectAtIndex: index]];
}
}
else
{
[self cmdPrintf: @"Unknown memory command '%@'.\n", op];
}
}
}
}
@ -3420,7 +3358,7 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval);
[self cmdPrintf: @" %"PRIu64"KB (average),"
@" %"PRIu64"KB (start)\n",
memAvge/1024, memStrt/1024];
[self cmdPrintf: @" %"PRIu64"KB (exempt)\n",
[self cmdPrintf: @" %"PRIu64"KB (reserved)\n",
[self ecNotLeaked]/1024];
[self cmdPrintf:
@"Memory error reporting after average usage: %"PRIu64"KB\n",
@ -4306,6 +4244,178 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval);
}
}
- (void) _memCheck
{
BOOL memDebug = [cmdDefs boolForKey: @"Memory"];
FILE *fptr;
int i;
/* /proc/pid/statm reports the process memory size in 4KB pages
*/
fptr = fopen([[NSString stringWithFormat: @"/proc/%d/statm",
[[NSProcessInfo processInfo] processIdentifier]] UTF8String], "r");
memLast = 1;
if (NULL != fptr)
{
if (fscanf(fptr, "%"PRIu64, &memLast) != 1)
{
memLast = 1;
}
else
{
memLast *= (4 * 1024);
if (memLast <= 0) memLast = 1;
}
fclose(fptr);
}
/* Do initial population so we can work immediately.
*/
if (0 == memSlot)
{
for (i = 1; i < MEMCOUNT; i++)
{
memRoll[i] = memLast;
}
memPrev = memStrt = memLast;
}
memRoll[memSlot % MEMCOUNT] = memLast;
memSlot++;
/* Find the average usage over the last set of samples.
* Round up to a block size.
*/
memAvge = 0;
for (i = 0; i < MEMCOUNT; i++)
{
memAvge += memRoll[i];
}
memAvge /= MEMCOUNT;
/* Convert to 1KB blocks.
*/
if (memAvge % 1024)
{
memAvge = ((memAvge / 1024) + 1) * 1024;
}
/* Update peak memory usage if necessary.
*/
if (memLast > memPeak)
{
memPeak = memLast;
}
/* If we have a defined maximum memory usage for the process,
* we should shut down with a non-zero status to get a restart.
*/
if (memMaximum > 0 && memPeak > (memMaximum * 1024 * 1024))
{
if (NO == cmdIsQuitting)
{
cmdIsQuitting = YES;
[self cmdQuit: -1];
}
return;
}
/* If the average memory usage is above the threshold, we alert and reset
* the threshold.
* During the first ten minutes though, we always adjust the threshold and
* we suppress any warnings. This gives us a more stable starting point.
*/
if (memAvge > memWarn || memSlot < MEMCOUNT)
{
NSInteger inc;
NSInteger pct;
NSInteger iMax = 0;
NSInteger pMax = 0;
/* We increase the threshold for the next alert by a percentage
* of the existing usage or by a fixed increment, whichever is
* the larger.
*/
pct = [cmdDefs integerForKey: @"MemoryPercentage"];
if (pct < 1 || pct > 1000) pct = 0;
inc = [cmdDefs integerForKey: @"MemoryIncrement"];
if (inc < 10 || inc > 1000000) inc = 0;
if (0 == inc && 0 == pct)
{
if (YES == memDebug)
{
/* We want detailed memory information, so we set the next
* alerting threshold 50 KB above the current peak usage.
*/
inc = 50;
pct = 0;
}
else
{
/* We do not want detailed memory information,
* so we set the next alerting threshold from
* 5000 KB above the current peak usage,
* ensuring that only serious increases
* in usage will generate an alert.
*/
inc = 5000;
pct = 10; // Use ten percent if more than fixed increment
}
}
if (inc > 0)
{
iMax = memPeak + (inc * 1024);
}
if (pct > 0)
{
pMax = (memPeak * (100 + pct)) / 100;
}
memWarn = (iMax > pMax) ? iMax : pMax;
if (memWarn % 1024)
{
memWarn = (memWarn/1024 + 1) * 1024;
}
if (memWarn < memAllowed * 1024 * 1024)
{
/* Never warn at less than the allowed memory.
*/
memWarn = memAllowed * 1024 * 1024;
}
/* If not in the initial period, we need to generate an alert
* because the average has risen above the allowed size.
*/
if (memSlot >= MEMCOUNT)
{
uint64_t prev;
NSDate *when;
prev = memPrev;
when = AUTORELEASE(memTime);
memPrev = memAvge;
memTime = [NSDate new];
if (nil == when)
{
[self cmdError: @"Average memory usage grown from %"
PRIu64"KB to %"PRIu64"KB (reserved: %"PRIu64"KB)",
prev/1024, memAvge/1024, [self ecNotLeaked]/1024];
}
else
{
[self cmdError: @"Average memory usage grown from %"
PRIu64"KB to %"PRIu64"KB (reserved: %"PRIu64"KB) since %@",
prev/1024, memAvge/1024, [self ecNotLeaked]/1024, when];
}
}
}
if (YES == memDebug)
{
[self cmdDbg: cmdDetailDbg
msg: @"Memory usage %"PRIu64"KB (reserved: %"PRIu64"KB)",
memLast/1024, [self ecNotLeaked]/1024];
}
}
- (NSString*) _moveLog: (NSString*)name to: (NSString*)sub
{
NSString *status;