Make the low-level log-reader object export a dictionary mapping keys
to lists of values, giving the contents of all the ADD_INFO records
seen so far.  This is initialized agressively when the log file is
opened, so that whoever is looking at the log reader can always see
the initial data loaded into the data stream.  ADD_INFO events later
in the log file continue to be reported to the application layer as
before.

Add a new method, addinfo(), to the profiler.  This can be used to
insert additional ADD_INFO records into the profiler log.

Fix the tp_flags and tp_name slots on the type objects.
diff --git a/Modules/_hotshot.c b/Modules/_hotshot.c
index 2dbabcc..b17be9f 100644
--- a/Modules/_hotshot.c
+++ b/Modules/_hotshot.c
@@ -82,6 +82,7 @@
 
 typedef struct {
     PyObject_HEAD
+    PyObject *info;
     FILE *logfp;
     int filled;
     int index;
@@ -257,6 +258,7 @@
 #define ERR_NONE          0
 #define ERR_EOF          -1
 #define ERR_EXCEPTION    -2
+#define ERR_BAD_RECTYPE  -3
 
 #define PISIZE            (sizeof(int) + 1)
 #define MPISIZE           (PISIZE + 1)
@@ -326,6 +328,73 @@
 }
 
 
+static int
+unpack_add_info(LogReaderObject *self, int skip_opcode)
+{
+    PyObject *key;
+    PyObject *value = NULL;
+    int err;
+
+    if (skip_opcode) {
+        if (self->buffer[self->index] != WHAT_ADD_INFO)
+            return ERR_BAD_RECTYPE;
+        self->index++;
+    }
+    err = unpack_string(self, &key);
+    if (!err) {
+        err = unpack_string(self, &value);
+        if (err)
+            Py_DECREF(key);
+        else {
+            PyObject *list = PyDict_GetItem(self->info, key);
+            if (list == NULL) {
+                list = PyList_New(0);
+                if (list == NULL) {
+                    err = ERR_EXCEPTION;
+                    goto finally;
+                }
+                if (PyDict_SetItem(self->info, key, list)) {
+                    err = ERR_EXCEPTION;
+                    goto finally;
+                }
+            }
+            if (PyList_Append(list, value))
+                err = ERR_EXCEPTION;
+        }
+    }
+ finally:
+    Py_XDECREF(key);
+    Py_XDECREF(value);
+    return err;
+}
+
+
+static void
+logreader_refill(LogReaderObject *self)
+{
+    int needed;
+    size_t res;
+
+    if (self->index) {
+        memmove(self->buffer, &self->buffer[self->index],
+                self->filled - self->index);
+        self->filled = self->filled - self->index;
+        self->index = 0;
+    }
+    needed = BUFFERSIZE - self->filled;
+    if (needed > 0) {
+        res = fread(&self->buffer[self->filled], 1, needed, self->logfp);
+        self->filled += res;
+    }
+}
+
+static void
+eof_error(void)
+{
+    PyErr_SetString(PyExc_EOFError,
+                    "end of file with incomplete profile record");
+}
+
 static PyObject *
 logreader_tp_iternext(LogReaderObject *self)
 {
@@ -346,21 +415,9 @@
         return NULL;
     }
  restart:
-    if ((self->filled - self->index) < MAXEVENTSIZE) {
-        /* add a little to the buffer */
-        int needed;
-        size_t res;
-    refill:
-        if (self->index) {
-            memmove(self->buffer, &self->buffer[self->index],
-                    self->filled - self->index);
-            self->filled = self->filled - self->index;
-            self->index = 0;
-        }
-        needed = BUFFERSIZE - self->filled;
-        res = fread(&self->buffer[self->filled], 1, needed, self->logfp);
-        self->filled += res;
-    }
+    if ((self->filled - self->index) < MAXEVENTSIZE)
+        logreader_refill(self);
+
     /* end of input */
     if (self->filled == 0)
         return NULL;
@@ -391,14 +448,7 @@
             err = unpack_packed_int(self, &tdelta, 0);
         break;
     case WHAT_ADD_INFO:
-        err = unpack_string(self, &s1);
-        if (!err) {
-            err = unpack_string(self, &s2);
-            if (err) {
-                Py_DECREF(s1);
-                s1 = NULL;
-            }
-        }
+        err = unpack_add_info(self, 0);
         break;
     case WHAT_DEFINE_FILE:
         err = unpack_packed_int(self, &fileno, 0);
