tools/trace-malloc/tmreader.c
author Benoit Jacob <bjacob@mozilla.com>
Sun, 04 Dec 2011 14:15:43 -0500
changeset 81418 432d88a73914212f73ebf3fef49925e00aea1b78
parent 41425 8102627c8c6262e502cd071c124a4745fc615a58
child 94475 f4157e8c410708d76703f19e4dfb61859bfe32d8
permissions -rw-r--r--
Bug 707033 - Kill the hashtables of WebGLUniformLocation's held by WebGLProgram's - r=jgilbert Currently, WebGLProgram holds a hashtable of refptrs to all WebGLUniformLocation's created from it. It's used for one thing: to ensure that multiple getUniformLocation() calls on same uniform return the same WebGLUniformLocation object. However, in a discussion on the public_webgl mailing list this week, we agreed that this was not mandated by the spec and that on the contrary, to ensure consistent behavior across browsers, we're going to require that NOT to happen. So this becomes a conformance issue. Removing this hashtable also simplifies code and ownership patterns.

/* -*- 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 tmreader.h/tmreader.c code, released
 * July 7, 2000.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Brendan Eich, 7-July-2000
 *
 * 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 <errno.h>      /* XXX push error reporting out to clients? */
#ifndef XP_WIN
#include <unistd.h>
#else
#include <stddef.h>
#endif
#include "prlog.h"
#include "plhash.h"
/* make sure this happens before tmreader.h */
#define PL_ARENA_CONST_ALIGN_MASK 2
#include "plarena.h"

#include "prnetdb.h"
#include "nsTraceMalloc.h"
#include "tmreader.h"

#undef  DEBUG_tmreader

static int accum_byte(FILE *fp, uint32 *uip)
{
    int c = getc(fp);
    if (c == EOF)
        return 0;
    *uip = (*uip << 8) | c;
    return 1;
}

static int get_uint32(FILE *fp, uint32 *uip)
{
    int c;
    uint32 ui;

    c = getc(fp);
    if (c == EOF)
        return 0;
    ui = 0;
    if (c & 0x80) {
        c &= 0x7f;
        if (c & 0x40) {
            c &= 0x3f;
            if (c & 0x20) {
                c &= 0x1f;
                if (c & 0x10) {
                    if (!accum_byte(fp, &ui))
                        return 0;
                } else {
                    ui = (uint32) c;
                }
                if (!accum_byte(fp, &ui))
                    return 0;
            } else {
                ui = (uint32) c;
            }
            if (!accum_byte(fp, &ui))
                return 0;
        } else {
            ui = (uint32) c;
        }
        if (!accum_byte(fp, &ui))
            return 0;
    } else {
        ui = (uint32) c;
    }
    *uip = ui;
    return 1;
}

static char *get_string(FILE *fp)
{
    char *cp;
    int c;
    static char buf[256];
    static char *bp = buf, *ep = buf + sizeof buf;
    static size_t bsize = sizeof buf;

    cp = bp;
    do {
        c = getc(fp);
        if (c == EOF)
            return 0;
        if (cp == ep) {
            if (bp == buf) {
                bp = malloc(2 * bsize);
                if (bp)
                    memcpy(bp, buf, bsize);
            } else {
                bp = realloc(bp, 2 * bsize);
            }
            if (!bp)
                return 0;
            cp = bp + bsize;
            bsize *= 2;
            ep = bp + bsize;
        }
        *cp++ = c;
    } while (c != '\0');
    return strdup(bp);
}

