PROTON-2144: Create memory tracking framework
- Note this is currently not enabled by default, as
  currently there are some important unknowns.
- However it is potentially very useful so it can
  be turned on with a compile time define (PN_MEMDEBUG).
- In future it should be characterised and perhaps
  enabled by default.
diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt
index 9981a08..d2e2888 100644
--- a/c/CMakeLists.txt
+++ b/c/CMakeLists.txt
@@ -245,6 +245,7 @@
   src/core/object/record.c
 
   src/core/init.c
+  src/core/memory.c
   src/core/logger.c
   src/core/util.c
   src/core/error.c
diff --git a/c/include/proton/logger.h b/c/include/proton/logger.h
index 68e5f29..8dfc74c 100644
--- a/c/include/proton/logger.h
+++ b/c/include/proton/logger.h
@@ -92,12 +92,13 @@
  */
 typedef enum pn_log_subsystem_t {
     PN_SUBSYSTEM_NONE    = 0,    /**< No subsystem */
-    PN_SUBSYSTEM_IO      = 1,    /**< Low level Input/Output */
-    PN_SUBSYSTEM_EVENT   = 2,    /**< Events */
-    PN_SUBSYSTEM_AMQP    = 4,    /**< AMQP protocol processing */
-    PN_SUBSYSTEM_SSL     = 8,    /**< TLS/SSL protocol processing */
-    PN_SUBSYSTEM_SASL    = 16,   /**< SASL protocol processing */
-    PN_SUBSYSTEM_BINDING = 32,   /**< Language binding */
+    PN_SUBSYSTEM_MEMORY  = 1,    /**< Memory usage */
+    PN_SUBSYSTEM_IO      = 2,    /**< Low level Input/Output */
+    PN_SUBSYSTEM_EVENT   = 4,    /**< Events */
+    PN_SUBSYSTEM_AMQP    = 8,    /**< AMQP protocol processing */
+    PN_SUBSYSTEM_SSL     = 16,   /**< TLS/SSL protocol processing */
+    PN_SUBSYSTEM_SASL    = 32,   /**< SASL protocol processing */
+    PN_SUBSYSTEM_BINDING = 64,   /**< Language binding */
     PN_SUBSYSTEM_ALL     = 65535 /**< Every subsystem */
 } pn_log_subsystem_t; /* We hint to the compiler it can use 16 bits for this value */
 
diff --git a/c/src/core/init.c b/c/src/core/init.c
index 105ec41..1a93373 100644
--- a/c/src/core/init.c
+++ b/c/src/core/init.c
@@ -18,13 +18,16 @@
  */
 
 #include "core/logger_private.h"
+#include "core/memory.h"
 
 void pn_init(void)
 {
   pni_init_default_logger();
+  pni_init_memory();
 }
 
 void pn_fini(void)
 {
+  pni_fini_memory();
   pni_fini_default_logger();
 }
diff --git a/c/src/core/logger.c b/c/src/core/logger.c
index 1ae4eae..631627b 100644
--- a/c/src/core/logger.c
+++ b/c/src/core/logger.c
@@ -21,6 +21,7 @@
 #include <proton/error.h>
 
 #include "logger_private.h"
+#include "memory.h"
 #include "util.h"
 
 #include <assert.h>
@@ -54,13 +55,15 @@
   logger->scratch = NULL;
 }
 
