forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			994 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			994 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 | |
|  *
 | |
|  * ***** BEGIN LICENSE BLOCK *****
 | |
|  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 | |
|  *
 | |
|  * The contents of this file are subject to the Mozilla Public License Version
 | |
|  * 1.1 (the "License"); you may not use this file except in compliance with
 | |
|  * the License. You may obtain a copy of the License at
 | |
|  * http://www.mozilla.org/MPL/
 | |
|  *
 | |
|  * Software distributed under the License is distributed on an "AS IS" basis,
 | |
|  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 | |
|  * for the specific language governing rights and limitations under the
 | |
|  * License.
 | |
|  *
 | |
|  * The Original Code is tmfrags.c code, released
 | |
|  * Oct 26, 2002.
 | |
|  *
 | |
|  * The Initial Developer of the Original Code is
 | |
|  * Netscape Communications Corporation.
 | |
|  * Portions created by the Initial Developer are Copyright (C) 2002
 | |
|  * the Initial Developer. All Rights Reserved.
 | |
|  *
 | |
|  * Contributor(s):
 | |
|  *   Garrett Arch Blythe, 26-October-2002
 | |
|  *
 | |
|  * Alternatively, the contents of this file may be used under the terms of
 | |
|  * either the GNU General Public License Version 2 or later (the "GPL"), or
 | |
|  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 | |
|  * in which case the provisions of the GPL or the LGPL are applicable instead
 | |
|  * of those above. If you wish to allow use of your version of this file only
 | |
|  * under the terms of either the GPL or the LGPL, and not to allow others to
 | |
|  * use your version of this file under the terms of the MPL, indicate your
 | |
|  * decision by deleting the provisions above and replace them with the notice
 | |
|  * and other provisions required by the GPL or the LGPL. If you do not delete
 | |
|  * the provisions above, a recipient may use your version of this file under
 | |
|  * the terms of any one of the MPL, the GPL or the LGPL.
 | |
|  *
 | |
|  * ***** END LICENSE BLOCK ***** */
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <time.h>
 | |
| #include <ctype.h>
 | |
| #include <errno.h>
 | |
| #include <math.h>
 | |
| 
 | |
| #include "nspr.h"
 | |
| #include "tmreader.h"
 | |
| 
 | |
| 
 | |
| #define ERROR_REPORT(num, val, msg)   fprintf(stderr, "error(%d):\t\"%s\"\t%s\n", (num), (val), (msg));
 | |
| #define CLEANUP(ptr)    do { if(NULL != ptr) { free(ptr); ptr = NULL; } } while(0)
 | |
| 
 | |
| 
 | |
| #define ticks2msec(reader, ticks) ticks2xsec((reader), (ticks), 1000)
 | |
| #define ticks2usec(reader, ticks) ticks2xsec((reader), (ticks), 1000000)
 | |
| #define TICK_RESOLUTION 1000
 | |
| #define TICK_PRINTABLE(timeval) ((PRFloat64)(timeval) / (PRFloat64)ST_TIMEVAL_RESOLUTION)
 | |
| 
 | |
| 
 | |
| typedef struct __struct_Options
 | |
| /*
 | |
| **  Options to control how we perform.
 | |
| **
 | |
| **  mProgramName    Used in help text.
 | |
| **  mInputName      Name of the file.
 | |
| **  mOutput         Output file, append.
 | |
| **                  Default is stdout.
 | |
| **  mOutputName     Name of the file.
 | |
| **  mHelp           Whether or not help should be shown.
 | |
| **  mOverhead       How much overhead an allocation will have.
 | |
| **  mAlignment      What boundry will the end of an allocation line up on.
 | |
| **  mPageSize       Controls the page size.  A page containing only fragments
 | |
| **                      is not fragmented.  A page containing any life memory
 | |
| **                      costs mPageSize in bytes.
 | |
| */
 | |
| {
 | |
|     const char* mProgramName;
 | |
|     char* mInputName;
 | |
|     FILE* mOutput;
 | |
|     char* mOutputName;
 | |
|     int mHelp;
 | |
|     unsigned mOverhead;
 | |
|     unsigned mAlignment;
 | |
|     unsigned mPageSize;
 | |
| }
 | |
| Options;
 | |
| 
 | |
| 
 | |
| typedef struct __struct_Switch
 | |
| /*
 | |
| **  Command line options.
 | |
| */
 | |
| {
 | |
|     const char* mLongName;
 | |
|     const char* mShortName;
 | |
|     int mHasValue;
 | |
|     const char* mValue;
 | |
|     const char* mDescription;
 | |
| }
 | |
| Switch;
 | |
| 
 | |
| #define DESC_NEWLINE "\n\t\t"
 | |
