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