forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			996 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			996 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 spacecategory.c code, released
 | |
|  * Apr 12, 2002.
 | |
|  *
 | |
|  * The Initial Developer of the Original Code is
 | |
|  * Netscape Communications Corporation.
 | |
|  * Portions created by the Initial Developer are Copyright (C) 2001
 | |
|  * the Initial Developer. All Rights Reserved.
 | |
|  *
 | |
|  * Contributor(s):
 | |
|  *   Suresh Duddi <dp@netscape.com>, 12-April-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 ***** */
 | |
| 
 | |
| /*
 | |
| ** 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 <ctype.h>
 | |
| #include <string.h>
 | |
| 
 | |
| /*
 | |
| ** Ugh, MSVC6's qsort is too slow...
 | |
| */
 | |
| #include "nsQuickSort.h"
 | |
| 
 | |
| #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 <gd.h>
 | |
| #include <gdfontt.h>
 | |
| #include <gdfonts.h>
 | |
| #include <gdfontmb.h>
 | |
| #endif /* HAVE_BOUTELL_GD */
 | |
| 
 | |
| /*
 | |
| ** 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;
 | |
|     }
 | |
| 
 | |
|     /* Check if we are looking for the root node */
 | |
|     if (!strcmp(catName, ST_ROOT_CATEGORY_NAME))
 | |
|         return &g->mCategoryRoot;
 | |
| 
 | |
|     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)
 | |