| 
 | |
| static Switch gInputSwitch = {"--input", "-i", 1, NULL, "Specify input file." DESC_NEWLINE "stdin is default."};
 | |
| static Switch gOutputSwitch = {"--output", "-o", 1, NULL, "Specify output file." DESC_NEWLINE "Appends if file exists." DESC_NEWLINE "stdout is default."};
 | |
| static Switch gHelpSwitch = {"--help", "-h", 0, NULL, "Information on usage."};
 | |
| static Switch gAlignmentSwitch = {"--alignment", "-al", 1, NULL, "All allocation sizes are made to be a multiple of this number." DESC_NEWLINE "Closer to actual heap conditions; set to 1 for true sizes." DESC_NEWLINE "Default value is 16."};
 | |
| static Switch gOverheadSwitch = {"--overhead", "-ov", 1, NULL, "After alignment, all allocations are made to increase by this number." DESC_NEWLINE "Closer to actual heap conditions; set to 0 for true sizes." DESC_NEWLINE "Default value is 8."};
 | |
| static Switch gPageSizeSwitch = {"--page-size", "-ps", 1, NULL, "Sets the page size which aids the identification of fragmentation." DESC_NEWLINE "Closer to actual heap conditions; set to 4294967295 for true sizes."  DESC_NEWLINE "Default value is 4096."};
 | |
| 
 | |
| static Switch* gSwitches[] = {
 | |
|         &gInputSwitch,
 | |
|         &gOutputSwitch,
 | |
|         &gAlignmentSwitch,
 | |
|         &gOverheadSwitch,
 | |
|         &gPageSizeSwitch,
 | |
|         &gHelpSwitch
 | |
| };
 | |
| 
 | |
| 
 | |
| typedef struct __struct_AnyArray
 | |
| /*
 | |
| **  Variable sized item array.
 | |
| **
 | |
| **  mItems      The void pointer items.
 | |
| **  mItemSize   Size of each different item.
 | |
| **  mCount      The number of items in the array.
 | |
| **  mCapacity   How many more items we can hold before reallocing.
 | |
| **  mGrowBy     How many items we allocate when we grow.
 | |
| */
 | |
| {
 | |
|     void* mItems;
 | |
|     unsigned mItemSize;
 | |
|     unsigned mCount;
 | |
|     unsigned mCapacity;
 | |
|     unsigned mGrowBy;
 | |
| }
 | |
| AnyArray;
 | |
| 
 | |
| 
 | |
| typedef int (*arrayMatchFunc)(void* inContext, AnyArray* inArray, void* inItem, unsigned inItemIndex)
 | |
| /*
 | |
| **  Callback function for the arrayIndexFn function.
 | |
| **  Used to determine an item match by customizable criteria.
 | |
| **
 | |
| **  inContext       The criteria and state of the search.
 | |
| **                  User specified/created.
 | |
| **  inArray         The array the item is in.
 | |
| **  inItem          The item to evaluate for match.
 | |
| **  inItemIndex     The index of this particular item in the array.
 | |
| **
 | |
| **  return int      0 to specify a match.
 | |
| **                  !0 to continue the search performed by arrayIndexFn.
 | |
| */
 | |
| ;
 | |
| 
 | |
| 
 | |
| typedef enum __enum_HeapEventType
 | |
| /*
 | |
| **  Simple heap events are really one of two things.
 | |
| */
 | |
| {
 | |
|     FREE,
 | |
|     ALLOC
 | |
| }
 | |
| HeapEventType;
 | |
| 
 | |
| 
 | |
| typedef enum __enum_HeapObjectType
 | |
| /*
 | |
| **  The various types of heap objects we track.
 | |
| */
 | |
| {
 | |
|     ALLOCATION,
 | |
|     FRAGMENT
 | |
| }
 | |
| HeapObjectType;
 | |
| 
 | |
| 
 | |
| typedef struct __struct_HeapObject HeapObject;
 | |
| typedef struct __struct_HeapHistory
 | |
| /*
 | |
| **  A marker as to what has happened.
 | |
| **
 | |
| **  mTimestamp      When history occurred.
 | |
| **  mTMRSerial      The historical state as known to the tmreader.
 | |
| **  mObjectIndex    Index to the object that was before or after this event.
 | |
| **                  The index as in the index according to all heap objects
 | |
| **                      kept in the TMState structure.
 | |
| **                  We use an index instead of a pointer as the array of
 | |
| **                      objects can change location in the heap.
 | |
| */
 | |
| {
 | |
|     unsigned mTimestamp;
 | |
|     unsigned mTMRSerial;
 | |
|     unsigned mObjectIndex;
 | |
| }
 | |
| HeapHistory;
 | |