static int get_tmevent(FILE *fp, tmevent *event)
{
    int c;
    char *s;

    c = getc(fp);
    if (c == EOF)
        return 0;
    event->type = (char) c;
    if (!get_uint32(fp, &event->serial))
        return 0;
    switch (c) {
      case TM_EVENT_LIBRARY:
        s = get_string(fp);
        if (!s)
            return 0;
        event->u.libname = s;
#ifdef DEBUG_tmreader
        fprintf(stderr, "tmevent %c %u libname=\"%s\"\n", event->type, event->serial,
                event->u.libname);
#endif
        break;

      case TM_EVENT_FILENAME:
        s = get_string(fp);
        if (!s)
            return 0;
        event->u.srcname = s;
#ifdef DEBUG_tmreader
        fprintf(stderr, "tmevent %c %u srcname=\"%s\"\n",
                event->type, event->serial, event->u.srcname);
#endif
        break;

      case TM_EVENT_METHOD:
        if (!get_uint32(fp, &event->u.method.library))
            return 0;
        if (!get_uint32(fp, &event->u.method.filename))
            return 0;
        if (!get_uint32(fp, &event->u.method.linenumber))
            return 0;
        s = get_string(fp);
        if (!s)
            return 0;
        event->u.method.name = s;
#ifdef DEBUG_tmreader
        fprintf(stderr, "tmevent %c %u library=%u filename=%u linenumber=%u "
                "name=\"%s\"\n",
                event->type, event->serial,
                event->u.method.library, event->u.method.filename,
                event->u.method.linenumber, event->u.method.name);
#endif
        break;

      case TM_EVENT_CALLSITE:
        if (!get_uint32(fp, &event->u.site.parent))
            return 0;
        if (!get_uint32(fp, &event->u.site.method))
            return 0;
        if (!get_uint32(fp, &event->u.site.offset))
            return 0;
#ifdef DEBUG_tmreader
        fprintf(stderr, "tmevent %c %u parent=%u method=%u offset=%u\n",
                event->type, event->serial,
                event->u.site.parent, event->u.site.method,
                event->u.site.offset);
#endif
        break;

      case TM_EVENT_MALLOC:
      case TM_EVENT_CALLOC:
      case TM_EVENT_FREE:
        if (!get_uint32(fp, &event->u.alloc.interval))
            return 0;
        if (!get_uint32(fp, &event->u.alloc.cost))
            return 0;
        if (!get_uint32(fp, &event->u.alloc.ptr))
            return 0;
        if (!get_uint32(fp, &event->u.alloc.size))
            return 0;
        event->u.alloc.oldserial = 0;
        event->u.alloc.oldptr = 0;
        event->u.alloc.oldsize = 0;
#ifdef DEBUG_tmreader
        fprintf(stderr, "tmevent %c %u interval=%u cost=%u ptr=0x%x size=%u\n",
                event->type, event->serial,
                event->u.alloc.interval, event->u.alloc.cost,
                event->u.alloc.ptr, event->u.alloc.size);
#endif
#if defined(DEBUG_dp)
        if (c == TM_EVENT_MALLOC)
            printf("%d malloc %d 0x%p\n", event->u.alloc.cost,
                   event->u.alloc.size, event->u.alloc.ptr);
        else if (c == TM_EVENT_CALLOC)
            printf("%d calloc %d 0x%p\n", event->u.alloc.cost,
                   event->u.alloc.size, event->u.alloc.ptr);
        else
            printf("%d free %d 0x%p\n", event->u.alloc.cost,
                   event->u.alloc.size, event->u.alloc.ptr);
#endif
        break;

      case TM_EVENT_REALLOC:
        if (!get_uint32(fp, &event->u.alloc.interval))
            return 0;
        if (!get_uint32(fp, &event->u.alloc.cost))
            return 0;
        if (!get_uint32(fp, &event->u.alloc.ptr))
            return 0;
        if (!get_uint32(fp, &event->u.alloc.size))
            return 0;
        if (!get_uint32(fp, &event->u.alloc.oldserial))
            return 0;
        if (!get_uint32(fp, &event->u.alloc.oldptr))
            return 0;
        if (!get_uint32(fp, &event->u.alloc.oldsize))
            return 0;
#ifdef DEBUG_tmreader
        fprintf(stderr, "tmevent %c %u interval=%u cost=%u ptr=0x%x size=%u "
                "oldserial=%u oldptr=0x%x oldsize=%u\n",
                event->type, event->serial,
                event->u.alloc.interval, event->u.alloc.cost,
                event->u.alloc.ptr, event->u.alloc.size,
                event->u.alloc.oldserial, event->u.alloc.oldptr,
                event->u.alloc.oldsize);
#endif
#if defined(DEBUG_dp)
        printf("%d realloc %d 0x%p %d\n", event->u.alloc.cost,
               event->u.alloc.size, event->u.alloc.ptr, event->u.alloc.oldsize);
#endif
        break;

      case TM_EVENT_STATS:
        if (!get_uint32(fp, &event->u.stats.tmstats.calltree_maxstack))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.calltree_maxdepth))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.calltree_parents))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.calltree_maxkids))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.calltree_kidhits))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.calltree_kidmisses))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.calltree_kidsteps))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.callsite_recurrences))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.backtrace_calls))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.backtrace_failures))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.btmalloc_failures))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.dladdr_failures))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.malloc_calls))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.malloc_failures))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.calloc_calls))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.calloc_failures))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.realloc_calls))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.realloc_failures))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.free_calls))
            return 0;
        if (!get_uint32(fp, &event->u.stats.tmstats.null_free_calls))
            return 0;
        if (!get_uint32(fp, &event->u.stats.calltree_maxkids_parent))
            return 0;
        if (!get_uint32(fp, &event->u.stats.calltree_maxstack_top))
            return 0;