@@ -426,6 +476,7 @@
             self->index++;
             goto restart;
         }
+        break;
     case WHAT_FRAME_TIMES:
         if (self->index >= self->filled)
             err = ERR_EOF;
@@ -434,8 +485,9 @@
             self->index++;
             goto restart;
         }
+        break;
     default:
-	;
+        err = ERR_BAD_RECTYPE;
     }
     if (err == ERR_EOF && oldindex != 0) {
         /* It looks like we ran out of data before we had it all; this
@@ -443,12 +495,15 @@
          * data.  Try forcing the buffer to be re-filled before failing.
          */
         err = ERR_NONE;
-        goto refill;
+        logreader_refill(self);
     }
-    if (err == ERR_EOF) {
+    if (err == ERR_BAD_RECTYPE) {
+        PyErr_SetString(PyExc_ValueError,
+                        "unknown record type in log file");
+    }
+    else if (err == ERR_EOF) {
         /* Could not avoid end-of-buffer error. */
-        PyErr_SetString(PyExc_EOFError,
-                        "end of file with incomplete profile record");
+        eof_error();
     }
     else if (!err) {
         result = PyTuple_New(4);
@@ -973,6 +1028,28 @@
 
 /* Profiler object interface methods. */
 
+static char addinfo__doc__[] =
+"addinfo(key, value)\n"
+"Insert an ADD_INFO record into the log.";
+
+static PyObject *
+profiler_addinfo(ProfilerObject *self, PyObject *args)
+{
+    PyObject *result = NULL;
+    char *key, *value;
+
+    if (PyArg_ParseTuple(args, "ss:addinfo", &key, &value)) {
+        if (self->logfp == NULL)
+            PyErr_SetString(ProfilerError, "profiler already closed");
+        else {
+            pack_add_info(self, key, value);
+            result = Py_None;
+            Py_INCREF(result);
+        }
+    }
+    return result;
+}
+
 static char close__doc__[] =
 "close()\n"
 "Shut down this profiler and close the log files, even if its active.";
@@ -1109,6 +1186,7 @@
  * more easily, requiring only the changes to the dispatcher to be made.
  */
 static PyMethodDef profiler_methods[] = {
+    {"addinfo", (PyCFunction)profiler_addinfo, METH_VARARGS, addinfo__doc__},
     {"close",   (PyCFunction)profiler_close,   METH_VARARGS, close__doc__},
     {"runcall", (PyCFunction)profiler_runcall, METH_VARARGS, runcall__doc__},
     {"runcode", (PyCFunction)profiler_runcode, METH_VARARGS, runcode__doc__},
@@ -1168,7 +1246,7 @@
 static PyTypeObject ProfilerType = {
     PyObject_HEAD_INIT(NULL)
     0,					/* ob_size		*/
-    "HotShot-profiler",			/* tp_name		*/
+    "_hotshot.ProfilerType",		/* tp_name		*/
     (int) sizeof(ProfilerObject),	/* tp_basicsize		*/
     0,					/* tp_itemsize		*/
     (destructor)profiler_dealloc,	/* tp_dealloc		*/
@@ -1186,7 +1264,7 @@
     0,					/* tp_getattro		*/
     0,					/* tp_setattro		*/
     0,					/* tp_as_buffer		*/
-    0,					/* tp_flags		*/
+    Py_TPFLAGS_DEFAULT,			/* tp_flags		*/
     profiler_object__doc__,		/* tp_doc		*/
 };
 
@@ -1200,8 +1278,12 @@
 };
 
 static PyObject *
-logreader_getattr(ProfilerObject *self, char *name)
+logreader_getattr(LogReaderObject *self, char *name)
 {
+    if (strcmp(name, "info") == 0) {
+        Py_INCREF(self->info);
+        return self->info;
+    }
     return Py_FindMethod(logreader_methods, (PyObject *)self, name);
 }
 
@@ -1226,7 +1308,7 @@
 static PyTypeObject LogReaderType = {
     PyObject_HEAD_INIT(NULL)
     0,					/* ob_size		*/
-    "HotShot-logreader",		/* tp_name		*/
+    "_hotshot.LogReaderType",		/* tp_name		*/
     (int) sizeof(LogReaderObject),	/* tp_basicsize		*/
     0,					/* tp_itemsize		*/
     (destructor)logreader_dealloc,	/* tp_dealloc		*/
@@ -1244,7 +1326,7 @@
     0,					/* tp_getattro		*/
     0,					/* tp_setattro		*/
     0,					/* tp_as_buffer		*/
-    0,					/* tp_flags		*/
+    Py_TPFLAGS_DEFAULT,			/* tp_flags		*/
     logreader__doc__,			/* tp_doc		*/
 #if Py_TPFLAGS_HAVE_ITER
     0,					/* tp_traverse		*/
@@ -1269,14 +1351,45 @@
             self->index = 0;
             self->frametimings = 1;
             self->linetimings = 0;
+            self->info = NULL;
             self->logfp = fopen(filename, "rb");
             if (self->logfp == NULL) {
                 PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
                 Py_DECREF(self);
                 self = NULL;
+                goto finally;
+            }
+            self->info = PyDict_New();
+            if (self->info == NULL) {
+                Py_DECREF(self);
+                goto finally;
+            }
+            /* Aggressively attempt to load all preliminary ADD_INFO
+             * records from the log so the info records are available
+             * from a fresh logreader object.
+             */
+            logreader_refill(self);
+            while (self->filled > self->index
+                   && self->buffer[self->index] == WHAT_ADD_INFO) {
+                int err = unpack_add_info(self, 1);
+                if (err) {
+                    if (err == ERR_EOF)
+                        eof_error();
+                    else
+                        PyErr_SetString(PyExc_RuntimeError,
+                                        "unexpected error");
+                    break;
+                }
+                /* Refill agressively so we can avoid EOF during
+                 * initialization unless there's a real EOF condition
+                 * (the tp_iternext handler loops attempts to refill
+                 * and try again).
+                 */
+                logreader_refill(self);
             }
         }
     }
+ finally:
     return (PyObject *) self;
 }
 