| 
 | |
| 
 | |
| struct __struct_HeapObject
 | |
| /*
 | |
| **  An object in the heap.
 | |
| **
 | |
| **  A special case should be noted here.  If either the birth or death
 | |
| **      history leads to an object of the same type, then this object
 | |
| **      is the same as that object, but was modified somehow.
 | |
| **  Also note that multiple objects may have the same birth object,
 | |
| **      as well as the same death object.
 | |
| **
 | |
| **  mUniqueID           Each object is unique.
 | |
| **  mType               Either allocation or fragment.
 | |
| **  mHeapOffset         Where in the heap the object is.
 | |
| **  mSize               How much of the heap the object takes.
 | |
| **  mBirth              History about the birth event.
 | |
| **  mDeath              History about the death event.
 | |
| */
 | |
| {
 | |
|     unsigned mUniqueID;
 | |
| 
 | |
|     HeapObjectType mType;
 | |
|     unsigned mHeapOffset;
 | |
|     unsigned mSize;
 | |
| 
 | |
|     HeapHistory mBirth;
 | |
|     HeapHistory mDeath;
 | |
| };
 | |
| 
 | |
| 
 | |
| typedef struct __struct_TMState
 | |
| /*
 | |
| **  State of our current operation.
 | |
| **  Stats we are trying to calculate.
 | |
| **
 | |
| **  mOptions        Obilgatory options pointer.
 | |
| **  mTMR            The tmreader, used in tmreader API calls.
 | |
| **  mLoopExitTMR    Set to non zero in order to quickly exit from tmreader
 | |
| **                      input loop.  This will also result in an error.
 | |
| **  uMinTicks       Start of run, milliseconds.
 | |
| **  uMaxTicks       End of run, milliseconds.
 | |
| */
 | |
| {
 | |
|     Options* mOptions;
 | |
|     tmreader* mTMR;
 | |
| 
 | |
|     int mLoopExitTMR;
 | |
| 
 | |
|     unsigned uMinTicks;
 | |
|     unsigned uMaxTicks;
 | |
| }
 | |
| TMState;
 | |
| 
 | |
| 
 | |
| int initOptions(Options* outOptions, int inArgc, char** inArgv)
 | |
| /*
 | |
| **  returns int     0 if successful.
 | |
| */
 | |