#ifdef DEBUG_tmreader
        fprintf(stderr, "tmevent %c %u\n", event->type, event->serial);
#endif
        break;
      default:
        fprintf(stderr, "Unknown event type 0x%x\n", (unsigned int)event->type);
        return 0;
    }
    return 1;
}

static void *arena_alloc(void* pool, PRSize size)
{
    PLArenaPool* arena = (PLArenaPool*)pool;
    void* result;
    PL_ARENA_ALLOCATE(result, arena, size);
    memset(result, 0, size);
    return result;
}

static void *generic_alloctable(void *pool, PRSize size)
{
    return arena_alloc(pool, size);
}

static void generic_freetable(void *pool, void *item)
{
    /* do nothing - arena-allocated */
}

static PLHashEntry *filename_allocentry(void *pool, const void *key)
{
    return (PLHashEntry*)arena_alloc(pool, sizeof(PLHashEntry));
}

static PLHashEntry *callsite_allocentry(void *pool, const void *key)
{
    return (PLHashEntry*)arena_alloc(pool, sizeof(tmcallsite));
}

static void init_graphnode(tmgraphnode* node)
{
    node->in = node->out = NULL;
    node->up = node->down = node->next = NULL;
    node->low = 0;
    node->allocs.bytes.direct = node->allocs.bytes.total = 0;
    node->allocs.calls.direct = node->allocs.calls.total = 0;
    node->frees.bytes.direct = node->frees.bytes.total = 0;
    node->frees.calls.direct = node->frees.calls.total = 0;
    node->sqsum = 0;
    node->sort = -1;
}

static PLHashEntry *graphnode_allocentry(void *pool, const void *key)
{
    tmgraphnode* node = (tmgraphnode*)arena_alloc(pool, sizeof(tmgraphnode));
    if (!node)
        return NULL;
    init_graphnode(node);
    return &node->entry;
}

static void init_method(tmmethodnode *node)
{
    node->graphnode.in = node->graphnode.out = NULL;
    node->graphnode.up = node->graphnode.down = node->graphnode.next = NULL;
    node->graphnode.low = 0;
    node->graphnode.allocs.bytes.direct = node->graphnode.allocs.bytes.total = 0;
    node->graphnode.allocs.calls.direct = node->graphnode.allocs.calls.total = 0;
    node->graphnode.frees.bytes.direct = node->graphnode.frees.bytes.total = 0;
    node->graphnode.frees.calls.direct = node->graphnode.frees.calls.total = 0;
    node->graphnode.sqsum = 0;
    node->graphnode.sort = -1;
    node->sourcefile = NULL;
    node->linenumber = 0;
}

static PLHashEntry *method_allocentry(void *pool, const void *key)
{
    tmmethodnode *node =
        (tmmethodnode*) arena_alloc(pool, sizeof(tmmethodnode));
    if (!node)
        return NULL;
    init_method(node);
    return &node->graphnode.entry;
}

static void graphnode_freeentry(void *pool, PLHashEntry *he, PRUintn flag)
{
    /* Always free the value, which points to a strdup'd string. */
    free(he->value);
#if 0                           /* using arenas now, no freeing! */
    /* Free the whole thing if we're told to. */
    if (flag == HT_FREE_ENTRY)
        free((void*) he);
#endif
}