@@ -1322,30 +1435,32 @@
         PyErr_NoMemory();
         return -1;
     }
-    pack_add_info(self, "HotShot-Version", buffer);
-    pack_add_info(self, "Requested-Line-Events",
+    pack_add_info(self, "hotshot-version", buffer);
+    pack_add_info(self, "requested-frame-timings",
+                  (self->frametimings ? "yes" : "no"));
+    pack_add_info(self, "requested-line-events",
                   (self->lineevents ? "yes" : "no"));
-    pack_add_info(self, "Platform", Py_GetPlatform());
-    pack_add_info(self, "Executable", Py_GetProgramFullPath());
+    pack_add_info(self, "requested-line-timings",
+                  (self->linetimings ? "yes" : "no"));
+    pack_add_info(self, "platform", Py_GetPlatform());
+    pack_add_info(self, "executable", Py_GetProgramFullPath());
     buffer = (char *) Py_GetVersion();
     if (buffer == NULL)
         PyErr_Clear();
     else
-        pack_add_info(self, "Executable-Version", buffer);
+        pack_add_info(self, "executable-version", buffer);
 
 #ifdef MS_WIN32
     sprintf(cwdbuffer, "%I64d", frequency.QuadPart);
-    pack_add_info(self, "Reported-Performance-Frequency", cwdbuffer);
+    pack_add_info(self, "reported-performance-frequency", cwdbuffer);
 #else
     sprintf(cwdbuffer, "%lu", rusage_diff);
-    pack_add_info(self, "Observed-Interval-getrusage", cwdbuffer);
+    pack_add_info(self, "observed-interval-getrusage", cwdbuffer);
     sprintf(cwdbuffer, "%lu", timeofday_diff);
-    pack_add_info(self, "Observed-Interval-gettimeofday", cwdbuffer);
+    pack_add_info(self, "observed-interval-gettimeofday", cwdbuffer);
 #endif
-    pack_frame_times(self);
-    pack_line_times(self);
 
-    pack_add_info(self, "Current-Directory",
+    pack_add_info(self, "current-directory",
                   getcwd(cwdbuffer, sizeof cwdbuffer));
 
     temp = PySys_GetObject("path");
@@ -1355,8 +1470,11 @@
         buffer = PyString_AsString(item);
         if (buffer == NULL)
             return -1;
-        pack_add_info(self, "Sys-Path-Entry", buffer);
+        pack_add_info(self, "sys-path-entry", buffer);
     }
+    pack_frame_times(self);
+    pack_line_times(self);
+
     return 0;
 }