From e42ff7fd06e342fef6506db327da8cf29a1c4fba Mon Sep 17 00:00:00 2001 From: "dp%netscape.com" Date: Tue, 16 Apr 2002 23:36:47 +0000 Subject: [PATCH] categories git-svn-id: svn://10.0.0.236/trunk@119177 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/tools/trace-malloc/Makefile.in | 12 +- mozilla/tools/trace-malloc/makefile.win | 2 +- mozilla/tools/trace-malloc/rules.txt | 30 + mozilla/tools/trace-malloc/spacecategory.c | 698 +++++++++++++++++++++ mozilla/tools/trace-malloc/spacetrace.c | 536 ++++++++++------ mozilla/tools/trace-malloc/spacetrace.h | 137 ++++ 6 files changed, 1227 insertions(+), 188 deletions(-) create mode 100644 mozilla/tools/trace-malloc/rules.txt create mode 100644 mozilla/tools/trace-malloc/spacecategory.c diff --git a/mozilla/tools/trace-malloc/Makefile.in b/mozilla/tools/trace-malloc/Makefile.in index d75a76afa44..e6fdd8280fe 100644 --- a/mozilla/tools/trace-malloc/Makefile.in +++ b/mozilla/tools/trace-malloc/Makefile.in @@ -28,13 +28,19 @@ include $(DEPTH)/config/autoconf.mk REQUIRES = string xpcom -CSRCS += \ +SIMPLECSRCS += \ bloatblame.c \ leakstats.c \ - spacetrace.c \ $(NULL) -SIMPLE_PROGRAMS = $(CSRCS:.c=$(BIN_SUFFIX)) +SIMPLE_PROGRAMS = $(SIMPLECSRCS:.c=$(BIN_SUFFIX)) + +CSRCS = \ + spacetrace.c \ + spacecategory.c \ + $(NULL) + +PROGRAM = spacetrace ifeq ($(OS_ARCH),WINNT) LOCAL_INCLUDES += -I$(topsrcdir)/config/os2 diff --git a/mozilla/tools/trace-malloc/makefile.win b/mozilla/tools/trace-malloc/makefile.win index ee5be064d03..e3c04054c06 100755 --- a/mozilla/tools/trace-malloc/makefile.win +++ b/mozilla/tools/trace-malloc/makefile.win @@ -79,7 +79,7 @@ $(LIB1): $(CPP_OBJS) $(PROG1): $(LIB1) bloatblame.c -$(PROG2): $(LIB1) spacetrace.c +$(PROG2): $(LIB1) spacetrace.c spacecategory.c $(PROG3): $(LIB1) leakstats.c diff --git a/mozilla/tools/trace-malloc/rules.txt b/mozilla/tools/trace-malloc/rules.txt new file mode 100644 index 00000000000..b44739977f8 --- /dev/null +++ b/mozilla/tools/trace-malloc/rules.txt @@ -0,0 +1,30 @@ +# Categorization rules for spacetrace +# +# This file defines the stack frame rules that will categorize +# allocations that spacetrace processes. The format of this file is +# +# +# substring for stack frame n +# substring for stack frame n+1 +# substring for stack frame n+2 +# +# The key in the matching rule is that for every rule, we provide a +# snippet of the stack frame - contiguous substring matches. +# categorynames and rules substring matches are case sensitive +# +# Predefined Categories +# "All" - All allocations [default] +# "uncategorized" - All allocations that dont match any category +# +# +# Suresh Duddi + +# NOTE: This is still under definition + +# css category + +New_CSS + +# xpcom category + +PrePopulateRegistry diff --git a/mozilla/tools/trace-malloc/spacecategory.c b/mozilla/tools/trace-malloc/spacecategory.c new file mode 100644 index 00000000000..3e249a3ea37 --- /dev/null +++ b/mozilla/tools/trace-malloc/spacecategory.c @@ -0,0 +1,698 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 spacecategory.c code, released + * Apr 12, 2002. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 2001 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Suresh Duddi , 12-April-2002 + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + */ + +/* +** spacecategory.c +** +** Cagtegorizes each allocation using a predefined set of rules +** and presents a tree of categories for browsing. +*/ + +/* +** Required include files. +*/ +#include "spacetrace.h" +#include +#include + +#if defined(HAVE_BOUTELL_GD) +/* +** See http://www.boutell.com/gd for the GD graphics library. +** Ports for many platorms exist. +** Your box may already have the lib (mine did, redhat 7.1 workstation). +*/ +#include +#include +#include +#include +#endif /* HAVE_BOUTELL_GD */ + +/* +** Forward declarations +**/ +#if defined(DEBUG_dp) +extern void printCategoryTree(STCategoryNode* root); +#endif + +/* +** AddRule +** +** Add a rule into the list of rules maintainted in global +*/ +int AddRule(STGlobals *g, STCategoryRule *rule) +{ + if (g->mNRules % ST_ALLOC_STEP == 0) + { + /* Need more space */ + STCategoryRule** newrules; + newrules = (STCategoryRule **) realloc(g->mCategoryRules, + (g->mNRules + ST_ALLOC_STEP)*sizeof(STCategoryRule *)); + if (!newrules) + { + REPORT_ERROR(__LINE__, AddRule_No_Memory); + return -1; + } + g->mCategoryRules = newrules; + } + g->mCategoryRules[g->mNRules++] = rule; + return 0; +} + +/* +** AddChild +** +** Add the node as a child of the parent node +*/ +int AddChild(STCategoryNode* parent, STCategoryNode* child) +{ + if (parent->nchildren % ST_ALLOC_STEP == 0) + { + /* need more space */ + STCategoryNode** newnodes; + newnodes = (STCategoryNode **) realloc(parent->children, + (parent->nchildren + ST_ALLOC_STEP)*sizeof(STCategoryNode *)); + if (!newnodes) + { + REPORT_ERROR(__LINE__, AddChild_No_Memory); + return -1; + } + parent->children = newnodes; + } + parent->children[parent->nchildren++] = child; + return 0; +} + +int ReParent(STCategoryNode* parent, STCategoryNode* child) +{ + PRUint32 i; + if (child->parent == parent) + return 0; + + /* Remove child from old parent */ + if (child->parent) + { + for (i = 0; i < child->parent->nchildren; i++) + { + if (child->parent->children[i] == child) + { + /* Remove child from list */ + if (i+1 < child->parent->nchildren) + memmove(&child->parent->children[i], &child->parent->children[i+1], + (child->parent->nchildren - i - 1) * sizeof(STCategoryNode*)); + child->parent->nchildren--; + break; + } + } + } + + /* Add child into new parent */ + AddChild(parent, child); + + return 0; +} + +/* +** findCategoryNode +** +** Given a category name, finds the Node corresponding to the category +*/ +STCategoryNode* findCategoryNode(const char *catName, STGlobals *g) +{ + PRUint32 i; + for(i = 0; i < g->mNCategoryMap; i++) + { + if (!strcmp(g->mCategoryMap[i]->categoryName, catName)) + return g->mCategoryMap[i]->node; + } + return NULL; +} + +/* +** AddCategoryNode +** +** Adds a mapping between a category and its Node into the categoryMap +*/ +int AddCategoryNode(STCategoryNode* node, STGlobals* g) +{ + if (g->mNCategoryMap % ST_ALLOC_STEP == 0) + { + /* Need more space */ + STCategoryMapEntry **newmap = (STCategoryMapEntry **) realloc(g->mCategoryMap, + (g->mNCategoryMap + ST_ALLOC_STEP) * sizeof(STCategoryMapEntry *)); + if (!newmap) + { + REPORT_ERROR(__LINE__, AddCategoryNode_No_Memory); + return -1; + } + g->mCategoryMap = newmap; + + } + g->mCategoryMap[g->mNCategoryMap] = (STCategoryMapEntry *) calloc(1, sizeof(STCategoryMapEntry)); + if (!g->mCategoryMap[g->mNCategoryMap]) + { + REPORT_ERROR(__LINE__, AddCategoryNode_No_Memory); + return -1; + } + g->mCategoryMap[g->mNCategoryMap]->categoryName = node->categoryName; + g->mCategoryMap[g->mNCategoryMap]->node = node; + g->mNCategoryMap++; + return 0; +} + +/* +** NewCategoryNode +** +** Creates a new category node for category name 'catname' and makes +** 'parent' its parent. +*/ +STCategoryNode* NewCategoryNode(const char* catName, STCategoryNode* parent, STGlobals* g) +{ + int i; + STCategoryNode* node; + + node = (STCategoryNode *) calloc(1, sizeof(STCategoryNode)); + if (!node) + return NULL; + node->categoryName = catName; + + /* Set parent of child */ + node->parent = parent; + + /* Set child in parent */ + AddChild(parent, node); + + /* Add node into mapping table */ + AddCategoryNode(node, g); + + return node; +} + +/* +** ProcessCategoryLeafRule +** +** Add this into the tree as a leaf node. It doesnt know who its parent is. For now we make +** root as its parent +*/ +int ProcessCategoryLeafRule(STCategoryRule* leafRule, STCategoryNode *root, STGlobals *g) +{ + STCategoryRule* rule; + STCategoryNode* node; + + rule = (STCategoryRule *) calloc(1, sizeof(STCategoryRule)); + if (!rule) + return -1; + + /* Take ownership of all elements of rule */ + *rule = *leafRule; + + /* Find/Make a STCategoryNode and add it into the tree */ + node = findCategoryNode(rule->categoryName, g); + if (!node) + node = NewCategoryNode(rule->categoryName, root, g); + + /* Make sure rule knows which node to access */ + rule->node = node; + + /* Add rule into rulelist */ + AddRule(g, rule); + + return 0; +} + +/* +** ProcessCategoryParentRule +** +** Rule has all the children of category as patterns. Sets up the tree so that +** the parent child relationship is honored. +*/ +int ProcessCategoryParentRule(STCategoryRule* parentRule, STCategoryNode *root, STGlobals* g) +{ + STCategoryNode* node; + STCategoryNode* child; + PRUint32 i; + + /* Find the parent node in the tree. If not make one and add it into the tree */ + node = findCategoryNode(parentRule->categoryName, g); + if (!node) + { + node = NewCategoryNode(parentRule->categoryName, root, g); + if (!node) + return -1; + } + + /* For every child node, Find/Create it and make it the child of this node */ + for(i = 0; i < parentRule->npats; i++) + { + child = findCategoryNode(parentRule->pats[i], g); + if (!child) + { + child = NewCategoryNode(parentRule->pats[i], node, g); + if (!child) + return -1; + } + else + { + /* Reparent child to node. This is because when we created the node + ** we would have created it as the child of root. Now we need to + ** remove it from root's child list and add it into this node + */ + ReParent(node, child); + } + } + + return 0; +} + +/* +** initCategories +** +** Initialize all categories. This reads in a file that says how to categorize +** each callsite, creates a tree of these categories and makes a list of these +** patterns in order for matching +*/ +int initCategories(STGlobals* g) +{ + FILE *fp; + char buf[1024], *in; + int n, i; + PRBool inrule, leaf; + STCategoryRule rule; + + fp = fopen(g->mOptions.mCategoryFile, "r"); + if (!fp) + { + REPORT_ERROR(__LINE__, initCategories); + return -1; + } + + inrule = PR_FALSE; + leaf = PR_FALSE; + + memset(&rule, 0, sizeof(rule)); + + while (fgets(buf, sizeof(buf), fp) != NULL) + { + /* Lose the \n */ + n = strlen(buf); + if (buf[n-1] == '\n') + buf[--n] = '\0'; + in = buf; + + /* skip comments */ + if (*in == '#') + continue; + + /* skip empty lines. If we are in a rule, end the rule. */ + while(*in && isspace(*in)) + in++; + if (*in == '\0') { + if (inrule) + { + /* End the rule : leaf or non-leaf*/ + if (leaf) + ProcessCategoryLeafRule(&rule, &g->mCategoryRoot, g); + else + /* non-leaf */ + ProcessCategoryParentRule(&rule, &g->mCategoryRoot, g); + inrule = PR_FALSE; + memset(&rule, 0, sizeof(rule)); + } + continue; + } + + /* if we are in a rule acculumate */ + if (inrule) + { + rule.pats[rule.npats++] = strdup(in); + } + else if (*in == '<') + { + /* Start a category */ + inrule = PR_TRUE; + leaf = PR_TRUE; + + /* Get the category name */ + in++; + n = strlen(in); + if (in[n-1] == '>') + in[n-1] = '\0'; + rule.categoryName = strdup(in); + } + else + { + /* this is a non-leaf category. Should be of the form CategoryName + ** followed by list of child category names one per line + */ + inrule = PR_TRUE; + leaf = PR_FALSE; + rule.categoryName = strdup(in); + } + } + + /* If we were in a rule when processing the last line, end the rule */ + if (inrule) + { + /* End the rule : leaf or non-leaf*/ + if (leaf) + ProcessCategoryLeafRule(&rule, &g->mCategoryRoot, g); + else + /* non-leaf */ + ProcessCategoryParentRule(&rule, &g->mCategoryRoot, g); + } + + /* Add the final "uncategorized" category. We make new memory locations + ** for all these to conform to the general principle of all strings are allocated + ** so it makes release logic very simple. + */ + memset(&rule, 0, sizeof(rule)); + rule.categoryName = strdup("uncategorized"); + rule.pats[0] = strdup(""); + rule.npats = 1; + ProcessCategoryLeafRule(&rule, &g->mCategoryRoot, g); + + return 0; +} + +/* +** callsiteMatchesRule +** +** Returns the corresponding node if callsite matches the rule. Rule is a sequence +** of patterns that must match contiguously the callsite. +*/ +int callsiteMatchesRule(tmcallsite* aCallsite, STCategoryRule* aRule) +{ + PRUint32 patnum = 0; + const char *methodName = NULL; + + while (patnum < aRule->npats && aCallsite && aCallsite->method) + { + methodName = tmmethodnode_name(aCallsite->method); + if (!methodName) + return 0; + if (!*aRule->pats[patnum] || strstr(methodName, aRule->pats[patnum])) + { + /* We have matched so far. Proceed up the stack and to the next pattern */ + patnum++; + aCallsite = aCallsite->parent; + } + else + { + /* Deal with mismatch */ + if (patnum > 0) + { + /* contiguous mismatch. Stop */ + return 0; + } + /* We still haven't matched the first pattern. Proceed up the stack without + ** moving to the next pattern. + */ + aCallsite = aCallsite->parent; + } + } + + if (patnum == aRule->npats) + { + /* all patterns matched. We have a winner. */ +#if defined(DEBUG_dp) && 0 + fprintf(stderr, "[%s] match\n", aRule->categoryName); +#endif + return 1; + } + + return 0; +} + +/* +** matchAllocation +** +** Runs through all rules and returns the node corresponding to +** a match of the allocation. +*/ +STCategoryNode* matchAllocation(STGlobals* g, STAllocation* aAllocation) +{ + PRUint32 rulenum; + int patnum; + int pos, firstpos; + STCategoryNode* node; + STCategoryRule* rule; + + for (rulenum = 0; rulenum < g->mNRules; rulenum++) + { + rule = g->mCategoryRules[rulenum]; + if (callsiteMatchesRule(aAllocation->mEvents[0].mCallsite, rule)) + return rule->node; + } + return NULL; +} + +/* +** categorizeAllocation +** +** Given an allocation, it adds it into the category tree at the right spot +** by comparing the allocation to the rules and adding into the right node. +** Also, does propogation of cost upwards in the tree. +** The root of the tree is in the globls as the tree is dependent on the +** category file (options) rather than the run. +*/ +int categorizeAllocation(STAllocation* aAllocation, STGlobals* g) +{ + /* Run through the rules in order to see if this allcation matches + ** any of them. + */ + STCategoryNode* node; + + node = matchAllocation(g, aAllocation); + if (!node) + { + /* ugh! it should atleast go into the "uncategorized" node. wierd! + */ + REPORT_ERROR(__LINE__, categorizeAllocation); + return -1; + } + + /* Create run for node if not already */ + if (!node->run) + { + /* + ** Create run with positive timestamp as we can harvest it later + ** for callsite details summarization + */ + node->run = createRun(PR_IntervalNow()); + if (!node->run) + { + REPORT_ERROR(__LINE__, categorizeAllocation_No_Memory); + return -1; + } + } + + /* Add allocation into node. We expand the table of allocations in steps */ + if (node->run->mAllocationCount % ST_ALLOC_STEP == 0) + { + /* Need more space */ + STAllocation** allocs; + allocs = (STAllocation**) realloc(node->run->mAllocations, + (node->run->mAllocationCount + ST_ALLOC_STEP) * sizeof(STAllocation*)); + if (!allocs) + { + REPORT_ERROR(__LINE__, categorizeAllocation_No_Memory); + return -1; + } + node->run->mAllocations = allocs; + } + node->run->mAllocations[node->run->mAllocationCount++] = aAllocation; + + /* Propogate upwards the statistics */ + /* XXX */ +#if defined(DEBUG_dp) && 0 + fprintf(stderr, "DEBUG: [%s] match\n", node->categoryName); +#endif + return 0; +} + +typedef PRBool STCategoryNodeProcessor(void* clientData, STCategoryNode* node); + +PRBool freeNodeRunProcessor(void* clientData, STCategoryNode* node) +{ + if (node->run) + { + freeRun(node->run); + node->run = NULL; + } + return PR_TRUE; +} + +#if defined(DEBUG_dp) +PRBool printNodeProcessor(void* clientData, STCategoryNode* node) +{ + if (node->nchildren) + fprintf(stderr, "%s [%d children]\n", node->categoryName, node->nchildren); + else + fprintf(stderr, "%s [%d allocations]\n", node->categoryName, node->run ? node->run->mAllocationCount:0); + return PR_TRUE; +} + +#endif + +/* +** walkTree +** +** General purpose tree walker. Issues callback for each node +*/ +#define MODINC(n, mod) ((n+1) % mod) + +void walkTree(STCategoryNode* root, STCategoryNodeProcessor func, void *clientData) +{ + STCategoryNode* nodes[1024], *node; + PRUint32 begin, end, i; + int ret = 0; + + nodes[0] = root; + begin = 0; end = 1; + while (begin != end) + { + node = nodes[begin]; + ret = (*func)(clientData, node); + if (ret == PR_FALSE) + { + /* Abort */ + break; + } + begin = MODINC(begin, 1024); + for (i = 0; i < node->nchildren; i++) + { + nodes[end] = node->children[i]; + end = MODINC(end, 1024); + } + } + return; +} + +int freeRule(STCategoryRule* rule) +{ + PRUint32 i; + char *p = (char *)rule->categoryName; + PR_FREEIF(p); + + for (i = 0; i < rule->npats; i++) + free(rule->pats[i]); + + free(rule); + return 0; +} + +void freeNodeRun(STCategoryNode* root) +{ + walkTree(root, freeNodeRunProcessor, NULL); +} + +void freeNodeMap(STGlobals* g) +{ + PRUint32 i; + + /* all nodes are in the map table. Just delete all of those. */ + for(i = 0; i < g->mNCategoryMap; i++) + { + free(g->mCategoryMap[i]->node); + free(g->mCategoryMap[i]); + } + free(g->mCategoryMap); +} + +int freeCategories(STGlobals* g) +{ + PRUint32 i; + + /* + ** walk the tree and free runs held in nodes + */ + freeNodeRun(&g->mCategoryRoot); + + /* + ** delete nodemap. This is the where nodes get deleted. + */ + freeNodeMap(g); + + /* + ** delete rule stuff + */ + for (i = 0; i < g->mNRules; i++) + { + freeRule(g->mCategoryRules[i]); + } + free(g->mCategoryRules); + + return 0; +} + + +/* +** categorizeRun +** +** categorize all the allocations of the run using the rules into +** a tree rooted at globls.mCategoryRoot +*/ +int categorizeRun(const STRun* aRun, STGlobals* g) +{ + PRUint32 i; +#if defined(DEBUG_dp) + PRIntervalTime start = PR_IntervalNow(); + fprintf(stderr, "DEBUG: categorizing run...\n"); +#endif + + /* + ** First, cleanup our tree + */ + walkTree(&g->mCategoryRoot, freeNodeRunProcessor, NULL); + + for (i = 0; i < aRun->mAllocationCount; i++) + { + categorizeAllocation(aRun->mAllocations[i], g); + } + + /* + ** the run is always going to be the one corresponding to the root node + */ + g->mCategoryRoot.run = (STRun *) aRun; + g->mCategoryRoot.categoryName = ST_ROOT_CATEGORY_NAME; + +#if defined(DEBUG_dp) + walkTree(&g->mCategoryRoot, printNodeProcessor, NULL); + fprintf(stderr, "DEBUG: categorizing ends: %dms [%d rules, %d allocations]\n", + PR_IntervalToMilliseconds(PR_IntervalNow() - start), g->mNRules, aRun->mAllocationCount); +#endif + + return 0; +} + diff --git a/mozilla/tools/trace-malloc/spacetrace.c b/mozilla/tools/trace-malloc/spacetrace.c index bfde90ab92a..ef7c7bf4cbc 100644 --- a/mozilla/tools/trace-malloc/spacetrace.c +++ b/mozilla/tools/trace-malloc/spacetrace.c @@ -370,6 +370,16 @@ int initOptions(int aArgCount, char** aArgArray) globals.mOptions.mAlignBy = ST_DEFAULT_ALIGNMENT_SIZE; globals.mOptions.mOverhead = ST_DEFAULT_OVERHEAD_SIZE; + /* + ** category file + **/ + globals.mOptions.mCategoryFile = "rules.txt"; + + /* + ** Default category name - name of root category + */ + globals.mOptions.mCategoryName = ST_ROOT_CATEGORY_NAME; + /* ** Go through all arguments. ** If argument does not being with a dash it is a file name. @@ -874,6 +884,11 @@ int initOptions(int aArgCount, char** aArgArray) } } + /* + ** initalize the categories + */ + initCategories(&globals); + return retval; } @@ -1211,6 +1226,104 @@ PRUint32 byteSize(STAllocation* aAlloc) } +/* +** recalculateAllocationCost +** +** Given an allocation, does a recalculation of Cost - weight, heapcount etc. +** and does the right thing to propogate the cost upwards. +*/ +int recalculateAllocationCost(STRun* aRun, STAllocation* aAllocation) +{ + /* + ** Now, see if they desire a callsite update. + ** As mentioned previously, we decide if the run desires us to + ** manipulate the callsite data only if it's stamp is set. + ** We change all callsites and parent callsites to have that + ** stamp as well, so as to mark them as being relevant to + ** the current run in question. + */ + if(0 != aRun->mStats.mStamp) + { + PRUint32 timeval = aAllocation->mMaxTimeval - aAllocation->mMinTimeval; + PRUint32 size = byteSize(aAllocation); + PRUint64 weight64 = LL_INIT(0, 0); + PRUint32 heapCost = aAllocation->mHeapRuntimeCost; + PRUint64 timeval64 = LL_INIT(0, 0); + PRUint64 size64 = LL_INIT(0, 0); + + LL_UI2L(timeval64, timeval); + LL_UI2L(size64, size); + LL_MUL(weight64, timeval64, size64); + + /* + ** First, update this run. + */ + aRun->mStats.mCompositeCount++; + aRun->mStats.mHeapRuntimeCost += heapCost; + aRun->mStats.mSize += size; + LL_ADD(aRun->mStats.mTimeval64, aRun->mStats.mTimeval64, timeval64); + LL_ADD(aRun->mStats.mWeight64, aRun->mStats.mWeight64, weight64); + + /* + ** Use the first event of the allocation to update the parent + ** callsites. + ** This has positive effect of not updating realloc callsites + ** with the same data over and over again. + */ + if(0 < aAllocation->mEventCount) + { + tmcallsite* callsite = aAllocation->mEvents[0].mCallsite; + STRun* callsiteRun = NULL; + + /* + ** Go up parents till we drop. + */ + while(NULL != callsite && NULL != callsite->method) + { + callsiteRun = CALLSITE_RUN(callsite); + if(NULL != callsiteRun) + { + /* + ** Do we init it? + */ + if(callsiteRun->mStats.mStamp != aRun->mStats.mStamp) + { + memset(&callsiteRun->mStats, 0, sizeof(STCallsiteStats)); + callsiteRun->mStats.mStamp = aRun->mStats.mStamp; + } + + /* + ** Add the values. + ** Note that if the allocation was ever realloced, + ** we are actually recording the final size. + ** Also, the composite count does not include + ** calls to realloc (or free for that matter), + ** but rather is simply a count of actual heap + ** allocation objects, from which someone will + ** draw conclusions regarding number of malloc + ** and free calls. + ** It is possible to generate the exact number + ** of calls to free/malloc/realloc should the + ** absolute need arise to count them individually, + ** but I fear it will take mucho memory and this + ** is perhaps good enough for now. + */ + callsiteRun->mStats.mCompositeCount++; + callsiteRun->mStats.mHeapRuntimeCost += heapCost; + callsiteRun->mStats.mSize += size; + LL_ADD(callsiteRun->mStats.mTimeval64, callsiteRun->mStats.mTimeval64, timeval64); + LL_ADD(callsiteRun->mStats.mWeight64, callsiteRun->mStats.mWeight64, weight64); + } + + callsite = callsite->parent; + } + } + } + + return 0; +} + + /* ** appendAllocation ** @@ -1266,90 +1379,9 @@ int appendAllocation(STRun* aRun, STAllocation* aAllocation) retval = __LINE__; /* - ** Now, see if they desire a callsite update. - ** As mentioned previously, we decide if the run desires us to - ** manipulate the callsite data only if it's stamp is set. - ** We change all callsites and parent callsites to have that - ** stamp as well, so as to mark them as being relevant to - ** the current run in question. + ** update allocation cost */ - if(0 != aRun->mStats.mStamp) - { - PRUint32 timeval = aAllocation->mMaxTimeval - aAllocation->mMinTimeval; - PRUint32 size = byteSize(aAllocation); - PRUint64 weight64 = LL_INIT(0, 0); - PRUint32 heapCost = aAllocation->mHeapRuntimeCost; - PRUint64 timeval64 = LL_INIT(0, 0); - PRUint64 size64 = LL_INIT(0, 0); - - LL_UI2L(timeval64, timeval); - LL_UI2L(size64, size); - LL_MUL(weight64, timeval64, size64); - - /* - ** First, update this run. - */ - aRun->mStats.mCompositeCount++; - aRun->mStats.mHeapRuntimeCost += heapCost; - aRun->mStats.mSize += size; - LL_ADD(aRun->mStats.mTimeval64, aRun->mStats.mTimeval64, timeval64); - LL_ADD(aRun->mStats.mWeight64, aRun->mStats.mWeight64, weight64); - - /* - ** Use the first event of the allocation to update the parent - ** callsites. - ** This has positive effect of not updating realloc callsites - ** with the same data over and over again. - */ - if(0 < aAllocation->mEventCount) - { - tmcallsite* callsite = aAllocation->mEvents[0].mCallsite; - STRun* callsiteRun = NULL; - - /* - ** Go up parents till we drop. - */ - while(NULL != callsite && NULL != callsite->method) - { - callsiteRun = CALLSITE_RUN(callsite); - if(NULL != callsiteRun) - { - /* - ** Do we init it? - */ - if(callsiteRun->mStats.mStamp != aRun->mStats.mStamp) - { - memset(&callsiteRun->mStats, 0, sizeof(STCallsiteStats)); - callsiteRun->mStats.mStamp = aRun->mStats.mStamp; - } - - /* - ** Add the values. - ** Note that if the allocation was ever realloced, - ** we are actually recording the final size. - ** Also, the composite count does not include - ** calls to realloc (or free for that matter), - ** but rather is simply a count of actual heap - ** allocation objects, from which someone will - ** draw conclusions regarding number of malloc - ** and free calls. - ** It is possible to generate the exact number - ** of calls to free/malloc/realloc should the - ** absolute need arise to count them individually, - ** but I fear it will take mucho memory and this - ** is perhaps good enough for now. - */ - callsiteRun->mStats.mCompositeCount++; - callsiteRun->mStats.mHeapRuntimeCost += heapCost; - callsiteRun->mStats.mSize += size; - LL_ADD(callsiteRun->mStats.mTimeval64, callsiteRun->mStats.mTimeval64, timeval64); - LL_ADD(callsiteRun->mStats.mWeight64, callsiteRun->mStats.mWeight64, weight64); - } - - callsite = callsite->parent; - } - } - } + recalculateAllocationCost(aRun, aAllocation); } else { @@ -1438,6 +1470,11 @@ int harvestRun(const STRun* aInRun, STRun* aOutRun, STOptions* aOptions) { int retval = 0; +#if defined(DEBUG_dp) + PRIntervalTime start = PR_IntervalNow(); + fprintf(stderr, "DEBUG: harvesting run...\n"); +#endif + if(NULL != aInRun && NULL != aOutRun && aInRun != aOutRun) { PRUint32 traverse = 0; @@ -1588,9 +1625,56 @@ int harvestRun(const STRun* aInRun, STRun* aOutRun, STOptions* aOptions) } } +#if defined(DEBUG_dp) + fprintf(stderr, "DEBUG: harvesting ends: %dms [%d allocations]\n", + PR_IntervalToMilliseconds(PR_IntervalNow() - start), aInRun->mAllocationCount); +#endif return retval; } +/* +** recalculateRunCost +** +** Goes over all allocations of a run and recalculates and propogates +** the allocation costs - weight, heapcount, size +*/ +int recalculateRunCost(STRun* aRun) +{ + PRUint32 traverse = 0; + STAllocation* current = NULL; + +#if defined(DEBUG_dp) + PRIntervalTime start = PR_IntervalNow(); + fprintf(stderr, "DEBUG: recalculateRunCost...\n"); +#endif + + if (NULL == aRun) + return -1; + + /* reset stats of this run to 0 to begin recalculation */ + memset(&aRun->mStats, 0, sizeof(STCallsiteStats)); + + /* reset timestamp to force propogation of cost */ + aRun->mStats.mStamp = PR_IntervalNow(); + + for(traverse = 0; traverse < aRun->mAllocationCount; traverse++) + { + current = aRun->mAllocations[traverse]; + if(NULL != current) + { + recalculateAllocationCost(aRun, current); + } + } + +#if defined(DEBUG_dp) + fprintf(stderr, "DEBUG: recalculateRunCost ends: %dms [%d allocations]\n", + PR_IntervalToMilliseconds(PR_IntervalNow() - start), aRun->mAllocationCount); +#endif + + return 0; +} + + /* ** compareAllocations ** @@ -1817,6 +1901,7 @@ STRun* createRunFromGlobal(void) failure = __LINE__; } + if(0 != failure) { freeRun(retval); @@ -1824,6 +1909,32 @@ STRun* createRunFromGlobal(void) REPORT_ERROR(failure, createRunFromGlobal); } + + /* + ** Categorize the run + */ + failure = categorizeRun(retval, &globals); + if (0 != failure) + { + REPORT_ERROR(__LINE__, appendAllocation); + } + + /* + ** if we are focussing on a category, return that run instead of + ** the harvested run. Make sure to recalculate cost. + */ + if (globals.mOptions.mCategoryName) + { + STCategoryNode* node = findCategoryNode(globals.mOptions.mCategoryName, &globals); + if (node && node->run) + { + /* Recalculate cost of run */ + recalculateRunCost(node->run); + + retval = node->run; + } + } + } return retval; @@ -2748,10 +2859,19 @@ void htmlHeader(const char* aTitle) "\n" "\n" "
\n" - , aTitle); +"\n" + , aTitle, + globals.mOptions.mCategoryName); + PR_fprintf(globals.mRequest.mFD,"\n"); + + PR_fprintf(globals.mRequest.mFD,"\n"); + + PR_fprintf(globals.mRequest.mFD,"
Category: %s"); htmlAnchor("index.html", "[Index]", NULL); + PR_fprintf(globals.mRequest.mFD,""); htmlAnchor("options.html", "[Options]", NULL); + PR_fprintf(globals.mRequest.mFD,"
\n"); /* ** This is a dubious feature at best. @@ -4913,6 +5033,112 @@ int graphWeight(STRun* aRun) } #endif /* WANT_GRAPHS */ +/* +** applySettings +** +** Apply settings and update global options. +** Returns 0 on success. Nonzero on failure. +*/ +int applySettings(void) +{ + int getRes = 0; + int changedSet = 0; + int changedOrder = 0; + int changedGraph = 0; + int changedDontCare = 0; + char looper_buf[32]; + PRIntn looper = 0; + + /* + ** If we've got get data, we need to attempt to enact the changes. + ** That way, when we show the page, it will have the new changes. + */ + if(NULL == globals.mRequest.mGetData || '\0' == *globals.mRequest.mGetData) + return 0; + + + getRes += getDataPRUint32(globals.mRequest.mGetData, "mListItemMax", &globals.mOptions.mListItemMax, &changedDontCare, 1); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mTimevalMin", &globals.mOptions.mTimevalMin, &changedSet, ST_TIMEVAL_RESOLUTION); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mTimevalMax", &globals.mOptions.mTimevalMax, &changedSet, ST_TIMEVAL_RESOLUTION); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mAllocationTimevalMin", &globals.mOptions.mAllocationTimevalMin, &changedSet, ST_TIMEVAL_RESOLUTION); + + getRes += getDataPRUint32(globals.mRequest.mGetData, "mAllocationTimevalMax", &globals.mOptions.mAllocationTimevalMax, &changedSet, ST_TIMEVAL_RESOLUTION); + +#if WANT_GRAPHS + getRes += getDataPRUint32(globals.mRequest.mGetData, "mGraphTimevalMin", &globals.mOptions.mGraphTimevalMin, &changedGraph, ST_TIMEVAL_RESOLUTION); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mGraphTimevalMax", &globals.mOptions.mGraphTimevalMax, &changedGraph, ST_TIMEVAL_RESOLUTION); +#endif /* WANT_GRAPHS */ + getRes += getDataPRUint32(globals.mRequest.mGetData, "mSizeMin", &globals.mOptions.mSizeMin, &changedSet, 1); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mSizeMax", &globals.mOptions.mSizeMax, &changedSet, 1); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mAlignBy", &globals.mOptions.mAlignBy, &changedSet, 1); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mOverhead", &globals.mOptions.mOverhead, &changedSet, 1); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mOrderBy", &globals.mOptions.mOrderBy, &changedOrder, 1); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mLifetimeMin", &globals.mOptions.mLifetimeMin, &changedSet, ST_TIMEVAL_RESOLUTION); + getRes += getDataPRUint32(globals.mRequest.mGetData, "mLifetimeMax", &globals.mOptions.mLifetimeMax, &changedSet, ST_TIMEVAL_RESOLUTION); + getRes += getDataPRUint64(globals.mRequest.mGetData, "mWeightMin", &globals.mOptions.mWeightMin64, &changedSet); + getRes += getDataPRUint64(globals.mRequest.mGetData, "mWeightMax", &globals.mOptions.mWeightMax64, &changedSet); + for(looper = 0; ST_SUBSTRING_MATCH_MAX > looper; looper++) + { + PR_snprintf(looper_buf, sizeof(looper_buf), "mRestrictText%d", looper); + getRes += getDataString(globals.mRequest.mGetData, looper_buf, &globals.mOptions.mRestrictText[looper], &changedSet); + } + getRes += getDataString(globals.mRequest.mGetData, "mCategoryName", &globals.mOptions.mCategoryName, &changedSet); + + /* + ** Sanity check options + */ + if (!globals.mOptions.mCategoryName || !*globals.mOptions.mCategoryName) + { + globals.mOptions.mCategoryName = ST_ROOT_CATEGORY_NAME; + } + else if (!findCategoryNode(globals.mOptions.mCategoryName, &globals)) + { + /* Category node is invalid. Reset to root. */ + globals.mOptions.mCategoryName = ST_ROOT_CATEGORY_NAME; + } + + /* + ** Resort the global based on new prefs if needed. + */ + if(0 != changedSet || 0 != changedOrder) + { + /* + ** Dont free globals.mCache.mSortedRun anymore. It is held in the root category node. + ** It will get freed automatically when categorization happens. + */ + globals.mCache.mSortedRun = createRunFromGlobal(); + if(NULL == globals.mCache.mSortedRun) + { + getRes = __LINE__; + REPORT_ERROR(__LINE__, createRunFromGlobal); + } + } + +#if WANT_GRAPHS + /* + ** If any of the set was changed, we need to throw away all our + ** cached graphs. + */ + if(0 != changedSet || 0 != changedGraph) + { + /* + ** Automove the graph timeval if required. + */ + if((globals.mMaxTimeval - globals.mMinTimeval) < globals.mOptions.mGraphTimevalMax) + { + globals.mOptions.mGraphTimevalMax = (globals.mMaxTimeval - globals.mMinTimeval); + } + + globals.mCache.mFootprintCached = 0; + globals.mCache.mTimevalCached = 0; + globals.mCache.mLifespanCached = 0; + globals.mCache.mWeightCached = 0; + } +#endif /* WANT_GRAPHS */ + + return getRes; +} + /* ** displaySettings ** @@ -4926,100 +5152,30 @@ int displaySettings(void) int retval = 0; PRUint32 cached = 0; PRIntn looper = 0; + int getRes; + + getRes = applySettings(); /* - ** If we've got get data, we need to attempt to enact the changes. - ** That way, when we show the page, it will have the new changes. + ** Display header for settings page. Do this after applying settings. + ** */ - if(NULL != globals.mRequest.mGetData && '\0' != *globals.mRequest.mGetData) + htmlHeader("SpaceTrace Settings"); + + /* + ** Report on the operation. + */ + if(0 != getRes) { - int getRes = 0; - int changedSet = 0; - int changedOrder = 0; - int changedGraph = 0; - int changedDontCare = 0; - char looper_buf[32]; - - getRes += getDataPRUint32(globals.mRequest.mGetData, "mListItemMax", &globals.mOptions.mListItemMax, &changedDontCare, 1); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mTimevalMin", &globals.mOptions.mTimevalMin, &changedSet, ST_TIMEVAL_RESOLUTION); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mTimevalMax", &globals.mOptions.mTimevalMax, &changedSet, ST_TIMEVAL_RESOLUTION); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mAllocationTimevalMin", &globals.mOptions.mAllocationTimevalMin, &changedSet, ST_TIMEVAL_RESOLUTION); - - getRes += getDataPRUint32(globals.mRequest.mGetData, "mAllocationTimevalMax", &globals.mOptions.mAllocationTimevalMax, &changedSet, ST_TIMEVAL_RESOLUTION); - -#if WANT_GRAPHS - getRes += getDataPRUint32(globals.mRequest.mGetData, "mGraphTimevalMin", &globals.mOptions.mGraphTimevalMin, &changedGraph, ST_TIMEVAL_RESOLUTION); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mGraphTimevalMax", &globals.mOptions.mGraphTimevalMax, &changedGraph, ST_TIMEVAL_RESOLUTION); -#endif /* WANT_GRAPHS */ - getRes += getDataPRUint32(globals.mRequest.mGetData, "mSizeMin", &globals.mOptions.mSizeMin, &changedSet, 1); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mSizeMax", &globals.mOptions.mSizeMax, &changedSet, 1); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mAlignBy", &globals.mOptions.mAlignBy, &changedSet, 1); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mOverhead", &globals.mOptions.mOverhead, &changedSet, 1); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mOrderBy", &globals.mOptions.mOrderBy, &changedOrder, 1); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mLifetimeMin", &globals.mOptions.mLifetimeMin, &changedSet, ST_TIMEVAL_RESOLUTION); - getRes += getDataPRUint32(globals.mRequest.mGetData, "mLifetimeMax", &globals.mOptions.mLifetimeMax, &changedSet, ST_TIMEVAL_RESOLUTION); - getRes += getDataPRUint64(globals.mRequest.mGetData, "mWeightMin", &globals.mOptions.mWeightMin64, &changedSet); - getRes += getDataPRUint64(globals.mRequest.mGetData, "mWeightMax", &globals.mOptions.mWeightMax64, &changedSet); - for(looper = 0; ST_SUBSTRING_MATCH_MAX > looper; looper++) - { - PR_snprintf(looper_buf, sizeof(looper_buf), "mRestrictText%d", looper); - getRes += getDataString(globals.mRequest.mGetData, looper_buf, &globals.mOptions.mRestrictText[looper], &changedSet); - } - - /* - ** Resort the global based on new prefs if needed. - */ - if(0 != changedSet || 0 != changedOrder) - { - if(NULL != globals.mCache.mSortedRun) - { - freeRun(globals.mCache.mSortedRun); - } - globals.mCache.mSortedRun = createRunFromGlobal(); - if(NULL == globals.mCache.mSortedRun) - { - retval = __LINE__; - REPORT_ERROR(__LINE__, createRunFromGlobal); - } - } - -#if WANT_GRAPHS - /* - ** If any of the set was changed, we need to throw away all our - ** cached graphs. - */ - if(0 != changedSet || 0 != changedGraph) - { - /* - ** Automove the graph timeval if required. - */ - if((globals.mMaxTimeval - globals.mMinTimeval) < globals.mOptions.mGraphTimevalMax) - { - globals.mOptions.mGraphTimevalMax = (globals.mMaxTimeval - globals.mMinTimeval); - } - - globals.mCache.mFootprintCached = 0; - globals.mCache.mTimevalCached = 0; - globals.mCache.mLifespanCached = 0; - globals.mCache.mWeightCached = 0; - } -#endif /* WANT_GRAPHS */ - - /* - ** Report on the operation. - */ - if(0 != getRes) - { - retval = __LINE__; - REPORT_ERROR(__LINE__, getDataPRUint32); - - PR_fprintf(globals.mRequest.mFD, "%u: There was a problem. Some changes may have been applied.