static void component_freeentry(void *pool, PLHashEntry *he, PRUintn flag)
{
    if (flag == HT_FREE_ENTRY) {
        tmgraphnode *comp = (tmgraphnode*) he;

        /* Free the key, which was strdup'd (N.B. value also points to it). */
        free((void*) tmcomponent_name(comp));
#if 0                           /* using arenas now, no freeing! */
        free((void*) comp);
#endif
    }
}

static PLHashAllocOps filename_hashallocops = {
    generic_alloctable,     generic_freetable,
    filename_allocentry,    graphnode_freeentry
};

static PLHashAllocOps callsite_hashallocops = {
    generic_alloctable,     generic_freetable,
    callsite_allocentry,    graphnode_freeentry
};

static PLHashAllocOps graphnode_hashallocops = {
    generic_alloctable,     generic_freetable,
    graphnode_allocentry,   graphnode_freeentry
};

static PLHashAllocOps method_hashallocops = {
    generic_alloctable,     generic_freetable,
    method_allocentry,      graphnode_freeentry
};

static PLHashAllocOps component_hashallocops = {
    generic_alloctable,     generic_freetable,
    graphnode_allocentry,   component_freeentry
};

static PLHashNumber hash_serial(const void *key)
{
    return (PLHashNumber) key;
}

tmreader *tmreader_new(const char *program, void *data)
{
    tmreader *tmr;

    tmr = calloc(1, sizeof *tmr);
    if (!tmr)
        return NULL;
    tmr->program = program;
    tmr->data = data;
    PL_INIT_ARENA_POOL(&tmr->arena, "TMReader", 256*1024);

    tmr->libraries = PL_NewHashTable(100, hash_serial, PL_CompareValues,
                                     PL_CompareStrings, &graphnode_hashallocops,
                                     &tmr->arena);
    tmr->filenames = PL_NewHashTable(100, hash_serial, PL_CompareValues,
                                     PL_CompareStrings, &filename_hashallocops,
                                     &tmr->arena);
    tmr->components = PL_NewHashTable(10000, PL_HashString, PL_CompareStrings,
                                      PL_CompareValues, &component_hashallocops,
                                      &tmr->arena);
    tmr->methods = PL_NewHashTable(10000, hash_serial, PL_CompareValues,
                                   PL_CompareStrings, &method_hashallocops,
                                   &tmr->arena);
    tmr->callsites = PL_NewHashTable(200000, hash_serial, PL_CompareValues,
                                     PL_CompareValues, &callsite_hashallocops,
                                     &tmr->arena);
    tmr->calltree_root.entry.value = (void*) strdup("root");

    if (!tmr->libraries || !tmr->components || !tmr->methods ||
        !tmr->callsites || !tmr->calltree_root.entry.value ||
        !tmr->filenames) {
        tmreader_destroy(tmr);
        return NULL;
    }
    return tmr;
}

void tmreader_destroy(tmreader *tmr)
{
    if (tmr->libraries)
        PL_HashTableDestroy(tmr->libraries);
    if (tmr->filenames)
        PL_HashTableDestroy(tmr->filenames);
    if (tmr->components)
        PL_HashTableDestroy(tmr->components);
    if (tmr->methods)
        PL_HashTableDestroy(tmr->methods);
    if (tmr->callsites)
        PL_HashTableDestroy(tmr->callsites);
    PL_FinishArenaPool(&tmr->arena);
    free(tmr);
}

