logdump.cs

// logdump.cs -- a little utility program that dumps summary statistics for CLR profiler logs
// © 2005 Microsoft Corporation 
// csc /o+ logdump.cs is all you need to do to build it.
// this program is offered as is with no warranty implied and confers no rights .

using System;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;

class LogDumper
{
    // this tells us the current number of allocations and total bytes at a specific stack for a specific type
    class MemCharge
    {
        public int count;
        public int bytes;
    };

    // this is used to help us find the top N of any kind of id by size
    struct TopCalc
    {
        public int id;
        public int bytes;
    }

    // the log file we will be reading
    private String strLogFile = null;

    // the filter file if we are going to only analyze specific types
    private String strFilterFile = null;

    // the number of stacks and types we will be dumping
    private int cTop = 10;

    private TopCalc[] topStacks = null;
    private TopCalc[] topTypes = null;

    private int bytesTotal = 0;
    private int allocsTotal = 0;

    private Char[] space = { ' ' };
    // storage is in these dictionaries

    private Dictionary<String, bool> dictTypenameFilterSet = null;
    private Dictionary<String, int> dictFuncNameToFuncId = new Dictionary<String, int>();
    private Dictionary<int, String> dictFuncIdToFuncName = new Dictionary<int, String>();

    private Dictionary<String, int> dictTypeNameToTypeId = new Dictionary<String, int>();
    private Dictionary<int, String> dictTypeIdToTypeName = new Dictionary<int, String>();

    private Dictionary<int, String> dictStackIdToStackstring = new Dictionary<int, String>();
    private Dictionary<String, int> dictStackstringToStackId = new Dictionary<String, int>();

    private Dictionary<int, Dictionary<int, MemCharge>> dictStackIdToCharges =
        new Dictionary<int, Dictionary<int, MemCharge>>();

    private Dictionary<int, int> dictStackIdToTypeId = new Dictionary<int, int>();
    private Dictionary<int, int> dictStackIdToSize = new Dictionary<int, int>();

    private Dictionary<int, MemCharge> dictTypeIdToCharges = new Dictionary<int, MemCharge>();
  
    public static void Main(String[] args)
    {
        new LogDumper().InitializeAndRun(args);
    }

    private void InitializeAndRun(String[] args)
    {
        if (args.Length == 0)
            goto UsageMessage;

        for (int iarg = 0; iarg < args.Length; iarg++)
        {
            if (args[iarg].Length >= 2 && args[iarg][0] == '-' || args[iarg][0] == '/')
            {
                switch (args[iarg][1])
                {
                    case 'n':
                        if (iarg + 1 >= args.Length)
                            goto UsageMessage;
                        iarg++;
                        cTop = Int32.Parse(args[iarg]);
                        break;

                    case 'f':
                        if (iarg + 1 >= args.Length)
                            goto UsageMessage;
                        iarg++;
                        strFilterFile = args[iarg];
                        break;
                    default:
                        goto UsageMessage;
                }
                continue;
            }

            strLogFile = args[iarg];
            break;
        }

        // establish the filter set if there is to be one
        if (strFilterFile != null)
        {
            ReadFilterStream(strFilterFile);
        }

        if (strLogFile == null)
            goto UsageMessage;

        // read the "new" file building up the full cost of the allocs
        ReadLogFile(strLogFile);

        WriteSummary();
        return;

    UsageMessage:
        Console.WriteLine("Usage: logdump [-n num] [-f filter] file");
        Console.WriteLine("-f filter : filter file indicates types to consider");
        Console.WriteLine("-n num : top num types and stacks displayed in summary");
        Console.WriteLine("file : the input file generated by clrprofiler for beta2 or later");

        return;
    }