| {
 | |
|     STCategoryNode *node;
 | |
| 
 | |
|     node = (STCategoryNode *) calloc(1, sizeof(STCategoryNode));
 | |
|     if (!node)
 | |
|         return NULL;
 | |
| 
 | |
|     node->runs =
 | |
|         (STRun **) calloc(g->mCommandLineOptions.mContexts, sizeof(STRun *));
 | |
|     if (NULL == node->runs) {
 | |
|         free(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 doesn't 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;
 | |
|     PRBool inrule, leaf;
 | |
|     STCategoryRule rule;
 | |
| 
 | |
|     fp = fopen(g->mCommandLineOptions.mCategoryFile, "r");
 | |
|     if (!fp) {
 | |
|         /* It isn't an error to not have a categories file */
 | |
|         REPORT_INFO("No categories file.");
 | |
|         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);
 | |
|             rule.patlen[rule.npats++] = strlen(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.patlen[0] = 0;
 | |
|     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]
 | |
|             || !strncmp(methodName, aRule->pats[patnum],
 | |
|                         aRule->patlen[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;
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG_dp
 | |
| PRIntervalTime _gMatchTime = 0;
 | |
| PRUint32 _gMatchCount = 0;
 | |
| PRUint32 _gMatchRules = 0;
 | |
| #endif
 | |
| 
 | |
| /*
 | |
| ** matchAllocation
 | |
| **
 | |
| ** Runs through all rules and returns the node corresponding to
 | |
| ** a match of the allocation.
 | |
| */
 | |
| STCategoryNode *
 | |
| matchAllocation(STGlobals * g, STAllocation * aAllocation)
 | |
| {
 | |
| #ifdef DEBUG_dp
 | |
|     PRIntervalTime start = PR_IntervalNow();
 | |
| #endif
 | |
|     PRUint32 rulenum;
 | |
|     STCategoryNode *node = NULL;
 | |
|     STCategoryRule *rule;
 | |
| 
 | |
|     for (rulenum = 0; rulenum < g->mNRules; rulenum++) {
 | |
| #ifdef DEBUG_dp
 | |
|         _gMatchRules++;
 | |
| #endif
 | |
|         rule = g->mCategoryRules[rulenum];
 | |
|         if (callsiteMatchesRule(aAllocation->mEvents[0].mCallsite, rule)) {
 | |
|             node = rule->node;
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| #ifdef DEBUG_dp
 | |
|     _gMatchCount++;
 | |
|     _gMatchTime += PR_IntervalNow() - start;
 | |
| #endif
 | |
|     return node;
 | |
| }
 | |
| 
 | |
| /*
 | |
| ** 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(STOptions * inOptions, STContext * inContext,
 | |
|                      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->runs[inContext->mIndex]) {
 | |
|         /*
 | |
|          ** Create run with positive timestamp as we can harvest it later
 | |
|          ** for callsite details summarization
 | |
|          */
 | |
|         node->runs[inContext->mIndex] =
 | |
|             createRun(inContext, PR_IntervalNow());
 | |
|         if (!node->runs[inContext->mIndex]) {
 | |
|             REPORT_ERROR(__LINE__, categorizeAllocation_No_Memory);
 | |
|             return -1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Add allocation into node. We expand the table of allocations in steps */
 | |
|     if (node->runs[inContext->mIndex]->mAllocationCount % ST_ALLOC_STEP == 0) {
 | |
|         /* Need more space */
 | |
|         STAllocation **allocs;
 | |
| 
 | |
|         allocs =
 | |
|             (STAllocation **) realloc(node->runs[inContext->mIndex]->
 | |
|                                       mAllocations,
 | |
|                                       (node->runs[inContext->mIndex]->
 | |
|                                        mAllocationCount +
 | |
|                                        ST_ALLOC_STEP) *
 | |
|                                       sizeof(STAllocation *));
 | |
|         if (!allocs) {
 | |
|             REPORT_ERROR(__LINE__, categorizeAllocation_No_Memory);
 | |
|             return -1;
 | |
|         }
 | |
|         node->runs[inContext->mIndex]->mAllocations = allocs;
 | |
|     }
 | |
|     node->runs[inContext->mIndex]->mAllocations[node->
 | |
|                                                 runs[inContext->mIndex]->
 | |
|                                                 mAllocationCount++] =
 | |
|         aAllocation;
 | |
| 
 | |
|     /*
 | |
|      ** Make sure run's stats are calculated. We don't go update the parents of allocation
 | |
|      ** at this time. That will happen when we focus on this category. This updating of
 | |
|      ** stats will provide us fast categoryreports.
 | |
|      */
 | |
|     recalculateAllocationCost(inOptions, inContext,
 | |
|                               node->runs[inContext->mIndex], aAllocation,
 | |
|                               PR_FALSE);
 | |
| 
 | |
|     /* Propagate upwards the statistics */
 | |
|     /* XXX */
 | |
| #if defined(DEBUG_dp) && 0
 | |
|     fprintf(stderr, "DEBUG: [%s] match\n", node->categoryName);
 | |
| #endif
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| typedef PRBool STCategoryNodeProcessor(STRequest * inRequest,
 | |
|                                        STOptions * inOptions,
 | |
|                                        STContext * inContext,
 | |
|                                        void *clientData,
 | |
|                                        STCategoryNode * node);
 | |
| 
 | |
| PRBool
 | |
| freeNodeRunProcessor(STRequest * inRequest, STOptions * inOptions,
 | |
|                      STContext * inContext, void *clientData,
 | |
|                      STCategoryNode * node)
 | |
| {
 | |
|     if (node->runs && node->runs[inContext->mIndex]) {
 | |
|         freeRun(node->runs[inContext->mIndex]);
 | |
|         node->runs[inContext->mIndex] = NULL;
 | |
|     }
 | |
|     return PR_TRUE;
 | |
| }
 | |
| 
 | |
| PRBool
 | |
| freeNodeRunsProcessor(STRequest * inRequest, STOptions * inOptions,
 | |
|                       STContext * inContext, void *clientData,
 | |
|                       STCategoryNode * node)
 | |
| {
 | |
|     if (node->runs) {
 | |
|         PRUint32 loop = 0;
 | |
| 
 | |
|         for (loop = 0; loop < globals.mCommandLineOptions.mContexts; loop++) {
 | |
|             if (node->runs[loop]) {
 | |
|                 freeRun(node->runs[loop]);
 | |
|                 node->runs[loop] = NULL;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         free(node->runs);
 | |
|         node->runs = NULL;
 | |
|     }
 | |
| 
 | |
|     return PR_TRUE;
 | |
| }
 | |
| 
 | |
| #if defined(DEBUG_dp)
 | |
| PRBool
 | |
| printNodeProcessor(STRequest * inRequest, STOptions * inOptions,
 | |
|                    STContext * inContext, void *clientData,
 | |
|                    STCategoryNode * node)
 | |
| {
 | |
|     STCategoryNode *root = (STCategoryNode *) clientData;
 | |
| 
 | |
|     fprintf(stderr, "%-25s [ %9s size", node->categoryName,
 | |
|             FormatNumber(node->run ? node->run->mStats[inContext->mIndex].
 | |
|                          mSize : 0));
 | |
|     fprintf(stderr, ", %5.1f%%",
 | |
|             node->run ? ((double) node->run->mStats[inContext->mIndex].mSize /
 | |
|                          root->run->mStats[inContext->mIndex].mSize *
 | |
|                          100) : 0);
 | |
|     fprintf(stderr, ", %7s allocations ]\n",
 | |
|             FormatNumber(node->run ? node->run->mStats[inContext->mIndex].
 | |
|                          mCompositeCount : 0));
 | |
|     return PR_TRUE;
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| typedef struct __struct_optcon
 | |
| {
 | |
|     STOptions *mOptions;
 | |
|     STContext *mContext;
 | |
| }
 | |
| optcon;
 | |
| 
 | |
| /*
 | |
| ** compareNode
 | |
| **
 | |
| ** qsort callback.
 | |
| ** Compare the nodes as specified by the options.
 | |
| */
 | |
| int
 | |
| compareNode(const void *aNode1, const void *aNode2, void *aContext)
 | |
| {
 | |
|     int retval = 0;
 | |
|     STCategoryNode *node1, *node2;
 | |
|     PRUint32 a, b;
 | |
|     optcon *oc = (optcon *) aContext;
 | |
| 
 | |
|     if (!aNode1 || !aNode2 || !oc->mOptions || !oc->mContext)
 | |
|         return 0;
 | |
| 
 | |
|     node1 = *((STCategoryNode **) aNode1);
 | |
|     node2 = *((STCategoryNode **) aNode2);
 | |
| 
 | |
|     if (node1 && node2) {
 | |
|         if (oc->mOptions->mOrderBy == ST_COUNT) {
 | |
|             a = (node1->runs[oc->mContext->mIndex]) ? node1->runs[oc->
 | |
|                                                                   mContext->
 | |
|                                                                   mIndex]->
 | |
|                 mStats[oc->mContext->mIndex].mCompositeCount : 0;
 | |
|             b = (node2->runs[oc->mContext->mIndex]) ? node2->runs[oc->
 | |
|                                                                   mContext->
 | |
|                                                                   mIndex]->
 | |
|                 mStats[oc->mContext->mIndex].mCompositeCount : 0;
 | |
|         }
 | |
|         else {
 | |
|             /* Default is by size */
 | |
|             a = (node1->runs[oc->mContext->mIndex]) ? node1->runs[oc->
 | |
|                                                                   mContext->
 | |
|                                                                   mIndex]->
 | |
|                 mStats[oc->mContext->mIndex].mSize : 0;
 | |
|             b = (node2->runs[oc->mContext->mIndex]) ? node2->runs[oc->
 | |
|                                                                   mContext->
 | |
|                                                                   mIndex]->
 | |
|                 mStats[oc->mContext->mIndex].mSize : 0;
 | |
|         }
 | |
|         if (a < b)
 | |
|             retval = __LINE__;
 | |
|         else
 | |
|             retval = -__LINE__;
 | |
|     }
 | |
|     return retval;
 | |
| }
 | |
| 
 | |
| PRBool
 | |
| sortNodeProcessor(STRequest * inRequest, STOptions * inOptions,
 | |
|                   STContext * inContext, void *clientData,
 | |
|                   STCategoryNode * node)
 | |
| {
 | |
|     if (node->nchildren) {
 | |
|         optcon context;
 | |
| 
 | |
|         context.mOptions = inOptions;
 | |
|         context.mContext = inContext;
 | |
| 
 | |
|         NS_QuickSort(node->children, node->nchildren,
 | |
|                      sizeof(STCategoryNode *), compareNode, &context);
 | |
|     }
 | |
| 
 | |
|     return PR_TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
| ** walkTree
 | |
| **
 | |
| ** General purpose tree walker. Issues callback for each node.
 | |
| ** If 'maxdepth' > 0, then stops after processing that depth. Root is
 | |
| ** depth 0, the nodes below it are depth 1 etc...
 | |
| */
 | |
| #define MODINC(n, mod) ((n+1) % mod)
 | |
| 
 | |
| void
 | |
| walkTree(STCategoryNode * root, STCategoryNodeProcessor func,
 | |
|          STRequest * inRequest, STOptions * inOptions, STContext * inContext,
 | |
|          void *clientData, int maxdepth)
 | |
| {
 | |
|     STCategoryNode *nodes[1024], *node;
 | |
|     PRUint32 begin, end, i;
 | |
|     int ret = 0;
 | |
|     int curdepth = 0, ncurdepth = 0;
 | |
| 
 | |
|     nodes[0] = root;
 | |
|     begin = 0;
 | |
|     end = 1;
 | |
|     ncurdepth = 1;
 | |
|     while (begin != end) {
 | |
|         node = nodes[begin];
 | |
|         ret = (*func) (inRequest, inOptions, inContext, 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);
 | |
|         }
 | |
|         /* Depth tracking. Do it only if walkTree is contrained by a maxdepth */
 | |
|         if (maxdepth > 0 && --ncurdepth == 0) {
 | |
|             /*
 | |
|              ** No more children in current depth. The rest of the nodes
 | |
|              ** we have in our list should be nodes in the depth below us.
 | |
|              */
 | |
|             ncurdepth = (begin < end) ? (end - begin) : (1024 - begin + end);
 | |
|             if (++curdepth > maxdepth) {
 | |
|                 /*
 | |
|                  ** Gone too deep. Stop.
 | |
|                  */
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     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
 | |
| freeNodeRuns(STCategoryNode * root)
 | |
| {
 | |
|     walkTree(root, freeNodeRunsProcessor, NULL, NULL, NULL, NULL, 0);
 | |
| }
 | |
| 
 | |
| 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
 | |
|      */
 | |
|     freeNodeRuns(&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(STOptions * inOptions, STContext * inContext,
 | |
|               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, inOptions,
 | |
|              inContext, NULL, 0);
 | |
| 
 | |
|     if (g->mNCategoryMap > 0) {
 | |
|         for (i = 0; i < aRun->mAllocationCount; i++) {
 | |
|             categorizeAllocation(inOptions, inContext, aRun->mAllocations[i],
 | |
|                                  g);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      ** the run is always going to be the one corresponding to the root node
 | |
|      */
 | |
|     g->mCategoryRoot.runs[inContext->mIndex] = (STRun *) aRun;
 | |
|     g->mCategoryRoot.categoryName = ST_ROOT_CATEGORY_NAME;
 | |
| 
 | |
| #if defined(DEBUG_dp)
 | |
|     fprintf(stderr,
 | |
|             "DEBUG: categorizing ends: %dms [%d rules, %d allocations]\n",
 | |
|             PR_IntervalToMilliseconds(PR_IntervalNow() - start), g->mNRules,
 | |
|             aRun->mAllocationCount);
 | |
|     fprintf(stderr, "DEBUG: match : %dms [%d calls, %d rule-compares]\n",
 | |
|             PR_IntervalToMilliseconds(_gMatchTime), _gMatchCount,
 | |
|             _gMatchRules);
 | |
| #endif
 | |
| 
 | |
|     /*
 | |
|      ** sort the tree based on our sort criterion
 | |
|      */
 | |
|     walkTree(&g->mCategoryRoot, sortNodeProcessor, NULL, inOptions, inContext,
 | |
|              NULL, 0);
 | |
| 
 | |
| #if defined(DEBUG_dp)
 | |
|     walkTree(&g->mCategoryRoot, printNodeProcessor, NULL, inOptions,
 | |
|              inContext, &g->mCategoryRoot, 0);
 | |
| #endif
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
| ** displayCategoryReport
 | |
| **
 | |
| ** Generate the category report - a list of all categories and details about each
 | |
| ** depth parameter controls how deep we traverse the category tree.
 | |
| */
 | |
| PRBool
 | |
| displayCategoryNodeProcessor(STRequest * inRequest, STOptions * inOptions,
 | |
|                              STContext * inContext, void *clientData,
 | |
|                              STCategoryNode * node)
 | |
| {
 | |
|     STCategoryNode *root = (STCategoryNode *) clientData;
 | |
|     PRUint32 byteSize = 0, heapCost = 0, count = 0;
 | |
|     double percent = 0;
 | |
|     STOptions customOps;
 | |
| 
 | |
|     if (node->runs[inContext->mIndex]) {
 | |
|         /*
 | |
|          ** Byte size
 | |
|          */
 | |
|         byteSize =
 | |
|             node->runs[inContext->mIndex]->mStats[inContext->mIndex].mSize;
 | |
| 
 | |
|         /*
 | |
|          ** Composite count
 | |
|          */
 | |
|         count =
 | |
|             node->runs[inContext->mIndex]->mStats[inContext->mIndex].
 | |
|             mCompositeCount;
 | |
| 
 | |
|         /*
 | |
|          ** Heap operation cost
 | |
|          **/
 | |
|         heapCost =
 | |
|             node->runs[inContext->mIndex]->mStats[inContext->mIndex].
 | |
|             mHeapRuntimeCost;
 | |
| 
 | |
|         /*
 | |
|          ** % of total size
 | |
|          */
 | |
|         if (root->runs[inContext->mIndex]) {
 | |
|             percent =
 | |
|                 ((double) byteSize) /
 | |
|                 root->runs[inContext->mIndex]->mStats[inContext->mIndex].
 | |
|                 mSize * 100;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     PR_fprintf(inRequest->mFD, " <tr>\n" "  <td>");
 | |
| 
 | |
|     /* a link to topcallsites report with focus on category */
 | |
|     memcpy(&customOps, inOptions, sizeof(customOps));
 | |
|     PR_snprintf(customOps.mCategoryName, sizeof(customOps.mCategoryName),
 | |
|                 "%s", node->categoryName);
 | |
| 
 | |
|     htmlAnchor(inRequest, "top_callsites.html", node->categoryName, NULL,
 | |
|                "category-callsites", &customOps);
 | |
|     PR_fprintf(inRequest->mFD,
 | |
|                "</td>\n" "  <td align=right>%u</td>\n"
 | |
|                "  <td align=right>%4.1f%%</td>\n"
 | |
|                "  <td align=right>%u</td>\n" "  <td align=right>"
 | |
|                ST_MICROVAL_FORMAT "</td>\n" " </tr>\n", byteSize, percent,
 | |
|                count, ST_MICROVAL_PRINTABLE(heapCost));
 | |
| 
 | |
|     return PR_TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| int
 | |
| displayCategoryReport(STRequest * inRequest, STCategoryNode * root, int depth)
 | |
| {
 | |
|     PR_fprintf(inRequest->mFD,
 | |
|                "<table class=\"category-list data\">\n"
 | |
|                " <tr class=\"row-header\">\n"
 | |
|                "  <th>Category</th>\n"
 | |
|                "  <th>Composite Byte Size</th>\n"
 | |
|                "  <th>%% of Total Size</th>\n"
 | |
|                "  <th>Heap Object Count</th>\n"
 | |
|                "  <th>Composite Heap Operations Seconds</th>\n" " </tr>\n");
 | |
| 
 | |
|     walkTree(root, displayCategoryNodeProcessor, inRequest,
 | |
|              &inRequest->mOptions, inRequest->mContext, root, depth);
 | |
| 
 | |
|     PR_fprintf(inRequest->mFD, "</table>\n");
 | |
| 
 | |
|     return 0;
 | |
| }
 | 