int tmreader_eventloop(tmreader *tmr, const char *filename,
                       tmeventhandler eventhandler)
{
    FILE *fp;
    char buf[NS_TRACE_MALLOC_MAGIC_SIZE];
    tmevent event;
    static const char magic[] = NS_TRACE_MALLOC_MAGIC;

    if (strcmp(filename, "-") == 0) {
        fp = stdin;
    } else {
#if defined(XP_WIN32)
        fp = fopen(filename, "rb");
#else
        fp = fopen(filename, "r");
#endif
        if (!fp) {
            fprintf(stderr, "%s: can't open %s: %s.\n",
                    tmr->program, filename, strerror(errno));
            return 0;
        }
    }

    if (read(fileno(fp), buf, sizeof buf) != sizeof buf ||
        strncmp(buf, magic, sizeof buf) != 0) {
        fprintf(stderr, "%s: bad magic string %s at start of %s.\n",
                tmr->program, buf, filename);
        fprintf(stderr, "either the data file is out of date,\nor your tools are out of date.\n");
        return 0;
    }

    /* Read in ticks per second. Used to convert platform specific intervals to time values */
    if (read(fileno(fp), &tmr->ticksPerSec, sizeof tmr->ticksPerSec) != sizeof tmr->ticksPerSec) {
        fprintf(stderr, "%s: Cannot read ticksPerSec. Log file read error.\n",
                tmr->program);
        return 0;
    }
    tmr->ticksPerSec = PR_ntohl(tmr->ticksPerSec);
#ifdef DEBUG_dp
    printf("DEBUG: ticks per sec = %d\n", tmr->ticksPerSec);
#endif
    while (get_tmevent(fp, &event)) {
        switch (event.type) {
          case TM_EVENT_LIBRARY: {
            const void *key;
            PLHashNumber hash;
            PLHashEntry **hep, *he;

            key = (const void*) event.serial;
            hash = hash_serial(key);
            hep = PL_HashTableRawLookup(tmr->libraries, hash, key);
            he = *hep;
            PR_ASSERT(!he);
            if (he) exit(2);

            he = PL_HashTableRawAdd(tmr->libraries, hep, hash, key,
                                    event.u.libname);
            if (!he) {
                perror(tmr->program);
                return -1;
            }
            break;
          }

          case TM_EVENT_FILENAME: {
            const void *key;
            PLHashNumber hash;
            PLHashEntry **hep, *he;

            key = (const void*) event.serial;
            hash = hash_serial(key);
            hep = PL_HashTableRawLookup(tmr->filenames, hash, key);
            he = *hep;
            PR_ASSERT(!he);
            if (he) exit(2);

            he = PL_HashTableRawAdd(tmr->filenames, hep, hash, key,
                                    event.u.srcname);
            if (!he) {
                perror(tmr->program);
                return -1;
            }
            break;
          }

          case TM_EVENT_METHOD: {
            const void *key, *sourcekey;
            PLHashNumber hash, sourcehash;
            PLHashEntry **hep, *he, **sourcehep, *sourcehe;
            char *name, *head, *mark, save;
            tmgraphnode *comp, *lib;
            tmmethodnode *meth;

            key = (const void*) event.serial;
            hash = hash_serial(key);
            hep = PL_HashTableRawLookup(tmr->methods, hash, key);
            he = *hep;
            PR_ASSERT(!he);
            if (he) exit(2);

            name = event.u.method.name;
            he = PL_HashTableRawAdd(tmr->methods, hep, hash, key, name);
            if (!he) {
                perror(tmr->program);
                return -1;
            }
            meth = (tmmethodnode*) he;

            meth->linenumber = event.u.method.linenumber;
            sourcekey = (const void*)event.u.method.filename;
            sourcehash = hash_serial(sourcekey);
            sourcehep = PL_HashTableRawLookup(tmr->filenames, sourcehash, sourcekey);
            sourcehe = *sourcehep;
            meth->sourcefile = filename_name(sourcehe);

            head = name;
            mark = strchr(name, ':');
            if (!mark) {
                mark = name;
                while (*mark != '\0' && *mark == '_')
                    mark++;
                head = mark;
                mark = strchr(head, '_');
                if (!mark) {
                    mark = strchr(head, '+');
                    if (!mark)
                        mark = head + strlen(head);
                }
            }

            save = *mark;
            *mark = '\0';
            hash = PL_HashString(head);
            hep = PL_HashTableRawLookup(tmr->components, hash, head);
            he = *hep;
            if (he) {
                comp = (tmgraphnode*) he;
            } else {
                head = strdup(head);
                if (head) {
                    he = PL_HashTableRawAdd(tmr->components, hep, hash, head,
                                            head);
                }
                if (!he) {
                    perror(tmr->program);
                    return -1;
                }
                comp = (tmgraphnode*) he;

                key = (const void*) event.u.method.library;
                hash = hash_serial(key);
                lib = (tmgraphnode*)
                      *PL_HashTableRawLookup(tmr->libraries, hash, key);
                if (lib) {
                    comp->up = lib;
                    comp->next = lib->down;
                    lib->down = comp;
                }
            }
            *mark = save;

            meth->graphnode.up = comp;
            meth->graphnode.next = comp->down;
            comp->down = &(meth->graphnode);
            break;
          }

          case TM_EVENT_CALLSITE: {
            const void *key, *mkey;
            PLHashNumber hash, mhash;
            PLHashEntry **hep, *he;
            tmcallsite *site, *parent;
            tmmethodnode *meth;

            key = (const void*) event.serial;
            hash = hash_serial(key);
            hep = PL_HashTableRawLookup(tmr->callsites, hash, key);
            he = *hep;

            /* there should not be an entry here! */
            PR_ASSERT(!he);
            if (he) exit(2);

            if (event.u.site.parent == 0) {
                parent = &tmr->calltree_root;
            } else {
                parent = tmreader_callsite(tmr, event.u.site.parent);
                if (!parent) {
                    fprintf(stderr, "%s: no parent for %lu (%lu)!\n",
                            tmr->program, (unsigned long) event.serial,
                            (unsigned long) event.u.site.parent);
                    continue;
                }
            }

            he = PL_HashTableRawAdd(tmr->callsites, hep, hash, key, NULL);
            if (!he) {
                perror(tmr->program);
                return -1;
            }

            site = (tmcallsite*) he;
            site->parent = parent;
            site->siblings = parent->kids;
            parent->kids = site;
            site->kids = NULL;

            mkey = (const void*) event.u.site.method;
            mhash = hash_serial(mkey);
            meth = (tmmethodnode*)
                   *PL_HashTableRawLookup(tmr->methods, mhash, mkey);
            site->method = meth;
            site->offset = event.u.site.offset;
            site->allocs.bytes.direct = site->allocs.bytes.total = 0;
            site->allocs.calls.direct = site->allocs.calls.total = 0;
            site->frees.bytes.direct = site->frees.bytes.total = 0;
            site->frees.calls.direct = site->frees.calls.total = 0;
            break;
          }

          case TM_EVENT_MALLOC:
          case TM_EVENT_CALLOC:
          case TM_EVENT_REALLOC: {
            tmcallsite *site;
            uint32 size, oldsize;
            double delta, sqdelta, sqszdelta = 0;
            tmgraphnode *comp, *lib;
            tmmethodnode *meth;

            site = tmreader_callsite(tmr, event.serial);
            if (!site) {
                fprintf(stderr, "%s: no callsite for '%c' (%lu)!\n",
                        tmr->program, event.type, (unsigned long) event.serial);
                continue;
            }

            size = event.u.alloc.size;
            oldsize = event.u.alloc.oldsize;
            delta = (double)size - (double)oldsize;
            site->allocs.bytes.direct += (unsigned long)delta;
            if (event.type != TM_EVENT_REALLOC)
                site->allocs.calls.direct++;
            meth = site->method;
            if (meth) {
                meth->graphnode.allocs.bytes.direct += (unsigned long)delta;
                sqdelta = delta * delta;
                if (event.type == TM_EVENT_REALLOC) {
                    sqszdelta = ((double)size * size)
                              - ((double)oldsize * oldsize);
                    meth->graphnode.sqsum += sqszdelta;
                } else {
                    meth->graphnode.sqsum += sqdelta;
                    meth->graphnode.allocs.calls.direct++;
                }
                comp = meth->graphnode.up;
                if (comp) {
                    comp->allocs.bytes.direct += (unsigned long)delta;
                    if (event.type == TM_EVENT_REALLOC) {
                        comp->sqsum += sqszdelta;
                    } else {
                        comp->sqsum += sqdelta;
                        comp->allocs.calls.direct++;
                    }
                    lib = comp->up;
                    if (lib) {
                        lib->allocs.bytes.direct += (unsigned long)delta;
                        if (event.type == TM_EVENT_REALLOC) {
                            lib->sqsum += sqszdelta;
                        } else {
                            lib->sqsum += sqdelta;
                            lib->allocs.calls.direct++;
                        }
                    }
                }
            }
            break;
          }

          case TM_EVENT_FREE: {
            tmcallsite *site;
            uint32 size;
            tmgraphnode *comp, *lib;
            tmmethodnode *meth;

            site = tmreader_callsite(tmr, event.serial);
            if (!site) {
                fprintf(stderr, "%s: no callsite for '%c' (%lu)!\n",
                        tmr->program, event.type, (unsigned long) event.serial);
                continue;
            }
            size = event.u.alloc.size;
            site->frees.bytes.direct += size;
            site->frees.calls.direct++;
            meth = site->method;
            if (meth) {
                meth->graphnode.frees.bytes.direct += size;
                meth->graphnode.frees.calls.direct++;
                comp = meth->graphnode.up;
                if (comp) {
                    comp->frees.bytes.direct += size;
                    comp->frees.calls.direct++;
                    lib = comp->up;
                    if (lib) {
                        lib->frees.bytes.direct += size;
                        lib->frees.calls.direct++;
                    }
                }
            }
            break;
          }

          case TM_EVENT_STATS:
            break;
        }

        eventhandler(tmr, &event);
    }

    return 1;
}