-#define LOGLEVEL(x) {#x, sizeof(#x)-1, PN_LEVEL_ ## x, (pn_log_level_t)(PN_LEVEL_ ## x-1)}
-#define TRACE(x) {#x, sizeof(#x)-1, PN_LEVEL_ ## x, PN_LEVEL_NONE}
+#define LOGLEVEL(x)   {sizeof(#x)-1, #x, PN_LEVEL_ ## x, PN_LEVEL_ ## x-1}
+#define TRACE(x)      {sizeof(#x)-1, #x, PN_LEVEL_ ## x}
+#define SPECIAL(x, y) {sizeof(#x)-1, #x, PN_LEVEL_NONE, PN_LEVEL_NONE, y}
 typedef struct {
-    const char *str;
-    size_t     strlen;
-    pn_log_level_t level;
-    pn_log_level_t plus_levels;
+    uint8_t   strlen;
+    const char str[11];
+    uint16_t   level;
+    uint16_t   plus_levels;
+    void     (*special)(void);
 } log_level;
 static const log_level log_levels[] = {
   LOGLEVEL(ERROR),
@@ -71,7 +74,8 @@
   LOGLEVEL(ALL),
   TRACE(FRAME),
   TRACE(RAW),
-  {NULL, 0, PN_LEVEL_ALL}
+  SPECIAL(MEMORY, pni_mem_setup_logging),
+  {0, ""}
 };
 
 void pni_decode_log_env(const char *log_env, int *setmask)