    private void ReadLogFile(String strFile)
    {
        String line;

        int id, code;
        String s;
        int typeid = -1;
        int typesize = -1;
        int referredStackId = -1;
        String[] a = null;
        Char[] space = { ' ' };

        using (StreamReader stmIn = new StreamReader(strFile))
        {
            while ((line = stmIn.ReadLine()) != null)
            {
                switch (line[0])
                {
                    // defines a function
                    case 'f':
                        // we'll use the trailing ')' character to find the full function name definition
                        {
                            int i1, i2, i3;

                            i1 = line.IndexOf(' ');
                            if (i1 < 0) break;
                            i1++;

                            i2 = line.IndexOf(' ', i1);
                            if (i2 < 0) break;

                            id = Int32.Parse(line.Substring(i1, i2 - i1 + 1));
                            i2++;

                            i3 = line.IndexOf(')', i2);
                            if (i3 < 0) break;

                            s = line.Substring(i2, i3 - i2 + 1);
                        }

                        // function names are not unique so we have to de-dupe, we'll use the first one
                        // for the canonical mapping
                        if (!dictFuncNameToFuncId.ContainsKey(s))
                            dictFuncNameToFuncId.Add(s, id);

                        if (!dictFuncIdToFuncName.ContainsKey(id))
                        {
                            dictFuncIdToFuncName.Add(id, s);
                        }
                        else
                        {
                            Console.Error.WriteLine("Warning: function id {0} duplicated", id);
                            Console.Error.WriteLine("Warning: 1st def: {0}", dictFuncIdToFuncName[id]);
                            Console.Error.WriteLine("Warning: 2nd def: {0}", s);
                            Console.Error.WriteLine();
                        }
                        break;

                    // an allocation
                    case '!':
                        a = line.Split(space);
                        if (a.Length != 4)
                        {
                            Console.Error.WriteLine("Corrupt allocation line: {0}", line);
                            break;
                        }
                       
                        ProcessAlloc(Int32.Parse(a[3]));
                        break;

                    // a new stack
                    case 'n':
                        a = line.Split(space);

                        id = Int32.Parse(a[1]);
                        code = Int32.Parse(a[2]);

                        int bit0 = code & 1;
                        code >>= 1;
                        int bit1 = code & 1;
                        code >>= 1;

                        int next = 3;

                        // indicates type and size of allocation present
                        if (bit0 != 0)
                        {
                            typeid = Int32.Parse(a[next]);
                            typesize = Int32.Parse(a[next + 1]);
                            next += 2;
                        }

                        referredStackId = -1;

                        if (code != 0)
                        {
                            referredStackId = Int32.Parse(a[next]);
                            next++;
                        }

                        // we're going to make the full stack string for the stack
                        // including any part which was done by reference to a previous stack
                        StringBuilder sb = new StringBuilder();

                        if (referredStackId != -1)
                        {
                            String t = dictStackIdToStackstring[referredStackId];

                            int cch = 0;
                            for (int i = 0; i < code; i++)
                            {
                                cch = t.IndexOf(' ', cch + 1);
                                if (cch < 0)
                                {
                                    cch = t.Length;
                                    break;
                                }
                            }

                            if (cch == t.Length)
                                sb.Append(t);
                            else
                                sb.Append(t, 0, cch);
                        }

                        while (next < a.Length)
                        {
                            if (sb.Length != 0)
                                sb.Append(" ");

                            int funcId = Int32.Parse(a[next++]);

                            funcId = dictFuncNameToFuncId[dictFuncIdToFuncName[funcId]];

                            sb.Append(funcId.ToString());
                        }

                        s = sb.ToString();

                        dictStackIdToStackstring.Add(id, s);

                        if (bit0 != 0)
                        {
                            dictStackIdToTypeId.Add(id, typeid);
                            dictStackIdToSize.Add(id, typesize);
                        }

                        break;

                    // type definition
                    case 't':
                        {
                            int i1, i2;

                            i1 = line.IndexOf(' ');
                            if (i1 < 0) break;
                            i1++;

                            i2 = line.IndexOf(' ', i1);
                            if (i2 < 0) break;

                            id = Int32.Parse(line.Substring(i1, i2 - i1 + 1));
                            i2++;

                            if (i2 + 1 >= line.Length)
                                break;

                            // try to be compatible with formats with an extra number before the type or not
                            while (line[i2] >= '0' && line[i2] <= '9')
                            {
                                i2 = line.IndexOf(' ', i2);
                                i2++;
                                if (i2 + 1 >= line.Length)
                                    break;
                            }

                            s = line.Substring(i2);

                            if (dictTypenameFilterSet != null && !dictTypenameFilterSet.ContainsKey(s))
                                break;

                            if (!dictTypeNameToTypeId.ContainsKey(s))
                                dictTypeNameToTypeId.Add(s, id);

                            dictTypeIdToTypeName.Add(id, s);
                        }
                        break;

                    default:
                        // the rest we can ignore for this dumper
                        break;
                }
            }
        }
    }

    private void WriteSummary()
    {
        Dictionary<int, MemCharge> dictTypeIdToCharges = new Dictionary<int, MemCharge>();

        topStacks = new TopCalc[cTop];
        topTypes = new TopCalc[cTop];

        Console.WriteLine("This report shows allocations in {0}", strLogFile);

        if (strFilterFile != null)
        {
            Console.WriteLine("This report only considers object types found in {0}", strFilterFile);
        }

        Array.Clear(topStacks, 0, cTop);
        Array.Clear(topTypes, 0, cTop);

        ComputeTopStacks();
        ComputeTopTypes();

        Console.WriteLine();
        Console.WriteLine("Total Allocations {0} Objects {1} Bytes", allocsTotal, bytesTotal);
        Console.WriteLine();

        PrintTypes();
        PrintStacks();
    }