tmgraphnode *tmreader_library(tmreader *tmr, uint32 serial)
{
    const void *key;
    PLHashNumber hash;

    key = (const void*) serial;
    hash = hash_serial(key);
    return (tmgraphnode*) *PL_HashTableRawLookup(tmr->libraries, hash, key);
}

tmgraphnode *tmreader_filename(tmreader *tmr, uint32 serial)
{
    const void *key;
    PLHashNumber hash;

    key = (const void*) serial;
    hash = hash_serial(key);
    return (tmgraphnode*) *PL_HashTableRawLookup(tmr->filenames, hash, key);
}

tmgraphnode *tmreader_component(tmreader *tmr, const char *name)
{
    PLHashNumber hash;

    hash = PL_HashString(name);
    return (tmgraphnode*) *PL_HashTableRawLookup(tmr->components, hash, name);
}

tmmethodnode *tmreader_method(tmreader *tmr, uint32 serial)
{
    const void *key;
    PLHashNumber hash;

    key = (const void*) serial;
    hash = hash_serial(key);
    return (tmmethodnode*) *PL_HashTableRawLookup(tmr->methods, hash, key);
}

tmcallsite *tmreader_callsite(tmreader *tmr, uint32 serial)
{
    const void *key;
    PLHashNumber hash;

    key = (const void*) serial;
    hash = hash_serial(key);
    return (tmcallsite*) *PL_HashTableRawLookup(tmr->callsites, hash, key);
}