\n", PR_IntervalNow()); - } - else - { - PR_fprintf(globals.mRequest.mFD, "%u: Your changes have been applied.

\n", PR_IntervalNow()); - } + REPORT_ERROR(getRes, applySettings); + + PR_fprintf(globals.mRequest.mFD, "%u: There was a problem. Some changes may have been applied.

\n", PR_IntervalNow()); } + else + { + PR_fprintf(globals.mRequest.mFD, "%u: Your changes have been applied.

\n", PR_IntervalNow()); + } + /* ** A small blurb regarding the options. @@ -5116,6 +5272,14 @@ int displaySettings(void) } PR_fprintf(globals.mRequest.mFD, "
\n"); + /* + ** category selection + */ + PR_fprintf(globals.mRequest.mFD, "By giving a category name, you can focus on allocations belonging to that particular category only like css, js, xpcom. Until we have a flashy new ui to browse categories, use a category name in rules.txt

\n"); + PR_fprintf(globals.mRequest.mFD, "Category to Focus on?
\n"); + PR_fprintf(globals.mRequest.mFD, "
\n", globals.mOptions.mCategoryName); + PR_fprintf(globals.mRequest.mFD, "


\n"); + /* ** And last but not least, the submission button. */ @@ -5222,8 +5386,6 @@ int handleRequest(tmreader* aTMR, PRFileDesc* aFD, const char* aFileName, const { int settingsRes = 0; - htmlHeader("SpaceTrace Settings"); - settingsRes = displaySettings(); if(0 != settingsRes) { @@ -5915,7 +6077,15 @@ int doRun(void) int tmResult = 0; int outputResult = 0; +#if defined(DEBUG_dp) + PRIntervalTime start = PR_IntervalNow(); + fprintf(stderr, "DEBUG: reading tracemalloc data...\n"); +#endif tmResult = tmreader_eventloop(tmr, globals.mOptions.mFileName, tmEventHandler); +#if defined(DEBUG_dp) + fprintf(stderr, "DEBUG: reading tracemalloc data ends: %dms [%d allocations]\n", + PR_IntervalToMilliseconds(PR_IntervalNow() - start), globals.mRun.mAllocationCount); +#endif if(0 == tmResult) { REPORT_ERROR(__LINE__, tmreader_eventloop); @@ -5937,10 +6107,7 @@ int doRun(void) /* ** Create the default sorted run. */ - if(NULL != globals.mCache.mSortedRun) - { - freeRun(globals.mCache.mSortedRun); - } + /* NS_ASSERTION(globals.mCache.mSortedRun == NULL, "mSortedRun not null"); */ globals.mCache.mSortedRun = createRunFromGlobal(); if(NULL != globals.mCache.mSortedRun) { @@ -5979,11 +6146,12 @@ int doRun(void) ** Check for NULL again, may have been realloced at some ** point with failure. */ - if(NULL != globals.mCache.mSortedRun) - { - freeRun(globals.mCache.mSortedRun); - globals.mCache.mSortedRun = NULL; - } + globals.mCache.mSortedRun = NULL; + + /* + ** Clear our categorization tree + */ + freeCategories(&globals); } else { diff --git a/mozilla/tools/trace-malloc/spacetrace.h b/mozilla/tools/trace-malloc/spacetrace.h index c8af8786e39..6fa62064da4 100644 --- a/mozilla/tools/trace-malloc/spacetrace.h +++ b/mozilla/tools/trace-malloc/spacetrace.h @@ -20,6 +20,7 @@ * * Contributor(s): * Garrett Arch Blythe, 31-October-2001 + * Suresh Duddi * * Alternatively, the contents of this file may be used under the * terms of the GNU Public License (the "GPL"), in which case the @@ -120,6 +121,21 @@ */ #define ST_SUBSTRING_MATCH_MAX 5 +/* +** Max Number of patterns per rule +*/ +#define ST_MAX_PATTERNS_PER_RULE 16 + +/* +** Rule pointers and child pointers are allocated in steps of ST_ALLOC_STEP +*/ +#define ST_ALLOC_STEP 16 + +/* +** Name of the root category. Appears in UI. +*/ +#define ST_ROOT_CATEGORY_NAME "All" + /* ** Set the desired resolution of the timevals. ** The resolution is just mimicking what is recorded in the trace-malloc @@ -137,6 +153,13 @@ #define ST_MICROVAL_PRINTABLE64(timeval) ((PRFloat64)((PRInt64)(timeval)) / (PRFloat64)ST_MICROVAL_RESOLUTION) #define ST_MICROVAL_MAX ((PRUint32)-1 - ((PRUint32)-1 % ST_MICROVAL_RESOLUTION)) +/* +** Forward Declaration +*/ +typedef struct __struct_STCategoryNode STCategoryNode; +typedef struct __struct_STCategoryRule STCategoryRule; + + /* ** STAllocEvent ** @@ -285,8 +308,79 @@ typedef struct __struct_STRun ** Callsites like to keep some information. */ STCallsiteStats mStats; + } STRun; +/* +** Categorize allocations +** +** The objective is to have a tree of categories with each leaf node of the tree +** matching a set of callsites that belong to the category. Each category can +** signify a functional area like say css and hence the user can browse this +** tree looking for how much of each of these are live at an instant. +*/ + +/* +** STCategoryNode +*/ + +struct __struct_STCategoryNode +{ + /* + ** Category name + */ + const char *categoryName; + + /* + ** Pointer to parent node. NULL for Root. + */ + STCategoryNode *parent; + + /* + ** For non-leaf nodes, an array of children node pointers. + ** NULL if leaf node. + */ + STCategoryNode** children; + PRUint32 nchildren; + + /* + ** For leaf nodes, the Run + */ + STRun *run; +}; + + +struct __struct_STCategoryRule +{ + /* + ** The pattern for the rule. Patterns are an array of strings. + ** A callsite needs to pass substring match for all the strings. + */ + char* pats[ST_MAX_PATTERNS_PER_RULE]; + PRUint32 npats; + + /* + ** Category name that this rule belongs to + */ + const char* categoryName; + + /* + ** The node this should be categorized into + */ + STCategoryNode* node; + +}; + + +/* +** CategoryName to Node mapping table +*/ +typedef struct __struct_STCategoryMapEntry { + STCategoryNode* node; + const char * categoryName; +} STCategoryMapEntry; + + /* ** STOptions ** @@ -386,6 +480,17 @@ typedef struct __struct_STOptions ** Restrict callsite backtraces to those containing text. */ char* mRestrictText[ST_SUBSTRING_MATCH_MAX]; + + /* + ** File containing rules to categorize allocations + */ + char* mCategoryFile; + + /*n + ** Category to focus report on. NULL if not focussing on a category. + */ + char *mCategoryName; + } STOptions; /* @@ -429,6 +534,11 @@ typedef struct __struct_STCache ** Pre sorted run. */ STRun* mSortedRun; + + /* + ** Category the mSortedRun belongs to. NULL if not to any category. + */ + const char *mCategoryName; /* ** Footprint graph cache. @@ -514,6 +624,33 @@ typedef struct __struct_STGlobals */ PRUint32 mPeakMemoryUsed; PRUint32 mMemoryUsed; + + /* + ** A list of rules for categorization read in from the mCategoryFile + */ + STCategoryRule** mCategoryRules; + PRUint32 mNRules; + + /* + ** CategoryName to Node mapping table + */ + STCategoryMapEntry** mCategoryMap; + PRUint32 mNCategoryMap; + + /* + ** Categorized allocations. For now we support only one tree. + */ + STCategoryNode mCategoryRoot; } STGlobals; + +/* +** Function prototypes +*/ +extern STRun* createRun(PRUint32 aStamp); +extern void freeRun(STRun* aRun); +extern int initCategories(STGlobals* g); +extern int categorizeRun(const STRun* aRun, STGlobals* g); +extern STCategoryNode* findCategoryNode(const char *catName, STGlobals *g); +extern int freeCategories(STGlobals* g); #endif /* spacetrace_h__ */