| {
 | |
|     int retval = 0;
 | |
|     int loop = 0;
 | |
|     int switchLoop = 0;
 | |
|     int match = 0;
 | |
|     const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
 | |
|     Switch* current = NULL;
 | |
| 
 | |
|     /*
 | |
|     **  Set any defaults.
 | |
|     */
 | |
|     memset(outOptions, 0, sizeof(Options));
 | |
|     outOptions->mProgramName = inArgv[0];
 | |
|     outOptions->mInputName = strdup("-");
 | |
|     outOptions->mOutput = stdout;
 | |
|     outOptions->mOutputName = strdup("stdout");
 | |
|     outOptions->mAlignment = 16;
 | |
|     outOptions->mOverhead = 8;
 | |
| 
 | |
|     if(NULL == outOptions->mOutputName || NULL == outOptions->mInputName)
 | |
|     {
 | |
|         retval = __LINE__;
 | |
|         ERROR_REPORT(retval, "stdin/stdout", "Unable to strdup.");
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|     **  Go through and attempt to do the right thing.
 | |
|     */
 | |
|     for(loop = 1; loop < inArgc && 0 == retval; loop++)
 | |
|     {
 | |
|         match = 0;
 | |
|         current = NULL;
 | |
| 
 | |
|         for(switchLoop = 0; switchLoop < switchCount && 0 == retval; switchLoop++)
 | |
|         {
 | |
|             if(0 == strcmp(gSwitches[switchLoop]->mLongName, inArgv[loop]))
 | |
|             {
 | |
|                 match = __LINE__;
 | |
|             }
 | |
|             else if(0 == strcmp(gSwitches[switchLoop]->mShortName, inArgv[loop]))
 | |
|             {
 | |
|                 match = __LINE__;
 | |
|             }
 | |
| 
 | |
|             if(match)
 | |
|             {
 | |
|                 if(gSwitches[switchLoop]->mHasValue)
 | |
|                 {
 | |
|                     /*
 | |
|                     **  Attempt to absorb next option to fullfill value.
 | |
|                     */
 | |
|                     if(loop + 1 < inArgc)
 | |
|                     {
 | |
|                         loop++;
 | |
| 
 | |
|                         current = gSwitches[switchLoop];
 | |
|                         current->mValue = inArgv[loop];
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     current = gSwitches[switchLoop];
 | |
|                 }
 | |
| 
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if(0 == match)
 | |
|         {
 | |
|             outOptions->mHelp = __LINE__;
 | |
|             retval = __LINE__;
 | |
|             ERROR_REPORT(retval, inArgv[loop], "Unknown command line switch.");
 | |
|         }
 | |
|         else if(NULL == current)
 | |
|         {
 | |
|             outOptions->mHelp = __LINE__;
 | |
|             retval = __LINE__;
 | |
|             ERROR_REPORT(retval, inArgv[loop], "Command line switch requires a value.");
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /*
 | |
|             ** Do something based on address/swtich.
 | |
|             */
 | |
|             if(current == &gInputSwitch)
 | |
|             {
 | |
|                 CLEANUP(outOptions->mInputName);
 | |
|                 outOptions->mInputName = strdup(current->mValue);
 | |
|                 if(NULL == outOptions->mInputName)
 | |
|                 {
 | |
|                     retval = __LINE__;
 | |
|                     ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
 | |
|                 }
 | |
|             }
 | |
|             else if(current == &gOutputSwitch)
 | |
|             {
 | |
|                 CLEANUP(outOptions->mOutputName);
 | |
|                 if(NULL != outOptions->mOutput && stdout != outOptions->mOutput)
 | |
|                 {
 | |
|                     fclose(outOptions->mOutput);
 | |
|                     outOptions->mOutput = NULL;
 | |
|                 }
 | |
| 
 | |
|                 outOptions->mOutput = fopen(current->mValue, "a");
 | |
|                 if(NULL == outOptions->mOutput)
 | |
|                 {
 | |
|                     retval = __LINE__;
 | |
|                     ERROR_REPORT(retval, current->mValue, "Unable to open output file.");
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     outOptions->mOutputName = strdup(current->mValue);
 | |
|                     if(NULL == outOptions->mOutputName)
 | |
|                     {
 | |
|                         retval = __LINE__;
 | |
|                         ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             else if(current == &gHelpSwitch)
 | |
|             {
 | |
|                 outOptions->mHelp = __LINE__;
 | |
|             }
 | |
|             else if(current == &gAlignmentSwitch)
 | |
|             {
 | |
|                 unsigned arg = 0;
 | |
|                 char* endScan = NULL;
 | |
| 
 | |
|                 errno = 0;
 | |
|                 arg = strtoul(current->mValue, &endScan, 0);
 | |
|                 if(0 == errno && endScan != current->mValue)
 | |
|                 {
 | |
|                     outOptions->mAlignment = arg;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     retval = __LINE__;
 | |
|                     ERROR_REPORT(retval, current->mValue, "Unable to convert to a number.");
 | |
|                 }
 | |
|             }
 | |
|             else if(current == &gOverheadSwitch)
 | |
|             {
 | |
|                 unsigned arg = 0;
 | |
|                 char* endScan = NULL;
 | |
| 
 | |
|                 errno = 0;
 | |
|                 arg = strtoul(current->mValue, &endScan, 0);
 | |
|                 if(0 == errno && endScan != current->mValue)
 | |
|                 {
 | |
|                     outOptions->mOverhead = arg;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     retval = __LINE__;
 | |
|                     ERROR_REPORT(retval, current->mValue, "Unable to convert to a number.");
 | |
|                 }
 | |
|             }
 | |
|             else if(current == &gPageSizeSwitch)
 | |
|             {
 | |
|                 unsigned arg = 0;
 | |
|                 char* endScan = NULL;
 | |
| 
 | |
|                 errno = 0;
 | |
|                 arg = strtoul(current->mValue, &endScan, 0);
 | |
|                 if(0 == errno && endScan != current->mValue)
 | |
|                 {
 | |
|                     outOptions->mPageSize = arg;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     retval = __LINE__;
 | |
|                     ERROR_REPORT(retval, current->mValue, "Unable to convert to a number.");
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 retval = __LINE__;
 | |
|                 ERROR_REPORT(retval, current->mLongName, "No handler for command line switch.");
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| PRUint32 ticks2xsec(tmreader* aReader, PRUint32 aTicks, PRUint32 aResolution)
 | |
| /*
 | |
| ** Convert platform specific ticks to second units
 | |
| */
 | |
| {
 | |
|     PRUint32 retval = 0;
 | |
|     PRUint64 bigone;
 | |
|     PRUint64 tmp64;
 | |
| 
 | |
|     LL_UI2L(bigone, aResolution);
 | |
|     LL_UI2L(tmp64, aTicks);
 | |
|     LL_MUL(bigone, bigone, tmp64);
 | |
|     LL_UI2L(tmp64, aReader->ticksPerSec);
 | |
|     LL_DIV(bigone, bigone, tmp64);
 | |
|     LL_L2UI(retval, bigone);
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| void cleanOptions(Options* inOptions)
 | |
| /*
 | |
| **  Clean up any open handles.
 | |
| */
 | |
| {
 | |
|     unsigned loop = 0;
 | |
| 
 | |
|     CLEANUP(inOptions->mInputName);
 | |
|     CLEANUP(inOptions->mOutputName);
 | |
|     if(NULL != inOptions->mOutput && stdout != inOptions->mOutput)
 | |
|     {
 | |
|         fclose(inOptions->mOutput);
 | |
|     }
 | |
| 
 | |
|     memset(inOptions, 0, sizeof(Options));
 | |
| }
 | |
| 
 | |
| 
 | |
| void showHelp(Options* inOptions)
 | |
| /*
 | |
| **  Show some simple help text on usage.
 | |
| */
 | |
| {
 | |
|     int loop = 0;
 | |
|     const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
 | |
|     const char* valueText = NULL;
 | |
| 
 | |
|     printf("usage:\t%s [arguments]\n", inOptions->mProgramName);
 | |
|     printf("\n");
 | |
|     printf("arguments:\n");
 | |
| 
 | |
|     for(loop = 0; loop < switchCount; loop++)
 | |
|     {
 | |
|         if(gSwitches[loop]->mHasValue)
 | |
|         {
 | |
|             valueText = " <value>";
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             valueText = "";
 | |
|         }
 | |
| 
 | |
|         printf("\t%s%s\n", gSwitches[loop]->mLongName, valueText);
 | |
|         printf("\t %s%s", gSwitches[loop]->mShortName, valueText);
 | |
|         printf(DESC_NEWLINE "%s\n\n", gSwitches[loop]->mDescription);
 | |
|     }
 | |
| 
 | |
|     printf("This tool reports heap fragmentation stats from a trace-malloc log.\n");
 | |
| }
 | |
| 
 | |
| 
 | |
| AnyArray* arrayCreate(unsigned inItemSize, unsigned inGrowBy)
 | |
| /*
 | |
| **  Create an array container object.
 | |
| */
 | |
| {
 | |
|     AnyArray* retval = NULL;
 | |
| 
 | |
|     if(0 != inGrowBy && 0 != inItemSize)
 | |
|     {
 | |
|         retval = (AnyArray*)calloc(1, sizeof(AnyArray));
 | |
|         retval->mItemSize = inItemSize;
 | |
|         retval->mGrowBy = inGrowBy;
 | |
|     }
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| void arrayDestroy(AnyArray* inArray)
 | |
| /*
 | |
| **  Release the memory the array contains.
 | |
| **  This will release the items as well.
 | |
| */
 | |
| {
 | |
|     if(NULL != inArray)
 | |
|     {
 | |
|         if(NULL != inArray->mItems)
 | |
|         {
 | |
|             free(inArray->mItems);
 | |
|         }
 | |
|         free(inArray);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| unsigned arrayAlloc(AnyArray* inArray, unsigned inItems)
 | |
| /*
 | |
| **  Resize the item array capcity to a specific number of items.
 | |
| **  This could possibly truncate the array, so handle that as well.
 | |
| **
 | |
| **  returns unsigned        <= inArray->mCapacity on success.
 | |
| */
 | |
| {
 | |
|     unsigned retval = (unsigned)-1;
 | |
| 
 | |
|     if(NULL != inArray)
 | |
|     {
 | |
|         void* moved = NULL;
 | |
| 
 | |
|         moved = realloc(inArray->mItems, inItems * inArray->mItemSize);
 | |
|         if(NULL != moved)
 | |
|         {
 | |
|             inArray->mItems = moved;
 | |
|             inArray->mCapacity = inItems;
 | |
|             if(inArray->mCount > inItems)
 | |
|             {
 | |
|                 inArray->mCount = inItems;
 | |
|             }
 | |
| 
 | |
|             retval = inItems;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| void* arrayItem(AnyArray* inArray, unsigned inIndex)
 | |
| /*
 | |
| **  Return the array item at said index.
 | |
| **  Zero based index.
 | |
| **
 | |
| **  returns void*       NULL on failure.
 | |
| */
 | |
| {
 | |
|     void* retval = NULL;
 | |
| 
 | |
|     if(NULL != inArray && inIndex < inArray->mCount)
 | |
|     {
 | |
|         retval = (void*)((char*)inArray->mItems + (inArray->mItemSize * inIndex));
 | |
|     }
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| unsigned arrayIndex(AnyArray* inArray, void* inItem, unsigned inStartIndex)
 | |
| /*
 | |
| **  Go through the array from the index specified looking for an item
 | |
| **      match based on byte for byte comparison.
 | |
| **  We allow specifying the start index in order to handle arrays with
 | |
| **      duplicate items.
 | |
| **
 | |
| **  returns unsigned        >= inArray->mCount on failure.
 | |
| */
 | |
| {
 | |
|     unsigned retval = (unsigned)-1;
 | |
| 
 | |
|     if(NULL != inArray && NULL != inItem && inStartIndex < inArray->mCount)
 | |
|     {
 | |
|         void* curItem = NULL;
 | |
| 
 | |
|         for(retval = inStartIndex; retval < inArray->mCount; retval++)
 | |
|         {
 | |
|             curItem = arrayItem(inArray, retval);
 | |
|             if(0 == memcmp(inItem, curItem, inArray->mItemSize))
 | |
|             {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| unsigned arrayIndexFn(AnyArray* inArray, arrayMatchFunc inFunc, void* inFuncContext, unsigned inStartIndex)
 | |
| /*
 | |
| **  Go through the array from the index specified looking for an item
 | |
| **      match based upon the return value of inFunc (0, Zero, is a match).
 | |
| **  We allow specifying the start index in order to facilitate looping over
 | |
| **      the array which could have multiple matches.
 | |
| **
 | |
| **  returns unsigned        >= inArray->mCount on failure.
 | |
| */
 | |
| {
 | |
|     unsigned retval = (unsigned)-1;
 | |
| 
 | |
|     if(NULL != inArray && NULL != inFunc && inStartIndex < inArray->mCount)
 | |
|     {
 | |
|         void* curItem = NULL;
 | |
| 
 | |
|         for(retval = inStartIndex; retval < inArray->mCount; retval++)
 | |
|         {
 | |
|             curItem = arrayItem(inArray, retval);
 | |
|             if(0 == inFunc(inFuncContext, inArray, curItem, retval))
 | |
|             {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| unsigned arrayAddItem(AnyArray* inArray, void* inItem)
 | |
| /*
 | |
| **  Add a new item to the array.
 | |
| **  This is done by copying the item.
 | |
| **
 | |
| **  returns unsigned        < inArray->mCount on success.
 | |
| */
 | |
| {
 | |
|     unsigned retval = (unsigned)-1;
 | |
| 
 | |
|     if(NULL != inArray && NULL != inItem)
 | |
|     {
 | |
|         int noCopy = 0;
 | |
| 
 | |
|         /*
 | |
|         **  See if the array should grow.
 | |
|         */
 | |
|         if(inArray->mCount == inArray->mCapacity)
 | |
|         {
 | |
|             unsigned allocRes = 0;
 | |
| 
 | |
|             allocRes = arrayAlloc(inArray, inArray->mCapacity + inArray->mGrowBy);
 | |
|             if(allocRes > inArray->mCapacity)
 | |
|             {
 | |
|                 noCopy = __LINE__;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if(0 == noCopy)
 | |
|         {
 | |
|             retval = inArray->mCount;
 | |
| 
 | |
|             inArray->mCount++;
 | |
|             memcpy(arrayItem(inArray, retval), inItem, inArray->mItemSize);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| HeapObject* initHeapObject(HeapObject* inObject)
 | |
| /*
 | |
| **  Function to init the heap object just right.
 | |
| **  Sets the unique ID to something unique.
 | |
| */
 | |
| {
 | |
|     HeapObject* retval = inObject;
 | |
| 
 | |
|     if(NULL != inObject)
 | |
|     {
 | |
|         static unsigned uniqueGenerator = 0;
 | |
| 
 | |
|         memset(inObject, -1, sizeof(HeapObject));
 | |
| 
 | |
|         inObject->mUniqueID = uniqueGenerator;
 | |
|         uniqueGenerator++;
 | |
|     }
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| int simpleHeapEvent(TMState* inStats, HeapEventType inType, unsigned mTimestamp, unsigned inSerial, unsigned inHeapID, unsigned inSize)
 | |
| /*
 | |
| **  A new heap event will cause the creation of a new heap object.
 | |
| **  The new heap object will displace, or replace, a heap object of a different type.
 | |
| */
 | |
| {
 | |
|     int retval = 0;
 | |
|     HeapObject newObject;
 | |
| 
 | |
|     /*
 | |
|     **  Set the most basic object details.
 | |
|     */
 | |
|     initHeapObject(&newObject);
 | |
|     newObject.mHeapOffset = inHeapID;
 | |
|     newObject.mSize = inSize;
 | |
|     if(FREE == inType)
 | |
|     {
 | |
|         newObject.mType = FRAGMENT;
 | |
|     }
 | |
|     else if(ALLOC == inType)
 | |
|     {
 | |
|         newObject.mType = ALLOCATION;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|     **  Add it to the heap object array.
 | |
|     */
 | |
| 
 | |
|     /*
 | |
|     **  TODO GAB
 | |
|     **
 | |
|     **  First thing to do is to add the new object to the heap in order to
 | |
|     **      obtain a valid index.
 | |
|     **
 | |
|     **  Next, find all matches to this range of heap memory that this event
 | |
|     **      refers to, that are alive during this timestamp (no death yet).
 | |
|     **  Fill in the death event of those objects.
 | |
|     **  If the objects contain some portions outside of the range, then
 | |
|     **      new objects for those ranges need to be created that carry on
 | |
|     **      the same object type, have the index of the old object for birth,
 | |
|     **      and the serial of the old object, new timestamp of course.
 | |
|     **  The old object's death points to the new object, which tells why the
 | |
|     **      fragmentation took place.
 | |
|     **  The new object birth points to the old object only if a fragment.
 | |
|     **  An allocation only has a birth object when it is a realloc (complex)
 | |
|     **      heap event.
 | |
|     **
 | |
|     **  I believe this give us enough information to look up particular
 | |
|     **      details of the heap at any given time.
 | |
|     */
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| int complexHeapEvent(TMState* inStats, unsigned mTimestamp, unsigned inOldSerial, unsigned inOldHeapID, unsigned inOSize, unsigned inNewSerial, unsigned inNewHeapID, unsigned inNewSize)
 | |
| /*
 | |
| **  Generally, this event intends to chain one old heap object to a newer heap object.
 | |
| **  Otherwise, the functionality should recognizable ala simpleHeapEvent.
 | |
| */
 | |
| {
 | |
|     int retval = 0;
 | |
| 
 | |
|     /*
 | |
|     **  TODO GAB
 | |
|     */
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| unsigned actualByteSize(Options* inOptions, unsigned retval)
 | |
| /*
 | |
| **  Apply alignment and overhead to size to figure out actual byte size.
 | |
| **  This by default mimics spacetrace with default options (msvc crt heap).
 | |
| */
 | |
| {
 | |
|     if(0 != retval)
 | |
|     {
 | |
|         unsigned eval = 0;
 | |
|         unsigned over = 0;
 | |
| 
 | |
|         eval = retval - 1;
 | |
|         if(0 != inOptions->mAlignment)
 | |
|         {
 | |
|             over = eval % inOptions->mAlignment;
 | |
|         }
 | |
|         retval = eval + inOptions->mOverhead + inOptions->mAlignment - over;
 | |
|     }
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| void tmEventHandler(tmreader* inReader, tmevent* inEvent)
 | |
| /*
 | |
| **  Callback from the tmreader_eventloop.
 | |
| **  Build up our fragmentation information herein.
 | |
| */
 | |
| {
 | |
|     char type = inEvent->type;
 | |
|     TMState* stats = (TMState*)inReader->data;
 | |
| 
 | |
|     /*
 | |
|     **  Only intersted in handling events of a particular type.
 | |
|     */
 | |
|     switch(type)
 | |
|     {
 | |
|     default:
 | |
|         return;
 | |
| 
 | |
|     case TM_EVENT_MALLOC:
 | |
|     case TM_EVENT_CALLOC:
 | |
|     case TM_EVENT_REALLOC:
 | |
|     case TM_EVENT_FREE:
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|     **  Should we even try to look?
 | |
|     **  Set mLoopExitTMR to non-zero to abort the read loop faster.
 | |
|     */
 | |
|     if(0 == stats->mLoopExitTMR)
 | |
|     {
 | |
|         Options* options = (Options*)stats->mOptions;
 | |
|         unsigned timestamp = ticks2msec(stats->mTMR, inEvent->u.alloc.interval);
 | |
|         unsigned actualSize = actualByteSize(options, inEvent->u.alloc.size);
 | |
|         unsigned heapID = inEvent->u.alloc.ptr;
 | |
|         unsigned serial = inEvent->serial;
 | |
|         
 | |
|         /*
 | |
|         **  Check the timestamp range of our overall state.
 | |
|         */
 | |
|         if(stats->uMinTicks > timestamp)
 | |
|         {
 | |
|             stats->uMinTicks = timestamp;
 | |
|         }
 | |
|         if(stats->uMaxTicks < timestamp)
 | |
|         {
 | |
|             stats->uMaxTicks = timestamp;
 | |
|         }
 | |
|         
 | |
|         /*
 | |
|         **  Realloc in general deserves some special attention if dealing
 | |
|         **      with an old allocation (not new memory).
 | |
|         */
 | |
|         if(TM_EVENT_REALLOC == type && 0 != inEvent->u.alloc.oldserial)
 | |
|         {
 | |
|             unsigned oldActualSize = actualByteSize(options, inEvent->u.alloc.oldsize);
 | |
|             unsigned oldHeapID = inEvent->u.alloc.oldptr;
 | |
|             unsigned oldSerial = inEvent->u.alloc.oldserial;
 | |
| 
 | |
|             if(0 == actualSize)
 | |
|             {
 | |
|                 /*
 | |
|                 **  Reallocs of size zero are to become free events.
 | |
|                 */
 | |
|                 stats->mLoopExitTMR = simpleHeapEvent(stats, FREE, timestamp, serial, oldHeapID, oldActualSize);
 | |
|             }
 | |
|             else if(heapID != oldHeapID || actualSize != oldActualSize)
 | |
|             {
 | |
|                 /*
 | |
|                 **  Reallocs which moved generate two events.
 | |
|                 **  Reallocs which changed size generate two events.
 | |
|                 **
 | |
|                 **  One event to free the old memory area.
 | |
|                 **  Another event to allocate the new memory area.
 | |
|                 **  They are to be linked to one another, so the history
 | |
|                 **      and true origin can be tracked.
 | |
|                 */
 | |
|                 stats->mLoopExitTMR = complexHeapEvent(stats, timestamp, oldSerial, oldHeapID, oldActualSize, serial, heapID, actualSize);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 /*
 | |
|                 **  The realloc is not considered an operation and is skipped.
 | |
|                 **  It is not an operation, because it did not move or change
 | |
|                 **      size; this can happen if a realloc falls within the
 | |
|                 **      alignment of an allocation.
 | |
|                 **  Say if you realloc a 1 byte allocation to 2 bytes, it will
 | |
|                 **      not really change heap impact unless you have 1 set as
 | |
|                 **      the alignment of your allocations.
 | |
|                 */
 | |
|             }
 | |
|         }
 | |
|         else if(TM_EVENT_FREE == type)
 | |
|         {
 | |
|             /*
 | |
|             **  Generate a free event to create a fragment.
 | |
|             */
 | |
|             stats->mLoopExitTMR = simpleHeapEvent(stats, FREE, timestamp, serial, heapID, actualSize);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /*
 | |
|             **  Generate an allocation event to clear fragments.
 | |
|             */
 | |
|             stats->mLoopExitTMR = simpleHeapEvent(stats, ALLOC, timestamp, serial, heapID, actualSize);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| int tmfrags(Options* inOptions)
 | |
| /*
 | |
| **  Load the input file and report stats.
 | |
| */
 | |
| {
 | |
|     int retval = 0;
 | |
|     TMState stats;
 | |
| 
 | |
|     memset(&stats, 0, sizeof(stats));
 | |
|     stats.mOptions = inOptions;
 | |
|     stats.uMinTicks = 0xFFFFFFFFU;
 | |
| 
 | |
|     /*
 | |
|     **  Need a tmreader.
 | |
|     */
 | |
|     stats.mTMR = tmreader_new(inOptions->mProgramName, &stats);
 | |
|     if(NULL != stats.mTMR)
 | |
|     {
 | |
|         int tmResult = 0;
 | |
| 
 | |
|         tmResult = tmreader_eventloop(stats.mTMR, inOptions->mInputName, tmEventHandler);
 | |
|         if(0 == tmResult)
 | |
|         {
 | |
|             retval = __LINE__;
 | |
|             ERROR_REPORT(retval, inOptions->mInputName, "Problem reading trace-malloc data.");
 | |
|         }
 | |
|         if(0 != stats.mLoopExitTMR)
 | |
|         {
 | |
|             retval = stats.mLoopExitTMR;
 | |
|             ERROR_REPORT(retval, inOptions->mInputName, "Aborted trace-malloc input loop.");
 | |
|         }
 | |
| 
 | |
|         tmreader_destroy(stats.mTMR);
 | |
|         stats.mTMR = NULL;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         retval = __LINE__;
 | |
|         ERROR_REPORT(retval, inOptions->mProgramName, "Unable to obtain tmreader.");
 | |
|     }
 | |
| 
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| 
 | |
| int main(int inArgc, char** inArgv)
 | |
| {
 | |
|     int retval = 0;
 | |
|     Options options;
 | |
| 
 | |
|     retval = initOptions(&options, inArgc, inArgv);
 | |
|     if(options.mHelp)
 | |
|     {
 | |
|         showHelp(&options);
 | |
|     }
 | |
|     else if(0 == retval)
 | |
|     {
 | |
|         retval = tmfrags(&options);
 | |
|     }
 | |
| 
 | |
|     cleanOptions(&options);
 | |
|     return retval;
 | |
| }
 | |
| 
 | 