@@ -79,7 +83,7 @@
   if (!log_env) return;
 
   for (int i = 0; log_env[i]; i++) {
-    for (const log_level *level = &log_levels[0]; level->str; level++) {
+    for (const log_level *level = &log_levels[0]; level->strlen; level++) {
       if (pn_strncasecmp(&log_env[i], level->str, level->strlen)==0) {
         *setmask |= level->level;
         i += level->strlen;
@@ -88,6 +92,7 @@
           *setmask |= level->plus_levels;
         }
         i--;
+        if (level->special) level->special();
         break;
       }
     }
@@ -136,6 +141,7 @@
 const char *pn_logger_subsystem_name(pn_log_subsystem_t subsystem)
 {
   if (subsystem==PN_SUBSYSTEM_ALL)    return "*ALL*";
+  if (subsystem&PN_SUBSYSTEM_MEMORY)  return "MEMORY";
   if (subsystem&PN_SUBSYSTEM_IO)      return "IO";
   if (subsystem&PN_SUBSYSTEM_EVENT)   return "EVENT";
   if (subsystem&PN_SUBSYSTEM_AMQP)    return "AMQP";
diff --git a/c/src/core/memory.c b/c/src/core/memory.c
new file mode 100644
index 0000000..213eab1
--- /dev/null
+++ b/c/src/core/memory.c
@@ -0,0 +1,278 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "core/memory.h"
+
+#ifdef PN_MEMDEBUG
+#include "logger_private.h"
+
+#include "proton/object.h"
+#include "proton/cid.h"
+
+#include <stdlib.h>
+
+#include <signal.h>
+
+// Non portable actual size of allocated block
+// malloc_usable_size() for glibc
+// _msize() for MSCRT
+// malloc_size() for BSDs
+#ifdef __GLIBC__
+#include <malloc.h>
+#define msize malloc_usable_size
+#elif defined(_WIN32)
+#include <malloc.h>
+#define msize _msize
+#elif defined(__APPLE__) || defined(__FreeBSD__)
+#include <malloc/malloc.h>
+#define msize malloc_size
+#else
+#define msize(x) (0)
+#endif
+
+static struct stats {
+  const char* name;
+  size_t count_alloc;
+  size_t count_dealloc;
+  size_t requested;
+  size_t alloc;
+  size_t dealloc;
+  size_t count_suballoc;
+  size_t count_subrealloc;
+  size_t count_subdealloc;
+  size_t subrequested;
+  size_t suballoc;
+  size_t subdealloc;
+} stats[CID_pn_listener_socket+1] = {{0}}; // Just happens to be the last CID
+
+static bool debug_memory = false;
+
+// Hidden export
+PN_EXTERN void pn_mem_dump_stats(void);
+
+// Dump out memory use
+void pn_mem_dump_stats(void)
+{
+  pn_logger_t *logger = pn_default_logger();
+
+  size_t total_alloc = 0;
+  size_t total_dealloc = 0;
+  size_t count_alloc = 0;
+  size_t count_dealloc = 0;
+
+  if (PN_SHOULD_LOG(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_DEBUG)) {
+    pn_logger_logf(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_DEBUG, "Memory allocation stats:");
+    pn_logger_logf(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_DEBUG, "%15s:          %5s %5s %5s %9s %9s",
+                   "class", "alloc", "free", "rallc", "bytes alloc", "bytes free"
+    );
+  }
+  for (int i = 1; i<=CID_pn_listener_socket; i++) {
+    struct stats *entry = &stats[i];
+    count_alloc += entry->count_alloc+entry->count_suballoc;
+    count_dealloc += entry->count_dealloc+entry->count_subdealloc;
+    total_alloc += entry->alloc+entry->suballoc;
+    total_dealloc += entry->dealloc+entry->subdealloc;
+    if (entry->name && PN_SHOULD_LOG(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_DEBUG)) {
+      pn_logger_logf(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_DEBUG, "%15s:   direct %5d %5d       %9d %9d",
+                     entry->name,
+                     entry->count_alloc, entry->count_dealloc,
+                     entry->alloc, entry->dealloc
+      );
+      pn_logger_logf(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_DEBUG, "%15s: indirect %5d %5d %5d %9d %9d",
+                     entry->name,
+                     entry->count_suballoc, entry->count_subdealloc, entry->count_subrealloc,
+                     entry->suballoc, entry->subdealloc
+      );
+    }
+  }
+
+  if (!PN_SHOULD_LOG(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_INFO)) return;
+
+  pn_logger_logf(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_INFO, "%15s:        %7d %5d       %9d %9d",
+                 "Totals", count_alloc, count_dealloc, total_alloc, total_dealloc
+  );
+}
+
+#ifdef _WIN32
+void pni_mem_setup_logging(void)
+{
+}
+#else
+static void pn_mem_dump_stats_int(int i)
+{
+  pn_logger_t *logger = pn_default_logger();
+  pn_logger_t save = *logger;
+
+  pn_logger_set_mask(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_DEBUG | PN_LEVEL_INFO);
+
+  pn_mem_dump_stats();
+
+  *logger = save;
+}
+
+void pni_mem_setup_logging(void)
+{
+  debug_memory = true;
+  signal(SIGUSR1, pn_mem_dump_stats_int);
+}
+#endif
+
+void pni_init_memory(void)
+{
+}
+
+void pni_fini_memory(void)
+{
+  pn_logger_t *logger = pn_default_logger();
+  pn_logger_t save = *logger;
+
+  if (debug_memory)
+    pn_logger_set_mask(logger, PN_SUBSYSTEM_MEMORY, PN_LEVEL_DEBUG | PN_LEVEL_INFO);
+
+  pn_mem_dump_stats();
+
+  *logger = save;
+}
+
+static inline struct stats *pni_track_common(const pn_class_t *clazz, void *o, size_t *size)
+{
+  struct stats *entry = &stats[pn_class_id(clazz)];
+  if (!entry->name) {entry->name = pn_class_name(clazz);}
+  *size = msize(o);
+  return entry;
+}
+
+static void pni_track_alloc(const pn_class_t *clazz, void *o, size_t requested)
+{
+  size_t size;
+  struct stats *entry = pni_track_common(clazz, o, &size);
+
+  entry->count_alloc++;
+  entry->alloc += size;
+  entry->requested += requested;
+}
+
+static void pni_track_dealloc(const pn_class_t *clazz, void *o)
+{
+  size_t size;
+  struct stats *entry = pni_track_common(clazz, o, &size);
+
+  entry->count_dealloc++;
+  entry->dealloc += size;
+}
+
+static void pni_track_suballoc(const pn_class_t *clazz, void *o, size_t requested)
+{
+  size_t size;
+  struct stats *entry = pni_track_common(clazz, o, &size);
+
+  entry->count_suballoc++;
+  entry->subrequested = requested;
+  entry->suballoc += size;
+}
+
+static void pni_track_subdealloc(const pn_class_t *clazz, void *o)
+{
+  size_t size;
+  struct stats *entry = pni_track_common(clazz, o, &size);
+
+  entry->count_subdealloc++;
+  entry->subdealloc += size;
+}
+
+static void pni_track_subrealloc(const pn_class_t *clazz, void *o, size_t oldsize, size_t requested)
+{
+  size_t size;
+  struct stats *entry = pni_track_common(clazz, o, &size);
+
+  if (oldsize) {
+    entry->count_subrealloc++;
+  } else {
+    entry->count_suballoc++;
+  }
+
+  int change = size - oldsize;
+  if (change > 0) entry->suballoc += change;
+  if (change < 0) entry->subdealloc += -change;
+}
+
+void *pni_mem_zallocate(const pn_class_t *clazz, size_t size)
+{
+  void *o = calloc(1, size);
+  pni_track_alloc(clazz, o, size);
+  return o;
+}
+
+void *pni_mem_allocate(const pn_class_t *clazz, size_t size)
+{
+  void * o = malloc(size);
+  pni_track_alloc(clazz, o, size);
+  return o;
+}
+
+void pni_mem_deallocate(const pn_class_t *clazz, void *object)
+{
+  if (!object) return;
+  pni_track_dealloc(clazz, object);
+  free(object);
+}
+
+void *pni_mem_suballocate(const pn_class_t *clazz, void *object, size_t size)
+{
+  void * o = malloc(size);
+  pni_track_suballoc(clazz, o, size);
+  return o;
+}
+
+void *pni_mem_subreallocate(const pn_class_t *clazz, void *object, void *buffer, size_t size)
+{
+  size_t oldsize = buffer ? msize(buffer) : 0;
+  void *o = realloc(buffer, size);
+  pni_track_subrealloc(clazz, o, oldsize, size);
+  return o;
+}
+
+void pni_mem_subdeallocate(const pn_class_t *clazz, void *object, void *buffer)
+{
+  if (!buffer) return;
+  pni_track_subdealloc(clazz, buffer);
+  free(buffer);
+}
+#else
+
+// Versions with no memory debugging - so we can compile with no performance penalty
+
+#include <stdlib.h>
+
+void pni_init_memory(void) {}
+void pni_fini_memory(void) {}
+
+void pni_mem_setup_logging(void) {}
+
+void *pni_mem_allocate(const pn_class_t *clazz, size_t size) { return malloc(size); }
+void *pni_mem_zallocate(const pn_class_t *clazz, size_t size) { return calloc(1, size); }
+void pni_mem_deallocate(const pn_class_t *clazz, void *object) { free(object); }
+
+void *pni_mem_suballocate(const pn_class_t *clazz, void *object, size_t size) { return malloc(size); }
+void *pni_mem_subreallocate(const pn_class_t *clazz, void *object, void *buffer, size_t size) { return realloc(buffer, size); }
+void pni_mem_subdeallocate(const pn_class_t *clazz, void *object, void *buffer) { free(buffer); }
+
+#endif
diff --git a/c/src/core/memory.h b/c/src/core/memory.h
new file mode 100644
index 0000000..23a448a
--- /dev/null
+++ b/c/src/core/memory.h
@@ -0,0 +1,42 @@
+#ifndef MEMORY_H
+#define MEMORY_H 1
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <proton/object.h>
+
+#include <stddef.h>
+
+void pni_init_memory(void);
+void pni_fini_memory(void);
+
+void pni_mem_setup_logging(void);
+
+void *pni_mem_allocate(const pn_class_t *clazz, size_t size);
+void *pni_mem_zallocate(const pn_class_t *clazz, size_t size);
+void pni_mem_deallocate(const pn_class_t *clazz, void *object);
+
+void *pni_mem_suballocate(const pn_class_t *clazz, void *object, size_t size);
+void *pni_mem_subreallocate(const pn_class_t *clazz, void *object, void *buffer, size_t size);
+void pni_mem_subdeallocate(const pn_class_t *clazz, void *object, void *buffer);
+
+#endif // MEMORY_H