    private void ComputeTopStacks()
    {
        foreach (KeyValuePair<int, Dictionary<int, MemCharge>> kvStack in dictStackIdToCharges)
        {
            Dictionary<int, MemCharge> d = kvStack.Value;
            int stackbytes = 0;

            foreach (KeyValuePair<int, MemCharge> kv in d)
            {
                if (kv.Value.count == 0 || kv.Value.bytes == 0)
                    continue;

                bytesTotal += kv.Value.bytes;
                allocsTotal += kv.Value.count;

                stackbytes += kv.Value.bytes;

                MemCharge charge = null;

                if (dictTypeIdToCharges.ContainsKey(kv.Key))
                {
                    charge = dictTypeIdToCharges[kv.Key];
                    charge.bytes += kv.Value.bytes;
                    charge.count += kv.Value.count;
                }
                else
                {
                    charge = new MemCharge();
                    dictTypeIdToCharges.Add(kv.Key, charge);
                    charge.bytes = kv.Value.bytes;
                    charge.count = kv.Value.count;
                }
            }

            int i;
            for (i = cTop; --i >= 0; )
            {
                if (topStacks[i].bytes > stackbytes)
                    break;
            }

            int iNew = i + 1;
            if (iNew < cTop)
            {
                for (i = cTop - 1; --i >= iNew; )
                {
                    topStacks[i + 1] = topStacks[i];
                }

                topStacks[iNew].bytes = stackbytes;
                topStacks[iNew].id = kvStack.Key;
            }
        }
    }

    private void ComputeTopTypes()
    {
        foreach (KeyValuePair<int, MemCharge> tmem in dictTypeIdToCharges)
        {
            int i;
            for (i = cTop; --i >= 0; )
            {
                if (topTypes[i].bytes > tmem.Value.bytes)
                    break;
            }

            int iNew = i + 1;
            if (iNew < cTop)
            {
                for (i = cTop - 1; --i >= iNew; )
                {
                    topTypes[i + 1] = topTypes[i];
                }

                topTypes[iNew].bytes = tmem.Value.bytes;
                topTypes[iNew].id = tmem.Key;
            }
        }
    }

    private void PrintTypes()
    {
        Console.WriteLine("Top {0} Allocated Types", cTop);
        Console.WriteLine();
        Console.WriteLine("{0,8:s} {1,8:s} {2}", "Count", "Bytes", "Type");

        for (int i = 0; i < cTop; i++)
        {
            int id = topTypes[i].id;

            if (id == 0)
                break;

            MemCharge charge = dictTypeIdToCharges[id];

            Console.WriteLine("{0,8:d} {1,8:d} {2}", charge.count, charge.bytes, dictTypeIdToTypeName[id]);
        }
    }

    private void PrintStacks()
    {
        Console.WriteLine();
        Console.WriteLine("Top {0} Allocating Stacks", cTop);

        for (int i = 0; i < cTop; i++)
        {
            int id = topStacks[i].id;

            if (id == 0 || topStacks[i].bytes == 0)
                break;

            Console.WriteLine();
            Console.WriteLine("Stack {0} allocates {1} bytes", i + 1, topStacks[i].bytes);

            String[] a = dictStackIdToStackstring[id].Split(space);
            for (int j = 0; j < a.Length; j++)
            {
                if (a[j].Length == 0)
                    break;

                int fid = Int32.Parse(a[j]);

                Console.WriteLine("{0}", dictFuncIdToFuncName[fid]);
            }

            Dictionary<int, MemCharge> d = dictStackIdToCharges[id];

            foreach (KeyValuePair<int, MemCharge> kv in d)
            {
                if (kv.Value.count == 0 || kv.Value.bytes == 0)
                    continue;

                Console.WriteLine("{0,8:d} {1,8:d} {2}", kv.Value.count, kv.Value.bytes, dictTypeIdToTypeName[kv.Key]);
            }
        }
    }

    private void ReadFilterStream(String ffilter)
    {
        using (StreamReader sr = new StreamReader(ffilter))
        {
            dictTypenameFilterSet = new Dictionary<String, bool>();

            String line;
            while ((line = sr.ReadLine()) != null)
            {
                dictTypenameFilterSet.Add(line, true);
            }
        }
    }

    private void ProcessAlloc(int stackid)
    {
        int typeid = dictStackIdToTypeId[stackid];
        int size = dictStackIdToSize[stackid];
        String s;

        if (!dictStackIdToStackstring.ContainsKey(stackid))
            return;

        s = dictStackIdToStackstring[stackid];

        // We want to report the results as though there was one unique
        // stackids for each callstack.  So we choose a standard stack
        // to charge here.  The other stacks will have no allocations
       
        if (dictStackstringToStackId.ContainsKey(s))
            stackid = dictStackstringToStackId[s];
        else
            dictStackstringToStackId.Add(s, stackid);

        // don't count allocations against types we filtered out
        if (!dictTypeIdToTypeName.ContainsKey(typeid))
            return;

        s = dictTypeIdToTypeName[typeid];
        if (!dictTypeNameToTypeId.ContainsKey(s))
            return;

        typeid = dictTypeNameToTypeId[s];

        if (!dictStackIdToCharges.ContainsKey(stackid))
            dictStackIdToCharges.Add(stackid, new Dictionary<int, MemCharge>());

        Dictionary<int, MemCharge> dictCharges = dictStackIdToCharges[stackid];

        MemCharge charge = null;

        if (dictCharges.ContainsKey(typeid))
        {
            charge = dictCharges[typeid];
            charge.count++;
            charge.bytes += size;
        }
        else
        {
            charge = new MemCharge();
            charge.count = 1;
            charge.bytes = size;
            dictCharges.Add(typeid, charge);
        }
    }
}