int tmgraphnode_connect(tmgraphnode *from, tmgraphnode *to, tmcallsite *site)
{
    tmgraphlink *outlink;
    tmgraphedge *edge;

    for (outlink = from->out; outlink; outlink = outlink->next) {
        if (outlink->node == to) {
            /*
             * Say the stack looks like this: ... => JS => js => JS => js.
             * We must avoid overcounting JS=>js because the first edge total
             * includes the second JS=>js edge's total (which is because the
             * lower site's total includes all its kids' totals).
             */
            edge = TM_LINK_TO_EDGE(outlink, TM_EDGE_OUT_LINK);
            if (!to->low || to->low < from->low) {
                /* Add the direct and total counts to edge->allocs. */
                edge->allocs.bytes.direct += site->allocs.bytes.direct;
                edge->allocs.bytes.total += site->allocs.bytes.total;
                edge->allocs.calls.direct += site->allocs.calls.direct;
                edge->allocs.calls.total += site->allocs.calls.total;

                /* Now update the free counts. */
                edge->frees.bytes.direct += site->frees.bytes.direct;
                edge->frees.bytes.total += site->frees.bytes.total;
                edge->frees.calls.direct += site->frees.calls.direct;
                edge->frees.calls.total += site->frees.calls.total;
            }
            return 1;
        }
    }

    edge = (tmgraphedge*) malloc(sizeof(tmgraphedge));
    if (!edge)
        return 0;
    edge->links[TM_EDGE_OUT_LINK].node = to;
    edge->links[TM_EDGE_OUT_LINK].next = from->out;
    from->out = &edge->links[TM_EDGE_OUT_LINK];
    edge->links[TM_EDGE_IN_LINK].node = from;
    edge->links[TM_EDGE_IN_LINK].next = to->in;
    to->in = &edge->links[TM_EDGE_IN_LINK];
    edge->allocs = site->allocs;
    edge->frees = site->frees;
    return 1;
}