[glib/halfline/debug-metrics: 4/12] gmetrics: Add debug api for print metrics
- From: Ray Strode <halfline src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/halfline/debug-metrics: 4/12] gmetrics: Add debug api for print metrics
- Date: Tue, 21 Dec 2021 17:45:09 +0000 (UTC)
commit 27c96b231a78f0f439aca9121aa162f201035f4a
Author: Ray Strode <rstrode redhat com>
Date: Sun Jun 27 18:02:10 2021 -0400
gmetrics: Add debug api for print metrics
In order to get a handle on memory usage it would be good to get
reports of various types of object usage over time.
This commit provides a little api for generating CSV files to
dump the data to.
glib/Makefile.am | 6 +-
glib/glib-init.c | 1 +
glib/glib-init.h | 1 +
glib/glib.h | 1 +
glib/gmessages.c | 2 +
glib/gmessages.h | 1 -
glib/gmetrics.c | 2781 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
glib/gmetrics.h | 333 +++++++
glib/uthash.h | 1138 ++++++++++++++++++++++
glib/utlist.h | 1073 +++++++++++++++++++++
glib/utstring.h | 411 ++++++++
11 files changed, 5745 insertions(+), 3 deletions(-)
---
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 8da549c7f..983134428 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -143,6 +143,7 @@ libglib_2_0_la_SOURCES = \
gmarkup.c \
gmem.c \
gmessages.c \
+ gmetrics.c \
gmirroringtable.h \
gnode.c \
goption.c \
@@ -278,6 +279,7 @@ glibsubinclude_HEADERS = \
gmarkup.h \
gmem.h \
gmessages.h \
+ gmetrics.h \
gnode.h \
goption.h \
gpattern.h \
@@ -351,8 +353,8 @@ pcre_lib = pcre/libpcre.la
pcre_inc =
endif
-libglib_2_0_la_CFLAGS = $(AM_CFLAGS) $(GLIB_HIDDEN_VISIBILITY_CFLAGS) $(LIBSYSTEMD_CFLAGS)
-libglib_2_0_la_LIBADD = libcharset/libcharset.la $(printf_la) @GIO@ @GSPAWN@ @PLATFORMDEP@ @ICONV_LIBS@
@G_LIBS_EXTRA@ $(pcre_lib) $(G_THREAD_LIBS_EXTRA) $(G_THREAD_LIBS_FOR_GTHREAD) $(LIBSYSTEMD_LIBS)
+libglib_2_0_la_CFLAGS = $(AM_CFLAGS) $(GLIB_HIDDEN_VISIBILITY_CFLAGS) $(LIBSYSTEMD_CFLAGS) $(ZLIB_CFLAGS)
+libglib_2_0_la_LIBADD = libcharset/libcharset.la $(printf_la) @GIO@ @GSPAWN@ @PLATFORMDEP@ @ICONV_LIBS@
@G_LIBS_EXTRA@ $(pcre_lib) $(G_THREAD_LIBS_EXTRA) $(G_THREAD_LIBS_FOR_GTHREAD) $(LIBSYSTEMD_LIBS) $(ZLIB_LIBS)
libglib_2_0_la_DEPENDENCIES = libcharset/libcharset.la $(printf_la) @GIO@ @GSPAWN@ @PLATFORMDEP@
$(glib_win32_res) $(glib_def)
libglib_2_0_la_LDFLAGS = $(GLIB_LINK_FLAGS) \
diff --git a/glib/glib-init.c b/glib/glib-init.c
index 5f312113a..ef6d7a9f0 100644
--- a/glib/glib-init.c
+++ b/glib/glib-init.c
@@ -268,6 +268,7 @@ glib_init (void)
glib_inited = TRUE;
+ g_metrics_init ();
g_messages_prefixed_init ();
g_debug_init ();
g_quark_init ();
diff --git a/glib/glib-init.h b/glib/glib-init.h
index 5da33c052..8d44a0f48 100644
--- a/glib/glib-init.h
+++ b/glib/glib-init.h
@@ -21,6 +21,7 @@
#define __GLIB_INIT_H__
#include "gmessages.h"
+#include "gmetrics.h"
extern GLogLevelFlags g_log_always_fatal;
extern GLogLevelFlags g_log_msg_prefix;
diff --git a/glib/glib.h b/glib/glib.h
index 4f5a7f702..33ed0de80 100644
--- a/glib/glib.h
+++ b/glib/glib.h
@@ -60,6 +60,7 @@
#include <glib/gmarkup.h>
#include <glib/gmem.h>
#include <glib/gmessages.h>
+#include <glib/gmetrics.h>
#include <glib/gnode.h>
#include <glib/goption.h>
#include <glib/gpattern.h>
diff --git a/glib/gmessages.c b/glib/gmessages.c
index cbaed5da9..5d2fe1667 100644
--- a/glib/gmessages.c
+++ b/glib/gmessages.c
@@ -202,6 +202,8 @@
#include <unistd.h>
#endif
+#include <zlib.h>
+
#ifdef G_OS_WIN32
#include <process.h> /* For getpid() */
#include <io.h>
diff --git a/glib/gmessages.h b/glib/gmessages.h
index cebdc1659..6690f95cc 100644
--- a/glib/gmessages.h
+++ b/glib/gmessages.h
@@ -618,7 +618,6 @@ GPrintFunc g_set_printerr_handler (GPrintFunc func);
return (val); }G_STMT_END
#endif /* !G_DISABLE_CHECKS */
-
G_END_DECLS
#endif /* __G_MESSAGES_H__ */
diff --git a/glib/gmetrics.c b/glib/gmetrics.c
new file mode 100644
index 000000000..6012ed335
--- /dev/null
+++ b/glib/gmetrics.c
@@ -0,0 +1,2781 @@
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GLib Team and others 1997-2000. See the AUTHORS
+ * file for a list of people on the GLib Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GLib at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+/*
+ * MT safe
+ */
+
+#include "config.h"
+
+#include <execinfo.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <signal.h>
+#include <locale.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/timerfd.h>
+#include <sys/types.h>
+
+#define uthash_malloc(_s) g_metrics_allocation_block_store_allocate_with_name
(metrics_allocation_block_store, _s, "uthash_malloc")
+#define uthash_free(_p,_s) g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, _p)
+#include <uthash.h>
+#include <utlist.h>
+#include <utstring.h>
+
+
+#include "glib-init.h"
+#include "galloca.h"
+#include "gbacktrace.h"
+#include "gcharset.h"
+#include "gconvert.h"
+#include "genviron.h"
+#include "gfileutils.h"
+#include "gmain.h"
+#include "gmem.h"
+#include "gmetrics.h"
+#include "gprintfint.h"
+#include "gtestutils.h"
+#include "gthread.h"
+#include "gstdio.h"
+#include "gstrfuncs.h"
+#include "gstring.h"
+#include "gtimer.h"
+
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <zlib.h>
+
+extern void *__libc_malloc (size_t size);
+extern void *__libc_realloc (void *ptr, size_t size);
+extern void *__libc_calloc (size_t num, size_t size);
+extern void __libc_free (void *ptr);
+typedef struct _GMetricsConfig GMetricsConfig;
+typedef struct _GMetricsAllocationHeader GMetricsAllocationHeader;
+typedef union _GMetricsAllocationBlock GMetricsAllocationBlock;
+typedef struct _GMetricsAllocation GMetricsAllocation;
+typedef struct _GMetricsAllocationBlockStore GMetricsAllocationBlockStore;
+typedef struct _GMetricsAllocationBlockStoreIter GMetricsAllocationBlockStoreIter;
+typedef struct _GMetricsAllocationBlocksIter GMetricsAllocationBlocksIter;
+typedef struct _GMetricsListNode GMetricsListNode;
+
+#define round_to_multiple(n, m) (((n) + (((m) - 1))) & ~((m) - 1))
+
+struct _GMetricsConfig
+{
+ char log_dir[1024];
+ char skipped_metrics[512];
+ char included_metrics[512];
+ char collection_ignore_list[512];
+ char collection_include_list[512];
+ guint collection_interval;
+ int stack_trace_size;
+ int stack_trace_annotation_size;
+ gsize max_allocation_block_stores;
+ gsize allocation_block_store_size;
+ gsize dedicated_allocation_block_store_threshold;
+ gsize generations_to_settle;
+ gsize generations_to_reset_average_window;
+ gsize number_of_interesting_instances;
+ gsize stack_trace_sample_interval;
+ guint32 metrics_enabled : 1;
+ guint32 override_system_malloc : 1;
+ guint32 override_glib_malloc : 1;
+ guint32 validate_allocation_blocks : 1;
+ guint32 use_map_files : 1;
+};
+
+struct _GMetricsFile
+{
+ gzFile gzipped_file;
+ char *static_format_string;
+ char *variadic_format_string;
+ gdouble now;
+};
+
+struct _GMetricsAllocationHeader
+{
+ char name[64];
+ guint32 is_freed;
+ gsize number_of_blocks;
+ GMetricsAllocationBlock *previous_block;
+};
+
+union _GMetricsAllocationBlock
+{
+ GMetricsAllocationHeader header;
+ char payload[128];
+};
+
+struct _GMetricsAllocation
+{
+ GMetricsAllocationBlock header_block;
+ GMetricsAllocationBlock payload_blocks[0];
+};
+
+typedef struct _GMetricsAllocationFileMap GMetricsAllocationFileMap;
+struct _GMetricsAllocationFileMap
+{
+ GMetricsAllocationBlockStore *block_store;
+ union
+ {
+ char *map_address;
+ GMetricsAllocationBlock *blocks;
+ };
+ gsize size;
+ gsize number_of_blocks;
+ GMetricsAllocationBlock *large_free_block;
+};
+
+struct _GMetricsAllocationBlockStore
+{
+ char name[128];
+ char thread_name[32];
+ GMetricsStackTrace *stack_trace;
+ GMetricsAllocationFileMap file_map;
+ gsize number_of_allocations;
+ gsize total_bytes_allocated;
+ guint32 is_dedicated : 1;
+ guint32 is_thread_default : 1;
+};
+
+struct _GMetricsAllocationBlockStoreIter
+{
+ GMetricsListIter list_iter;
+};
+
+struct _GMetricsAllocationBlocksIter
+{
+ GMetricsAllocationFileMap *file_map;
+ GMetricsAllocationBlock *starting_block;
+ GMetricsAllocationBlock *previous_block;
+ gsize items_examined;
+};
+
+static void g_metrics_allocation_blocks_iter_init_after_block (GMetricsAllocationBlocksIter *iter,
+ GMetricsAllocationFileMap *file_map,
+ GMetricsAllocationBlock *block);
+static gboolean g_metrics_allocation_blocks_iter_next (GMetricsAllocationBlocksIter *iter,
+ GMetricsAllocationBlock **next_block);
+
+static gboolean g_metrics_allocation_file_map_is_open (GMetricsAllocationFileMap *file_map);
+
+static volatile gboolean needs_flush;
+static gulong g_metrics_generation;
+static int timeout_fd = -1;
+GMetricsList *timeout_handlers;
+G_LOCK_DEFINE_STATIC (timeouts);
+static GMetricsAllocationBlockStore store_for_allocation_block_stores;
+G_LOCK_DEFINE_STATIC (allocation_block_stores);
+static GMetricsAllocationBlockStore *metrics_allocation_block_store;
+static __thread GMetricsList block_store_stack;
+static GMetricsList allocation_block_stores;
+static GMetricsFile *allocation_block_store_metrics_file;
+G_LOCK_DEFINE_STATIC (allocations);
+
+static __thread GMetricsStackTraceAnnotationHandler stack_trace_annotation_handler;
+static __thread gpointer stack_trace_annotation_handler_user_data;
+
+static GMetricsConfig metrics_config;
+
+static const char *default_skipped_metrics="arrays lists metrics-allocations objects-by-type ptr-arrays
signals";
+static const char *default_collection_ignore_list="Handler GSList";
+
+static char *
+int_to_string (guint64 integer,
+ char *string,
+ gsize string_size)
+{
+ gsize i, j, bytes_used;
+ char swap_byte;
+
+ bytes_used = 0;
+ while (integer != 0 && bytes_used < string_size)
+ {
+ string[bytes_used++] = (integer % 10) + '0';
+ integer /= 10;
+ }
+ string[bytes_used] = '\0';
+
+ j = bytes_used - 1;
+ for (i = 0; i < j; i++, j--)
+ {
+ swap_byte = string[i];
+ string[i] = string[j];
+ string[j] = swap_byte;
+ }
+ return string + bytes_used;
+}
+
+gboolean
+g_metrics_enabled (void)
+{
+ return metrics_config.metrics_enabled;
+}
+
+static gsize
+g_metrics_allocation_get_payload_size (GMetricsAllocation *allocation)
+{
+ GMetricsAllocationHeader *header;
+ header = &allocation->header_block.header;
+
+ return (header->number_of_blocks * sizeof (GMetricsAllocationBlock)) - sizeof (allocation->header_block);
+}
+
+static void
+g_metrics_allocation_file_map_validate_block (GMetricsAllocationFileMap *file_map,
+ GMetricsAllocationBlock *block)
+{
+ GMetricsAllocation *allocation;
+ GMetricsAllocationHeader *header;
+
+ if (!metrics_config.validate_allocation_blocks)
+ return;
+
+ if (!block)
+ return;
+
+ allocation = (GMetricsAllocation *) block;
+ header = &allocation->header_block.header;
+
+ if (header->is_freed != 1 && header->is_freed != 0)
+ G_BREAKPOINT ();
+
+ if (header->number_of_blocks == 0)
+ G_BREAKPOINT ();
+
+ if (header->number_of_blocks > file_map->number_of_blocks)
+ G_BREAKPOINT ();
+
+ if (header->previous_block != NULL)
+ {
+ GMetricsAllocationHeader *previous_header;
+
+ if (header->previous_block < file_map->blocks)
+ G_BREAKPOINT ();
+
+ previous_header = (GMetricsAllocationHeader *) header->previous_block;
+
+ if (previous_header->number_of_blocks == 0)
+ G_BREAKPOINT ();
+
+ if (previous_header->number_of_blocks > file_map->number_of_blocks)
+ G_BREAKPOINT ();
+
+ if (header->previous_block + previous_header->number_of_blocks != block)
+ G_BREAKPOINT ();
+ }
+
+ if (block + header->number_of_blocks < file_map->blocks + file_map->number_of_blocks)
+ {
+ GMetricsAllocationBlock *next_block;
+ GMetricsAllocationHeader *next_header;
+
+ next_block = block + header->number_of_blocks;
+ next_header = (GMetricsAllocationHeader *) next_block;
+
+ if (next_header->number_of_blocks == 0)
+ G_BREAKPOINT ();
+
+ if (next_header->number_of_blocks > file_map->number_of_blocks)
+ G_BREAKPOINT ();
+
+ if (next_header->previous_block != block)
+ G_BREAKPOINT ();
+ }
+}
+
+static gboolean
+g_metrics_allocation_file_map_open (GMetricsAllocationFileMap *file_map,
+ gsize size)
+{
+ GMetricsAllocationBlockStore *block_store;
+ char filename[1024] = "";
+ int result;
+ int map_fd = -1;
+ int mmap_flags;
+
+ file_map->size = size;
+
+ block_store = file_map->block_store;
+ snprintf (filename, sizeof (filename) - 1, "/var/tmp/user-%d-for-pid-%d-%s-%p.map", getuid (), getpid (),
block_store->name, file_map);
+ unlink (filename);
+
+ map_fd = open (filename, O_RDWR | O_CREAT, 0644);
+
+ if (map_fd < 0)
+ goto fail;
+
+ result = ftruncate (map_fd, file_map->size);
+
+ unlink (filename);
+
+ if (result < 0)
+ goto fail;
+
+ if (metrics_config.use_map_files)
+ mmap_flags = MAP_SHARED;
+ else
+ mmap_flags = MAP_PRIVATE;
+
+ file_map->map_address = mmap (NULL, file_map->size, PROT_READ | PROT_WRITE, mmap_flags, map_fd, 0);
+
+ if (file_map->map_address == MAP_FAILED)
+ goto fail;
+
+ close (map_fd);
+ map_fd = -1;
+
+ file_map->number_of_blocks = file_map->size / sizeof (GMetricsAllocationBlock);
+
+ file_map->blocks[0].header.number_of_blocks = file_map->number_of_blocks;
+ file_map->blocks[0].header.is_freed = TRUE;
+ file_map->blocks[0].header.previous_block = NULL;
+
+ file_map->large_free_block = &file_map->blocks[0];
+
+ return TRUE;
+
+fail:
+ if (map_fd >= 0)
+ {
+ close (map_fd);
+ map_fd = -1;
+ }
+ file_map->size = 0;
+ file_map->map_address = MAP_FAILED;
+ return FALSE;
+}
+
+static void
+g_metrics_allocation_file_map_close (GMetricsAllocationFileMap *file_map)
+{
+ munmap (file_map->map_address, file_map->size);
+ file_map->map_address = MAP_FAILED;
+}
+
+static gboolean
+g_metrics_allocation_file_map_is_open (GMetricsAllocationFileMap *file_map)
+{
+ return file_map->map_address != MAP_FAILED;
+}
+
+static gboolean
+g_metrics_allocation_file_map_has_allocation (GMetricsAllocationFileMap *file_map,
+ gpointer allocation)
+{
+ char *allocation_bytes = allocation;
+ return allocation_bytes >= file_map->map_address && allocation_bytes < (file_map->map_address +
file_map->size);
+}
+
+static gsize
+g_metrics_allocation_file_map_get_index_of_block (GMetricsAllocationFileMap *file_map,
+ GMetricsAllocationBlock *block)
+{
+ return block - file_map->blocks;
+}
+
+static void
+g_metrics_allocation_file_map_shrink_allocation (GMetricsAllocationFileMap *file_map,
+ GMetricsAllocation *allocation,
+ gsize number_of_blocks)
+{
+ GMetricsAllocationBlockStore *block_store;
+ GMetricsAllocationHeader *header;
+ GMetricsAllocationBlock *first_block;
+ gsize blocks_left;
+
+ block_store = file_map->block_store;
+ header = &allocation->header_block.header;
+ first_block = (GMetricsAllocationBlock *) allocation;
+
+ blocks_left = header->number_of_blocks - number_of_blocks;
+ header->number_of_blocks = number_of_blocks;
+
+ if (blocks_left > 0)
+ {
+ GMetricsAllocationBlock *next_block, *block_after_next;
+ GMetricsAllocation *next_allocation;
+ GMetricsAllocationHeader *next_header;
+
+ next_block = first_block + number_of_blocks;
+
+ if (metrics_config.validate_allocation_blocks)
+ {
+ if (next_block >= &file_map->blocks[file_map->number_of_blocks])
+ G_BREAKPOINT ();
+ }
+
+ next_allocation = (GMetricsAllocation *) next_block;
+ next_header = &next_allocation->header_block.header;
+
+ next_header->number_of_blocks = blocks_left;
+ next_header->is_freed = TRUE;
+ next_header->previous_block = first_block;
+
+ if (file_map->large_free_block == NULL
+ || file_map->large_free_block->header.number_of_blocks < next_header->number_of_blocks)
+ file_map->large_free_block = next_block;
+
+ block_store->total_bytes_allocated -= next_header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+
+ block_after_next = next_block + next_header->number_of_blocks;
+
+ if (block_after_next < &file_map->blocks[file_map->number_of_blocks])
+ {
+ GMetricsAllocation *allocation_after_next;
+ GMetricsAllocationHeader *header_after_next;
+
+ allocation_after_next = (GMetricsAllocation *) block_after_next;
+ header_after_next = &allocation_after_next->header_block.header;
+ header_after_next->previous_block = next_block;
+ if (metrics_config.validate_allocation_blocks)
+ {
+ if (next_block >= &file_map->blocks[file_map->number_of_blocks])
+ G_BREAKPOINT ();
+ }
+ }
+ }
+}
+
+
+static GMetricsAllocationBlock *
+find_next_free_block (GMetricsAllocationFileMap *file_map,
+ GMetricsAllocationBlock *allocated_block)
+{
+ GMetricsAllocationBlock *block, *next_block;
+
+ block = allocated_block;
+
+ g_metrics_allocation_file_map_validate_block (file_map, block);
+
+ do
+ {
+ if (block + block->header.number_of_blocks >= file_map->blocks + file_map->number_of_blocks)
+ return NULL;
+
+ next_block = block + block->header.number_of_blocks;
+
+ if (next_block->header.is_freed)
+ {
+ g_metrics_allocation_file_map_validate_block (file_map, next_block);
+
+ return next_block;
+ }
+
+ block = next_block;
+ }
+ while (block <= file_map->blocks + file_map->number_of_blocks);
+
+ return NULL;
+}
+
+static void
+consolidate_consecutive_blocks (GMetricsAllocationFileMap *file_map,
+ GMetricsAllocationBlock *block,
+ gsize blocks_needed)
+{
+ GMetricsAllocation *allocation = NULL;
+ GMetricsAllocationHeader *header;
+ GMetricsAllocationBlocksIter look_ahead_iter;
+ GMetricsAllocationBlock *look_ahead_block;
+ GMetricsAllocationBlock *next_block = NULL;
+
+ allocation = (GMetricsAllocation *) block;
+ header = &allocation->header_block.header;
+
+ if (header->number_of_blocks >= blocks_needed)
+ return;
+
+ g_metrics_allocation_blocks_iter_init_after_block (&look_ahead_iter, file_map, block);
+ while (g_metrics_allocation_blocks_iter_next (&look_ahead_iter, &look_ahead_block))
+ {
+ GMetricsAllocation *look_ahead_allocation;
+ GMetricsAllocationHeader *look_ahead_header;
+
+ if (look_ahead_block >= &file_map->blocks[file_map->number_of_blocks])
+ G_BREAKPOINT ();
+
+ look_ahead_allocation = (GMetricsAllocation *) look_ahead_block;
+ look_ahead_header = &look_ahead_allocation->header_block.header;
+
+ if (look_ahead_block < block)
+ break;
+
+ if (!look_ahead_header->is_freed)
+ break;
+
+ header->number_of_blocks += look_ahead_header->number_of_blocks;
+
+ if (metrics_config.validate_allocation_blocks)
+ memset (look_ahead_block, 0xaa, MIN (look_ahead_header->number_of_blocks, 4) * sizeof
(GMetricsAllocationBlock));
+
+ if (header->number_of_blocks >= blocks_needed)
+ break;
+
+ g_metrics_allocation_blocks_iter_init_after_block (&look_ahead_iter, file_map, block);
+ }
+
+ if (block + header->number_of_blocks < &file_map->blocks[file_map->number_of_blocks])
+ {
+ next_block = block + header->number_of_blocks;
+ next_block->header.previous_block = block;
+
+ if (block >= &file_map->blocks[file_map->number_of_blocks])
+ G_BREAKPOINT ();
+
+ g_metrics_allocation_file_map_validate_block (file_map, next_block);
+ }
+
+ g_metrics_allocation_file_map_validate_block (file_map, block);
+
+ if (file_map->large_free_block < block + header->number_of_blocks)
+ {
+ if (file_map->large_free_block > block)
+ {
+ file_map->large_free_block = block;
+ }
+ else if (file_map->large_free_block != NULL && next_block != NULL)
+ {
+ g_metrics_allocation_file_map_validate_block (file_map, next_block);
+ if (file_map->large_free_block->header.number_of_blocks < next_block->header.number_of_blocks)
+ file_map->large_free_block = next_block;
+ }
+ }
+}
+
+static void
+g_metrics_allocation_file_map_claim_allocation (GMetricsAllocationFileMap *file_map,
+ GMetricsAllocation *allocation)
+{
+ GMetricsAllocationBlockStore *block_store;
+ GMetricsAllocationHeader *header;
+ GMetricsAllocationBlock *block;
+
+ block_store = file_map->block_store;
+ header = &allocation->header_block.header;
+ block = (GMetricsAllocationBlock *) allocation;
+
+ header->is_freed = FALSE;
+ block_store->total_bytes_allocated += header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+ block_store->number_of_allocations++;
+
+ if (file_map->large_free_block == block)
+ file_map->large_free_block = find_next_free_block (file_map, block);
+}
+
+static gboolean
+g_metrics_allocation_file_map_grow_allocation (GMetricsAllocationFileMap *file_map,
+ GMetricsAllocation *allocation,
+ gsize number_of_blocks)
+{
+ GMetricsAllocationHeader *header;
+ GMetricsAllocationBlock *first_block;
+ GMetricsAllocationBlockStore *block_store;
+ gsize old_size;
+
+ block_store = file_map->block_store;
+ header = &allocation->header_block.header;
+ first_block = (GMetricsAllocationBlock *) allocation;
+
+ old_size = header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+ consolidate_consecutive_blocks (file_map, first_block, number_of_blocks);
+
+ block_store->total_bytes_allocated -= old_size;
+ block_store->total_bytes_allocated += header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+
+ if (header->number_of_blocks > number_of_blocks)
+ g_metrics_allocation_file_map_shrink_allocation (file_map, allocation, number_of_blocks);
+
+ g_metrics_allocation_file_map_validate_block (file_map, first_block);
+
+ return header->number_of_blocks == number_of_blocks;
+}
+
+static void
+g_metrics_allocation_file_map_release_allocation (GMetricsAllocationFileMap *file_map,
+ GMetricsAllocation *allocation)
+{
+ GMetricsAllocationBlockStore *block_store;
+ GMetricsAllocationHeader *header;
+ GMetricsAllocationBlock *block, *previous_block;
+
+ block_store = file_map->block_store;
+ header = &allocation->header_block.header;
+ block = (GMetricsAllocationBlock *) allocation;
+
+ if (metrics_config.validate_allocation_blocks)
+ {
+ if (block < &file_map->blocks[0])
+ G_BREAKPOINT ();
+
+ if (block >= &file_map->blocks[file_map->number_of_blocks])
+ G_BREAKPOINT ();
+ }
+
+ if (header->is_freed)
+ G_BREAKPOINT ();
+
+ g_metrics_allocation_file_map_validate_block (file_map, block);
+
+ header->is_freed = TRUE;
+ block_store->total_bytes_allocated -= header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+ block_store->number_of_allocations--;
+
+ g_metrics_allocation_file_map_validate_block (file_map, file_map->large_free_block);
+
+ if (file_map->large_free_block == NULL
+ || file_map->large_free_block->header.number_of_blocks < header->number_of_blocks)
+ file_map->large_free_block = block;
+
+ if (block->header.previous_block)
+ {
+ previous_block = (GMetricsAllocationBlock *) block->header.previous_block;
+
+ if (previous_block->header.is_freed)
+ consolidate_consecutive_blocks (file_map,
+ previous_block,
+ previous_block->header.number_of_blocks + header->number_of_blocks);
+ }
+}
+
+static gboolean
+g_metrics_allocation_block_store_init (GMetricsAllocationBlockStore *block_store,
+ const char *name,
+ gsize size)
+{
+ gboolean file_map_mapped;
+
+ strncpy (block_store->name, name, sizeof (block_store->name) - 1);
+
+ block_store->file_map.block_store = block_store;
+ file_map_mapped = g_metrics_allocation_file_map_open (&block_store->file_map, size);
+
+ return file_map_mapped;
+}
+
+void
+g_metrics_allocation_block_store_free (GMetricsAllocationBlockStore *block_store)
+{
+ G_LOCK (allocation_block_stores);
+ g_metrics_allocation_file_map_close (&block_store->file_map);
+ g_metrics_list_remove_item (&allocation_block_stores, block_store);
+ g_metrics_stack_trace_free (block_store->stack_trace);
+ g_metrics_allocation_block_store_deallocate (&store_for_allocation_block_stores,
+ block_store);
+ G_UNLOCK (allocation_block_stores);
+}
+
+GMetricsAllocationBlockStore *
+g_metrics_allocation_block_store_new (const char *name,
+ gsize size)
+{
+ GMetricsAllocationBlockStore *block_store;
+ gboolean initialized;
+ char thread_name[32] = "thread-";
+ pid_t current_thread_id;
+
+ current_thread_id = (pid_t) syscall (SYS_gettid);
+ int_to_string (current_thread_id, thread_name + strlen (thread_name), sizeof (thread_name) - strlen
(thread_name));
+
+ if (name == NULL)
+ name = thread_name;
+
+ G_LOCK (allocation_block_stores);
+ block_store = g_metrics_allocation_block_store_allocate_with_name (&store_for_allocation_block_stores,
+ sizeof (GMetricsAllocationBlockStore),
+ "GMetricsAllocationBlockStore");
+ G_UNLOCK (allocation_block_stores);
+
+ memset (block_store, 0, sizeof (GMetricsAllocationBlockStore));
+
+ strncpy (block_store->thread_name, thread_name, sizeof (block_store->thread_name) - 1);
+
+ initialized = g_metrics_allocation_block_store_init (block_store, name, size);
+
+ if (!initialized)
+ {
+ G_BREAKPOINT ();
+ return NULL;
+ }
+
+ G_LOCK(allocation_block_stores);
+ if (metrics_allocation_block_store)
+ g_metrics_list_add_item (&allocation_block_stores, block_store);
+ G_UNLOCK (allocation_block_stores);
+
+ return block_store;
+}
+
+static gboolean
+g_metrics_allocation_block_store_has_allocation (GMetricsAllocationBlockStore *block_store,
+ gpointer allocation)
+{
+ return g_metrics_allocation_file_map_has_allocation (&block_store->file_map, allocation);
+}
+
+static void
+initialize_store_for_allocation_block_stores (void)
+{
+ g_metrics_allocation_block_store_init (&store_for_allocation_block_stores,
+ "allocation-block-stores",
+ metrics_config.max_allocation_block_stores * sizeof
(GMetricsAllocationBlockStore));
+}
+
+static void
+allocate_metrics_block_store (void)
+{
+ GMetricsAllocationBlockStore *block_store;
+
+ block_store = g_metrics_allocation_block_store_new ("metrics",
+ metrics_config.allocation_block_store_size);
+
+ if (block_store == NULL)
+ return;
+
+ G_LOCK (allocation_block_stores);
+ metrics_allocation_block_store = block_store;
+ g_metrics_list_add_item (&allocation_block_stores, metrics_allocation_block_store);
+ G_UNLOCK (allocation_block_stores);
+}
+
+static void
+allocate_thread_default_block_store (void)
+{
+ GMetricsAllocationBlockStore *block_store;
+
+ block_store = g_metrics_allocation_block_store_new (NULL, metrics_config.allocation_block_store_size);
+ block_store->is_thread_default = TRUE;
+ g_metrics_push_default_allocation_block_store (block_store);
+}
+
+static void
+g_metrics_allocation_block_store_iter_init (GMetricsAllocationBlockStoreIter *iter)
+{
+ g_metrics_list_iter_init (&iter->list_iter, &allocation_block_stores);
+}
+
+static gboolean
+g_metrics_allocation_block_store_iter_next (GMetricsAllocationBlockStoreIter *iter,
+ GMetricsAllocationBlockStore **block_store)
+{
+ return g_metrics_list_iter_next (&iter->list_iter, block_store);
+}
+
+static void
+write_allocation_list (GMetricsAllocationBlockStore *block_store)
+{
+ GMetricsAllocationFileMap *file_map;
+ GMetricsAllocationBlocksIter iter;
+ GMetricsAllocationBlock *block;
+ int fd = -1;
+
+ file_map = &block_store->file_map;
+ g_metrics_allocation_blocks_iter_init_after_block (&iter, file_map, NULL);
+ while (g_metrics_allocation_blocks_iter_next (&iter, &block))
+ {
+ GMetricsAllocation *allocation;
+ GMetricsAllocationHeader *header;
+
+ allocation = (GMetricsAllocation *) block;
+ header = (GMetricsAllocationHeader *) &allocation->header_block.header;
+
+ if (header->is_freed)
+ {
+ consolidate_consecutive_blocks (file_map,
+ block,
+ file_map->number_of_blocks);
+ continue;
+ }
+
+ if (header->name[0] != '\0')
+ {
+ if (fd == -1)
+ {
+ char filename[1024] = { 0 };
+ strncat (filename, metrics_config.log_dir, sizeof (filename) - 1);
+ strncat (filename, "/", sizeof (filename) - 1);
+ strncat (filename, block_store->name, sizeof (filename) - 1);
+ strncat (filename, "-allocations.list", sizeof (filename) - 1);
+ fd = open (filename, O_CREAT | O_TRUNC | O_RDWR, 0644);
+ }
+
+ write (fd, header->name, strlen (header->name));
+ write (fd, "\n", 1);
+ }
+ }
+ if (fd != -1)
+ close (fd);
+}
+
+static void
+on_allocation_block_stores_metrics_timeout (void)
+{
+ GMetricsAllocationBlockStoreIter iter;
+ GMetricsAllocationBlockStore *block_store = NULL;
+
+ if (!allocation_block_store_metrics_file)
+ return;
+
+ G_LOCK (allocation_block_stores);
+
+ G_LOCK (allocations);
+ if (g_metrics_requested ("metrics-allocations"))
+ write_allocation_list (metrics_allocation_block_store);
+ G_UNLOCK (allocations);
+
+ if (!g_metrics_requested ("allocation-block-stores"))
+ return;
+
+ g_metrics_file_start_record (allocation_block_store_metrics_file);
+ g_metrics_allocation_block_store_iter_init (&iter);
+ while (g_metrics_allocation_block_store_iter_next (&iter, &block_store))
+ {
+ const char *stack_trace = NULL;
+ GMetricsAllocationFileMap *file_map;
+
+ file_map = &block_store->file_map;
+
+ if (!g_metrics_allocation_file_map_is_open (file_map))
+ continue;
+
+ if (block_store->stack_trace != NULL)
+ stack_trace = g_metrics_stack_trace_get_output (block_store->stack_trace);
+
+ g_metrics_file_add_row (allocation_block_store_metrics_file,
+ block_store->name,
+ block_store->thread_name,
+ block_store->number_of_allocations,
+ block_store->total_bytes_allocated,
+ stack_trace?: "");
+ }
+ g_metrics_file_end_record (allocation_block_store_metrics_file);
+ G_UNLOCK (allocation_block_stores);
+}
+
+static gsize
+get_int_from_environment (const char *variable,
+ gsize default_value)
+{
+ const char *value;
+
+ value = getenv (variable);
+
+ if (value == NULL)
+ return default_value;
+
+ return strtol (value, NULL, 10);
+}
+
+static void
+load_metrics_config_command (void)
+{
+ static char cmdline[1024] = { 0 };
+ const char *requested_command;
+ const char *requested_user, *current_user;
+ int fd;
+
+ fd = open ("/proc/self/cmdline", O_RDONLY);
+ if (fd >= 0)
+ {
+ read (fd, cmdline, 1023);
+ close (fd);
+ }
+
+ current_user = getenv ("USER");
+ requested_user = getenv ("G_METRICS_USER");
+ requested_command = getenv ("G_METRICS_COMMAND")? : "gnome-shell";
+
+ metrics_config.metrics_enabled = g_str_has_suffix (cmdline, requested_command) &&
+ g_strcmp0 (current_user, requested_user) == 0;
+}
+
+static void
+load_metrics_allocation_config (void)
+{
+ metrics_config.max_allocation_block_stores = get_int_from_environment
("G_METRICS_MAX_ALLOCATION_BLOCK_STORES", 8192);
+ metrics_config.allocation_block_store_size = get_int_from_environment
("G_METRICS_DEFAULT_ALLOCATION_BLOCK_STORE_SIZE", 10485760) * 1024L;
+ metrics_config.dedicated_allocation_block_store_threshold = get_int_from_environment
("G_METRICS_DEDICATED_ALLOCATION_BLOCK_STORE_THRESHOLD", 8192);
+ metrics_config.override_system_malloc = get_int_from_environment ("G_METRICS_OVERRIDE_SYSTEM_MALLOC", 0);
+ metrics_config.override_glib_malloc = get_int_from_environment ("G_METRICS_OVERRIDE_GLIB_MALLOC", 0);
+ metrics_config.validate_allocation_blocks = get_int_from_environment
("G_METRICS_VALIDATE_ALLOCATION_BLOCKS", 0);
+ metrics_config.use_map_files = get_int_from_environment ("G_METRICS_USE_MAP_FILES", 1);
+}
+
+static void
+load_metrics_logging_config (void)
+{
+ const char *log_dir;
+
+ log_dir = getenv ("G_METRICS_LOG_DIR");
+
+ if (log_dir != NULL)
+ {
+ strncpy (metrics_config.log_dir, log_dir, sizeof (metrics_config.log_dir) - 1);
+ }
+ else
+ {
+ const char *cache_dir;
+ char process_id[32] = "";
+
+ cache_dir = getenv ("XDG_CACHE_HOME");
+
+ if (cache_dir != NULL)
+ {
+ strncat (metrics_config.log_dir, cache_dir, sizeof (metrics_config.log_dir));
+ }
+ else
+ {
+ strncat (metrics_config.log_dir, getenv ("HOME"), sizeof (metrics_config.log_dir));
+ strncat (metrics_config.log_dir, "/.cache", sizeof (metrics_config.log_dir));
+ }
+ strncat (metrics_config.log_dir, "/metrics/", sizeof (metrics_config.log_dir));
+
+ int_to_string (getpid (), process_id, sizeof (process_id));
+
+ strncat (metrics_config.log_dir, process_id, sizeof (metrics_config.log_dir));
+ }
+}
+
+static void
+load_metrics_inclusions_config (void)
+{
+ const char *included_metrics;
+
+ included_metrics = getenv ("G_METRICS_INCLUDE");
+
+ if (included_metrics != NULL)
+ strncpy (metrics_config.included_metrics,
+ included_metrics,
+ sizeof (metrics_config.included_metrics) - 1);
+}
+
+static void
+load_metrics_exclusions_config (void)
+{
+ const char *skipped_metrics;
+
+ skipped_metrics = getenv ("G_METRICS_SKIP");
+
+ if (skipped_metrics != NULL)
+ strncpy (metrics_config.skipped_metrics,
+ skipped_metrics,
+ sizeof (metrics_config.skipped_metrics) - 1);
+ else
+ strncpy (metrics_config.skipped_metrics,
+ default_skipped_metrics,
+ sizeof (metrics_config.skipped_metrics) - 1);
+}
+
+static void
+load_metrics_collection_config (void)
+{
+ const char *collection_ignore_list, *collection_include_list;
+
+ metrics_config.collection_interval = get_int_from_environment ("G_METRICS_COLLECTION_INTERVAL", 10);
+ metrics_config.generations_to_settle = get_int_from_environment
("G_METRICS_COLLECTION_NUMBER_OF_PRELOAD_INVERVALS", 10);
+ metrics_config.generations_to_reset_average_window = get_int_from_environment
("G_METRICS_COLLECTION_AVERAGE_WINDOW_THRESHOLD", 10);
+ metrics_config.number_of_interesting_instances = get_int_from_environment
("G_METRICS_COLLECTION_INSTANCE_COUNT", 10);
+ metrics_config.stack_trace_sample_interval = get_int_from_environment
("G_METRICS_STACK_TRACE_SAMPLE_INTERVAL", 1);
+
+ collection_ignore_list = getenv ("G_METRICS_COLLECTION_INSTANCE_IGNORE_LIST");
+
+ if (collection_ignore_list != NULL)
+ strncpy (metrics_config.collection_ignore_list,
+ collection_ignore_list,
+ sizeof (metrics_config.collection_ignore_list) - 1);
+ else
+ strncpy (metrics_config.collection_ignore_list,
+ default_collection_ignore_list,
+ sizeof (metrics_config.collection_ignore_list) - 1);
+
+ collection_include_list = getenv ("G_METRICS_COLLECTION_INSTANCE_INCLUDE_LIST");
+
+ if (collection_include_list != NULL)
+ strncpy (metrics_config.collection_include_list,
+ collection_include_list,
+ sizeof (metrics_config.collection_include_list) - 1);
+ else
+ strncpy (metrics_config.collection_include_list,
+ default_collection_ignore_list,
+ sizeof (metrics_config.collection_include_list) - 1);
+}
+
+static void
+load_metrics_stack_trace_config (void)
+{
+ metrics_config.stack_trace_size = get_int_from_environment ("G_METRICS_STACK_TRACE_SIZE", 15);
+
+ metrics_config.stack_trace_annotation_size = get_int_from_environment
("G_METRICS_STACK_TRACE_ANNOTATION_SIZE", 512);
+}
+
+static void
+load_metrics_config (void)
+{
+ load_metrics_config_command ();
+
+ if (!metrics_config.metrics_enabled)
+ return;
+
+ load_metrics_allocation_config ();
+ load_metrics_logging_config ();
+ load_metrics_inclusions_config ();
+ load_metrics_exclusions_config ();
+ load_metrics_collection_config ();
+ load_metrics_stack_trace_config ();
+}
+
+void
+g_metrics_init (void)
+{
+ static gboolean initialized = 0;
+
+ if (initialized)
+ return;
+
+ load_metrics_config ();
+
+ if (!g_metrics_enabled ())
+ {
+ initialized = TRUE;
+ return;
+ }
+
+ initialize_store_for_allocation_block_stores ();
+ allocate_metrics_block_store ();
+ allocate_thread_default_block_store ();
+
+ initialized = TRUE;
+
+ G_LOCK (timeouts);
+ if (timeout_handlers == NULL)
+ timeout_handlers = g_metrics_list_new ();
+ G_UNLOCK (timeouts);
+}
+
+static void
+g_metrics_allocation_blocks_iter_init_at_block (GMetricsAllocationBlocksIter *iter,
+ GMetricsAllocationFileMap *file_map,
+ GMetricsAllocationBlock *block)
+{
+
+ if (metrics_config.validate_allocation_blocks && block != NULL)
+ {
+ if (block < &file_map->blocks[0])
+ G_BREAKPOINT ();
+
+ if (block >= &file_map->blocks[file_map->number_of_blocks])
+ G_BREAKPOINT ();
+ }
+
+ iter->file_map = file_map;
+
+ if (block != NULL)
+ iter->starting_block = block;
+ else
+ iter->starting_block = &file_map->blocks[0];
+
+ iter->previous_block = NULL;
+ iter->items_examined = 0;
+}
+
+static void
+g_metrics_allocation_blocks_iter_init_after_block (GMetricsAllocationBlocksIter *iter,
+ GMetricsAllocationFileMap *file_map,
+ GMetricsAllocationBlock *block)
+{
+ gsize index = 0;
+ iter->file_map = file_map;
+
+ if (block != NULL)
+ {
+ GMetricsAllocation *allocation;
+ GMetricsAllocationHeader *header;
+
+ if (metrics_config.validate_allocation_blocks)
+ {
+ if (block < &file_map->blocks[0])
+ G_BREAKPOINT ();
+
+ if (block >= &file_map->blocks[file_map->number_of_blocks])
+ G_BREAKPOINT ();
+ }
+
+ allocation = (GMetricsAllocation *) block;
+ header = (GMetricsAllocationHeader *) &allocation->header_block.header;
+
+ index = g_metrics_allocation_file_map_get_index_of_block (file_map, block);
+ index += header->number_of_blocks;
+ index %= file_map->number_of_blocks;
+ }
+
+ iter->starting_block = &file_map->blocks[index];
+
+ iter->previous_block = NULL;
+ iter->items_examined = 0;
+}
+
+static gboolean
+g_metrics_allocation_blocks_iter_next (GMetricsAllocationBlocksIter *iter,
+ GMetricsAllocationBlock **next_block)
+{
+ GMetricsAllocationFileMap *file_map;
+ GMetricsAllocationBlock *block;
+
+ file_map = iter->file_map;
+
+ if (iter->previous_block == NULL)
+ {
+ block = iter->starting_block;
+ }
+ else
+ {
+ gsize index;
+ GMetricsAllocation *previous_allocation;
+ GMetricsAllocationHeader *previous_header;
+
+ previous_allocation = (GMetricsAllocation *) iter->previous_block;
+ previous_header = &previous_allocation->header_block.header;
+
+ index = g_metrics_allocation_file_map_get_index_of_block (file_map, iter->previous_block);
+ index += previous_header->number_of_blocks;
+ index %= file_map->number_of_blocks;
+
+ block = &file_map->blocks[index];
+ }
+ if (block == iter->starting_block && iter->items_examined > 1)
+ {
+ if (next_block)
+ *next_block = NULL;
+ return FALSE;
+ }
+
+ if (next_block)
+ *next_block = block;
+
+ iter->items_examined++;
+ iter->previous_block = block;
+
+ return TRUE;
+}
+
+static GMetricsAllocation *
+get_allocation (GMetricsAllocationBlockStore *block_store,
+ gsize number_of_blocks,
+ const char *name)
+{
+ GMetricsAllocationFileMap *file_map;
+ GMetricsAllocationBlocksIter iter;
+ GMetricsAllocationBlock *block;
+ GMetricsAllocation *allocation = NULL;
+
+ file_map = &block_store->file_map;
+ g_metrics_allocation_blocks_iter_init_at_block (&iter, file_map, file_map->large_free_block);
+ while (g_metrics_allocation_blocks_iter_next (&iter, &block))
+ {
+ GMetricsAllocationHeader *header;
+
+ if (metrics_config.validate_allocation_blocks)
+ {
+ if (block >= &file_map->blocks[file_map->number_of_blocks])
+ G_BREAKPOINT ();
+ }
+
+ allocation = (GMetricsAllocation *) block;
+ header = &allocation->header_block.header;
+
+ if (!header->is_freed)
+ continue;
+
+ consolidate_consecutive_blocks (file_map, block, number_of_blocks);
+
+ if (header->number_of_blocks < number_of_blocks)
+ {
+ if (file_map->large_free_block == NULL
+ || file_map->large_free_block->header.number_of_blocks < header->number_of_blocks)
+ file_map->large_free_block = block;
+
+ g_metrics_allocation_file_map_validate_block (file_map, block);
+
+ continue;
+ }
+
+ g_metrics_allocation_file_map_claim_allocation (file_map, allocation);
+ if (header->number_of_blocks > number_of_blocks)
+ g_metrics_allocation_file_map_shrink_allocation (file_map, allocation, number_of_blocks);
+
+ break;
+ }
+
+ g_metrics_allocation_file_map_validate_block (file_map, block);
+
+ allocation = (GMetricsAllocation *) block;
+
+ if (allocation != NULL)
+ {
+ GMetricsAllocationHeader *header;
+
+ header = &allocation->header_block.header;
+
+ if (header->number_of_blocks < number_of_blocks)
+ G_BREAKPOINT ();
+
+ if (name != NULL)
+ strncpy (header->name, name, sizeof (header->name) - 1); header->name[sizeof (header->name) - 1] =
'\0';
+ }
+ else
+ {
+ G_BREAKPOINT();
+ }
+ return allocation;
+}
+
+static gsize
+calculate_blocks_needed_for_size (gsize size)
+{
+ GMetricsAllocation allocation;
+ gsize aligned_size;
+ static const gsize payload_block_size = sizeof (GMetricsAllocationBlock);
+
+ aligned_size = sizeof (allocation.header_block) + round_to_multiple (size, payload_block_size);
+
+ return aligned_size / payload_block_size;
+}
+
+static GMetricsAllocationBlockStore *
+g_metrics_get_thread_default_allocation_block_store (void)
+{
+ GMetricsAllocationBlockStore *block_store;
+
+ if (!g_metrics_enabled ())
+ return NULL;
+
+ block_store = g_metrics_list_get_last_item (&block_store_stack);
+
+ if (block_store == NULL)
+ {
+ allocate_thread_default_block_store ();
+ block_store = g_metrics_list_get_last_item (&block_store_stack);
+
+ if (block_store != NULL)
+ block_store->stack_trace = g_metrics_stack_trace_new (4, 5, " -> ");
+
+ }
+
+ return block_store;
+}
+
+static GMetricsAllocationBlockStore *
+g_metrics_get_allocation_block_store_for_address (gpointer allocation)
+{
+ GMetricsAllocationBlockStoreIter iter;
+ GMetricsAllocationBlockStore *block_store = NULL;
+
+ g_metrics_allocation_block_store_iter_init (&iter);
+ while (g_metrics_allocation_block_store_iter_next (&iter, &block_store))
+ {
+ if (!g_metrics_allocation_block_store_has_allocation (block_store, allocation))
+ continue;
+
+ break;
+ }
+
+ return block_store;
+}
+
+void
+g_metrics_push_default_allocation_block_store (GMetricsAllocationBlockStore *block_store)
+{
+ g_metrics_list_add_item (&block_store_stack, block_store);
+}
+
+void
+g_metrics_pop_default_allocation_block_store (void)
+{
+ g_metrics_list_remove_last_item (&block_store_stack);
+}
+
+gpointer
+g_metrics_allocation_block_store_allocate (GMetricsAllocationBlockStore *block_store,
+ gsize size)
+{
+ return g_metrics_allocation_block_store_allocate_with_name (block_store, size, NULL);
+}
+
+gpointer
+g_metrics_allocation_block_store_allocate_with_name (GMetricsAllocationBlockStore *block_store,
+ gsize size,
+ const char *name)
+{
+ GMetricsAllocation *allocation;
+ gsize needed_blocks;
+
+ if (!g_metrics_allocation_file_map_is_open (&block_store->file_map))
+ return NULL;
+
+ needed_blocks = calculate_blocks_needed_for_size (size);
+
+ G_LOCK (allocations);
+ allocation = get_allocation (block_store, needed_blocks, name);
+ G_UNLOCK (allocations);
+
+ memset (allocation->payload_blocks, 0, size);
+ return (gpointer) allocation->payload_blocks;
+}
+
+gpointer
+g_metrics_allocation_block_store_reallocate (GMetricsAllocationBlockStore *block_store,
+ gpointer payload,
+ gsize size)
+{
+ GMetricsAllocationFileMap *file_map;
+ GMetricsAllocationBlock *payload_blocks;
+ GMetricsAllocationBlock *first_block;
+ GMetricsAllocation *allocation;
+ GMetricsAllocationHeader *header;
+ gsize old_size;
+ gsize needed_blocks;
+ gpointer new_payload;
+ gboolean could_grow;
+
+ g_metrics_init ();
+
+ if (!g_metrics_enabled ())
+ return __libc_realloc (payload, size);
+
+ if (size == 0)
+ {
+ g_metrics_allocation_block_store_deallocate (block_store, payload);
+
+ return NULL;
+ }
+
+ if (payload == NULL)
+ return g_metrics_allocation_block_store_allocate_with_name (block_store, size, __func__);
+
+ G_LOCK (allocations);
+ file_map = &block_store->file_map;
+ payload_blocks = (GMetricsAllocationBlock *) payload;
+ first_block = payload_blocks - 1;
+ allocation = (GMetricsAllocation *) first_block;
+ header = &allocation->header_block.header;
+ needed_blocks = calculate_blocks_needed_for_size (size);
+
+ if (needed_blocks == header->number_of_blocks)
+ {
+ G_UNLOCK (allocations);
+ return payload;
+ }
+
+ if (needed_blocks < header->number_of_blocks)
+ {
+ g_metrics_allocation_file_map_shrink_allocation (file_map, allocation, needed_blocks);
+ G_UNLOCK (allocations);
+
+ return payload;
+ }
+
+ could_grow = g_metrics_allocation_file_map_grow_allocation (file_map, allocation, needed_blocks);
+
+ G_UNLOCK (allocations);
+
+ if (could_grow)
+ return payload;
+
+ old_size = g_metrics_allocation_get_payload_size (allocation);
+
+ new_payload = g_metrics_allocation_block_store_allocate_with_name (block_store, size, header->name);
+
+ memcpy (new_payload, payload, old_size);
+
+ g_metrics_allocation_block_store_deallocate (block_store, payload);
+
+ return new_payload;
+}
+
+gpointer
+g_metrics_allocation_block_store_copy (GMetricsAllocationBlockStore *block_store,
+ gconstpointer allocation,
+ gsize size)
+{
+ return g_metrics_allocation_block_store_copy_with_name (block_store, allocation, size, __func__);
+}
+
+gpointer
+g_metrics_allocation_block_store_copy_with_name (GMetricsAllocationBlockStore *block_store,
+ gconstpointer allocation,
+ gsize size,
+ const char *name)
+{
+ gpointer copy;
+
+ copy = g_metrics_allocation_block_store_allocate_with_name (block_store, size, name);
+
+ memcpy (copy, allocation, size);
+
+ return copy;
+}
+
+__attribute__((visibility("default")))
+void *
+malloc (size_t size)
+{
+ if (!metrics_config.override_system_malloc)
+ return __libc_malloc (size);
+
+ return g_metrics_allocate (size);
+}
+
+__attribute__((visibility("default")))
+void *
+calloc (size_t nmemb, size_t size)
+{
+ if (!metrics_config.override_system_malloc)
+ return __libc_calloc (nmemb, size);
+
+ return g_metrics_allocate (size * nmemb);
+}
+
+__attribute__((visibility("default")))
+void *realloc (void *ptr, size_t size)
+{
+ if (!metrics_config.override_system_malloc)
+ return __libc_realloc (ptr, size);
+
+ return g_metrics_reallocate (ptr, size);
+}
+
+__attribute__((visibility("default")))
+void
+free (void *ptr)
+{
+ g_metrics_free (ptr);
+}
+
+void
+g_metrics_allocation_block_store_deallocate (GMetricsAllocationBlockStore *block_store,
+ gpointer payload)
+{
+ GMetricsAllocationBlock *payload_blocks;
+ GMetricsAllocationBlock *first_block;
+ GMetricsAllocation *allocation;
+
+ if (!payload)
+ return;
+
+ G_LOCK (allocations);
+ payload_blocks = (GMetricsAllocationBlock *) payload;
+ first_block = payload_blocks - 1;
+
+ allocation = (GMetricsAllocation *) first_block;
+
+ g_metrics_allocation_file_map_release_allocation (&block_store->file_map, allocation);
+ G_UNLOCK (allocations);
+
+ if (block_store->total_bytes_allocated == 0 && block_store->is_dedicated)
+ g_metrics_allocation_block_store_free (block_store);
+}
+
+gpointer
+g_metrics_allocate (gsize size)
+{
+ GMetricsAllocationBlockStore *block_store = NULL;
+ static gsize counter = 0;
+
+ g_metrics_init ();
+
+ if (!metrics_config.override_glib_malloc)
+ goto fallback;
+
+ block_store = g_metrics_get_thread_default_allocation_block_store ();
+ if (block_store == NULL)
+ goto fallback;
+
+ if (!g_metrics_allocation_file_map_is_open (&block_store->file_map))
+ goto fallback;
+
+ if (size >= metrics_config.dedicated_allocation_block_store_threshold)
+ {
+ GMetricsAllocationBlockStore *dedicated_block_store = NULL;
+ char *name;
+
+ asprintf (&name, "allocation-%ld-%ld", size, counter);
+ counter++;
+
+ dedicated_block_store = g_metrics_allocation_block_store_new (name,
metrics_config.allocation_block_store_size);
+ g_metrics_free (name);
+
+ if (dedicated_block_store != NULL)
+ {
+ dedicated_block_store->is_dedicated = TRUE;
+ dedicated_block_store->stack_trace = g_metrics_stack_trace_new (4, 5, " -> ");
+
+ return g_metrics_allocation_block_store_allocate (dedicated_block_store, size);
+ }
+ }
+
+ return g_metrics_allocation_block_store_allocate (block_store, size);
+
+fallback:
+ return __libc_calloc (1, size);
+}
+
+gpointer
+g_metrics_reallocate (gpointer allocation,
+ gsize size)
+{
+ GMetricsAllocationBlockStore *block_store = NULL;
+
+ g_metrics_init ();
+
+ if (!metrics_config.override_glib_malloc)
+ goto fallback;
+
+ block_store = g_metrics_get_allocation_block_store_for_address (allocation);
+ if (block_store == NULL)
+ goto fallback;
+
+ if (!g_metrics_allocation_file_map_is_open (&block_store->file_map))
+ goto fallback;
+
+ return g_metrics_allocation_block_store_reallocate (block_store, allocation, size);
+
+fallback:
+ return __libc_realloc (allocation, size);
+}
+
+gpointer
+g_metrics_copy (gconstpointer allocation,
+ gsize size)
+{
+ GMetricsAllocationBlockStore *block_store = NULL;
+ gpointer copy;
+
+ g_metrics_init ();
+
+ if (!metrics_config.override_glib_malloc)
+ goto fallback;
+
+ block_store = g_metrics_get_thread_default_allocation_block_store ();
+ if (block_store == NULL)
+ goto fallback;
+
+ if (!g_metrics_allocation_file_map_is_open (&block_store->file_map))
+ goto fallback;
+
+ return g_metrics_allocation_block_store_copy_with_name (block_store, allocation, size, __func__);
+
+fallback:
+ copy = __libc_malloc (size);
+ memcpy (copy, allocation, size);
+ return copy;
+}
+
+void
+g_metrics_free (gpointer allocation)
+{
+ GMetricsAllocationBlockStore *block_store = NULL;
+
+ if (!allocation)
+ return;
+
+ block_store = g_metrics_get_allocation_block_store_for_address (allocation);
+
+ if (block_store != NULL)
+ {
+ g_metrics_allocation_block_store_deallocate (block_store, allocation);
+ return;
+ }
+
+ __libc_free (allocation);
+}
+
+static void
+g_metrics_file_write (GMetricsFile *metrics_file,
+ const char *data,
+ gsize size)
+{
+ gchar *buf = (gchar *) data;
+ gsize to_write = size;
+
+ while (to_write > 0)
+ {
+ gssize count = gzwrite (metrics_file->gzipped_file, buf, to_write);
+ if (count <= 0)
+ {
+ if (errno != EINTR)
+ return;
+ }
+ else
+ {
+ to_write -= count;
+ buf += count;
+ }
+ }
+}
+
+static void
+on_sigusr1 (int signal_number)
+{
+ needs_flush = TRUE;
+}
+
+GMetricsFile *
+g_metrics_file_new (const char *name,
+ const char *first_column_name,
+ const char *first_column_format,
+ ...)
+{
+ GMetricsFile *metrics_file;
+ va_list args;
+ const char *column_name, *column_format;
+ UT_string *format_string = NULL;
+ UT_string *header_string = NULL;
+ char *filename = NULL;
+
+ g_metrics_init ();
+
+ utstring_new (format_string);
+ utstring_new (header_string);
+ utstring_printf (header_string, "generation,timestamp,%s", first_column_name);
+
+ va_start (args, first_column_format);
+ do
+ {
+ column_name = va_arg (args, const char *);
+
+ if (column_name == NULL)
+ break;
+
+ column_format = va_arg (args, const char *);
+
+ utstring_printf (header_string, ",%s", column_name);
+ utstring_printf (format_string, ",%s", column_format);
+ }
+ while (column_name != NULL);
+ va_end (args);
+
+ utstring_printf (header_string, "\n");
+
+ metrics_file = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store,
+ sizeof (GMetricsFile),
+ "GMetricsFile");
+ memset (metrics_file, 0, sizeof (GMetricsFile));
+
+ asprintf (&metrics_file->static_format_string, "%%lu,%%lf,%s", first_column_format);
+ metrics_file->variadic_format_string = strdup (utstring_body (format_string));
+
+ asprintf (&filename,"%s/%s.csv.gz", metrics_config.log_dir, name);
+ g_mkdir_with_parents (metrics_config.log_dir, 0755);
+
+ metrics_file->gzipped_file = gzopen (filename, "wbe");
+ g_metrics_file_write (metrics_file, utstring_body (header_string), utstring_len (header_string));
+ utstring_free (format_string);
+ utstring_free (header_string);
+ free (filename);
+
+ signal (SIGUSR1, on_sigusr1);
+
+ return metrics_file;
+}
+
+void
+g_metrics_file_start_record (GMetricsFile *metrics_file)
+{
+ metrics_file->now = ((long double) g_get_real_time ()) / (1.0 * G_USEC_PER_SEC);
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+void
+g_metrics_file_add_row_without_cast (GMetricsFile *metrics_file,
+ gconstpointer first_column_value,
+ ...)
+
+{
+ va_list args;
+ gsize row_length = 0, buffer_left = 0, buffer_written = 0;
+ char *row;
+ gulong generation;
+
+ generation = g_metrics_get_generation ();
+
+ row_length += snprintf (NULL, 0, metrics_file->static_format_string, generation, metrics_file->now,
first_column_value);
+
+ va_start (args, first_column_value);
+ row_length += vsnprintf (NULL, 0, metrics_file->variadic_format_string, args);
+ va_end (args);
+
+ row_length++;
+
+ buffer_left = row_length + 1;
+ row = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, buffer_left,
__func__);
+
+ buffer_written += snprintf (row, buffer_left, metrics_file->static_format_string, generation,
metrics_file->now, first_column_value);
+ buffer_left -= buffer_written;
+
+ va_start (args, first_column_value);
+ buffer_written += vsnprintf (row + buffer_written, buffer_left, metrics_file->variadic_format_string,
args);
+ buffer_left -= buffer_written;
+ va_end (args);
+ *(row + buffer_written) = '\n';
+
+ g_metrics_file_write (metrics_file, row, row_length);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, row);
+}
+#pragma GCC diagnostic pop
+
+void
+g_metrics_file_end_record (GMetricsFile *metrics_file)
+{
+ gulong generation;
+
+ generation = g_metrics_get_generation ();
+
+ if (needs_flush)
+ {
+ gzflush (metrics_file->gzipped_file, Z_FULL_FLUSH);
+ }
+ else if ((generation % 10) == 0)
+ {
+ gzflush (metrics_file->gzipped_file, Z_PARTIAL_FLUSH);
+ }
+}
+
+void
+g_metrics_file_free (GMetricsFile *metrics_file)
+{
+ gzclose (metrics_file->gzipped_file);
+ free (metrics_file->static_format_string);
+ free (metrics_file->variadic_format_string);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, metrics_file);
+}
+
+struct _GMetricsTableEntry
+{
+ UT_hash_handle hh;
+ char *name;
+ gpointer record;
+};
+
+struct _GMetricsTable
+{
+ gsize record_size;
+ GMetricsTableEntry *entries;
+ gboolean is_sorted;
+};
+
+GMetricsTable *
+g_metrics_table_new (gsize record_size)
+{
+ GMetricsTable *table;
+
+ g_metrics_init ();
+
+ table = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof
(GMetricsTable), "GMetricsTable");
+ table->record_size = record_size;
+
+ return table;
+}
+
+void
+g_metrics_table_set_record (GMetricsTable *metrics_table,
+ const char *name,
+ gpointer record)
+{
+ GMetricsTableEntry *entry = NULL;
+
+ HASH_FIND_STR (metrics_table->entries, name, entry);
+
+ if (entry == NULL)
+ {
+ entry = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof
(GMetricsTableEntry), "GMetricsTableEntry");
+ memset (entry, 0, sizeof (GMetricsTableEntry));
+
+ entry->name = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store, name,
strlen (name) + 1, "GMetricsTableEntry::name");
+ entry->record = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store,
record, metrics_table->record_size, "GMetricsTableEntry::record");
+
+ HASH_ADD_KEYPTR (hh, metrics_table->entries, entry->name, strlen (entry->name), entry);
+ }
+ else
+ {
+ gpointer old_record;
+ old_record = entry->record;
+ entry->record = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store,
record, metrics_table->record_size, "GMetricsTableEntry::record");
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, old_record);
+ }
+ metrics_table->is_sorted = FALSE;
+}
+
+gpointer
+g_metrics_table_get_record (GMetricsTable *metrics_table,
+ const char *name)
+{
+ GMetricsTableEntry *entry = NULL;
+
+ HASH_FIND_STR (metrics_table->entries, name, entry);
+
+ if (entry == NULL)
+ return NULL;
+
+ return entry->record;
+}
+
+void
+g_metrics_table_remove_record (GMetricsTable *metrics_table,
+ const char *name)
+{
+ GMetricsTableEntry *entry = NULL;
+
+ HASH_FIND_STR (metrics_table->entries, name, entry);
+
+ if (entry == NULL)
+ return;
+
+ HASH_DEL (metrics_table->entries, entry);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry->name);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry->record);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry);
+
+ metrics_table->is_sorted = FALSE;
+}
+
+void
+g_metrics_table_clear (GMetricsTable *metrics_table)
+{
+ GMetricsTableEntry *entry = NULL, *next_entry = NULL;
+
+ HASH_ITER (hh, metrics_table->entries, entry, next_entry)
+ {
+ HASH_DEL (metrics_table->entries, entry);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry->name);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry->record);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry);
+ }
+ metrics_table->is_sorted = FALSE;
+}
+
+void
+g_metrics_table_free (GMetricsTable *metrics_table)
+{
+ g_metrics_table_clear (metrics_table);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, metrics_table);
+}
+
+void
+g_metrics_table_iter_init (GMetricsTableIter *iter,
+ GMetricsTable *table)
+{
+ iter->entry = table->entries;
+}
+
+static int
+comparison_wrapper (GMetricsTableEntry *entry_1,
+ GMetricsTableEntry *entry_2,
+ GCompareFunc comparison_function)
+{
+ if (comparison_function == NULL)
+ return g_strcmp0 (entry_1->name, entry_2->name);
+
+ return comparison_function (entry_1->record, entry_2->record);
+}
+
+void
+g_metrics_table_sorted_iter_init (GMetricsTableIter *iter,
+ GMetricsTable *table,
+ GCompareFunc comparison_function)
+{
+ if (!table->is_sorted)
+ {
+ HASH_SRT_DATA (hh, table->entries, comparison_wrapper, comparison_function);
+ table->is_sorted = TRUE;
+ }
+
+ iter->entry = table->entries;
+}
+
+gboolean
+g_metrics_table_iter_next_without_cast (GMetricsTableIter *iter,
+ const char **name,
+ gpointer *record)
+{
+ if (iter->entry == NULL)
+ {
+ if (name)
+ *name = NULL;
+ return FALSE;
+ }
+
+ if (name)
+ *name = (const char *) iter->entry->name;
+
+ if (iter->entry->record == NULL)
+ G_BREAKPOINT ();
+
+ if (record)
+ *record = iter->entry->record;
+
+ iter->entry = iter->entry->hh.next;
+ return TRUE;
+}
+
+struct _GMetricsInstanceCounterEntry
+{
+ UT_hash_handle hh;
+ char *name;
+ gpointer record;
+};
+
+struct _GMetricsInstanceCounter
+{
+ GMetricsTable *tables[2];
+ GMetricsTable *interesting_instances;
+ ssize_t last_table_index;
+ ssize_t current_table_index;
+ size_t generation;
+};
+
+static gint
+g_metrics_instance_counter_metrics_sort (GMetricsInstanceCounterMetrics *a, GMetricsInstanceCounterMetrics
*b)
+{
+ if (b->average_instance_change == a->average_instance_change)
+ {
+ if (b->total_memory_usage == a->total_memory_usage)
+ {
+ if (b->instance_count == a->instance_count)
+ return 0;
+
+ if (b->instance_count > a->instance_count)
+ return 1;
+
+ return -1;
+ }
+
+ if (b->total_memory_usage > a->total_memory_usage)
+ return 1;
+
+ return -1;
+ }
+
+ if (b->average_instance_change > a->average_instance_change)
+ return 1;
+
+ return -1;
+}
+
+GMetricsInstanceCounter *
+g_metrics_instance_counter_new (void)
+{
+ GMetricsInstanceCounter *counter;
+
+ g_metrics_init ();
+
+ counter = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store,
+ sizeof (GMetricsInstanceCounter),
+ "GMetricsInstanceCounter");
+ memset (counter, 0, sizeof (GMetricsInstanceCounter));
+ counter->last_table_index = -1;
+ counter->interesting_instances = g_metrics_table_new (sizeof (gboolean));
+
+ return counter;
+}
+
+void
+g_metrics_instance_counter_start_record (GMetricsInstanceCounter *counter)
+{
+ GMetricsTable *table;
+
+ counter->current_table_index = (counter->last_table_index + 1) % G_N_ELEMENTS (counter->tables);
+
+ if (counter->tables[counter->current_table_index] == NULL)
+ counter->tables[counter->current_table_index] = g_metrics_table_new (sizeof
(GMetricsInstanceCounterMetrics));
+
+ table = counter->tables[counter->current_table_index];
+ g_metrics_table_clear (table);
+}
+
+void
+g_metrics_instance_counter_end_record (GMetricsInstanceCounter *counter)
+{
+ GMetricsTable *old_table, *table;
+
+ old_table = counter->tables[counter->last_table_index];
+ table = counter->tables[counter->current_table_index];
+
+ if (old_table != NULL)
+ {
+ GMetricsInstanceCounterMetrics *old_metrics = NULL;
+ const char *name = NULL;
+ GMetricsTableIter metrics_iter;
+
+ g_metrics_table_iter_init (&metrics_iter, old_table);
+ while (g_metrics_table_iter_next (&metrics_iter, &name, &old_metrics))
+ {
+ GMetricsInstanceCounterMetrics *metrics;
+
+ metrics = g_metrics_table_get_record (table, name);
+ if (metrics == NULL)
+ {
+ GMetricsInstanceCounterMetrics new_metrics = { { 0 }, 0 };
+ new_metrics.instance_change = -old_metrics->instance_count;
+ g_metrics_table_set_record (table, name, &new_metrics);
+ }
+ }
+ g_metrics_table_clear (old_table);
+ }
+
+ counter->last_table_index = counter->current_table_index;
+ counter->current_table_index = -1;
+}
+
+gboolean
+g_metrics_instance_counter_instance_is_interesting (GMetricsInstanceCounter *counter,
+ const char *name)
+{
+ GMetricsInstanceCounterIter iter;
+ GMetricsInstanceCounterMetrics *metrics;
+ const char *instance_name;
+ size_t i = 0;
+
+ if (strstr (name, metrics_config.collection_include_list) != NULL)
+ return TRUE;
+
+ g_metrics_instance_counter_iter_init (&iter, counter);
+ while (g_metrics_instance_counter_iter_next (&iter, &instance_name, &metrics) &&
+ i < metrics_config.number_of_interesting_instances)
+ {
+ if (g_strcmp0 (name, instance_name) == 0 &&
+ strstr (name, metrics_config.collection_ignore_list) == NULL &&
+ metrics->average_instance_change > 0)
+ return TRUE;
+ i++;
+ }
+
+ return FALSE;
+}
+
+void
+g_metrics_instance_counter_add_instances (GMetricsInstanceCounter *counter,
+ const char *name,
+ const char *comment,
+ size_t number_of_instances,
+ size_t total_usage)
+{
+ GMetricsTable *old_table, *table;
+ GMetricsInstanceCounterMetrics *metrics;
+ size_t old_instance_count = 0, old_instance_watermark = 0;
+ ssize_t old_average_instance_change = 0;
+ size_t old_number_of_samples = 0;
+ gulong generation;
+
+ old_table = counter->tables[counter->last_table_index];
+ table = counter->tables[counter->current_table_index];
+
+ if (old_table != NULL)
+ {
+ GMetricsInstanceCounterMetrics *old_metrics = NULL;
+
+ old_metrics = g_metrics_table_get_record (old_table, name);
+
+ if (old_metrics != NULL)
+ {
+ old_average_instance_change = old_metrics->average_instance_change;
+ old_instance_count = old_metrics->instance_count;
+ old_instance_watermark = old_metrics->instance_watermark;
+ old_number_of_samples = old_metrics->number_of_samples;
+ }
+ }
+
+ metrics = g_metrics_table_get_record (table, name);
+ if (metrics == NULL)
+ {
+ GMetricsInstanceCounterMetrics new_metrics = { { 0 }, 0 };
+ g_metrics_table_set_record (table, name, &new_metrics);
+
+ metrics = g_metrics_table_get_record (table, name);
+ if (comment != NULL)
+ strncpy (metrics->comment, comment, sizeof (metrics->comment) - 1);
+ }
+
+ metrics->instance_count += number_of_instances;
+ metrics->instance_change = metrics->instance_count - old_instance_count;
+
+ generation = g_metrics_get_generation ();
+ if (generation > metrics_config.generations_to_settle &&
+ metrics->instance_change != 0)
+ {
+ if (old_number_of_samples != 0)
+ {
+ metrics->average_instance_change = (old_average_instance_change * (ssize_t) old_number_of_samples) +
(((ssize_t) metrics->instance_change) - old_average_instance_change);
+ metrics->average_instance_change /= (ssize_t) old_number_of_samples;
+ metrics->number_of_samples = old_number_of_samples + 1;
+ }
+ else
+ {
+ metrics->average_instance_change = metrics->instance_change;
+ metrics->number_of_samples = 1;
+ }
+ }
+ else
+ {
+ metrics->average_instance_change = old_average_instance_change;
+ metrics->number_of_samples = old_number_of_samples;
+ }
+
+ metrics->instance_watermark = MAX (metrics->instance_watermark, metrics->instance_count);
+ metrics->instance_watermark = MAX (metrics->instance_watermark, old_instance_watermark);
+ metrics->total_memory_usage += total_usage;
+
+ g_metrics_table_set_record (table, name, metrics);
+}
+
+void
+g_metrics_instance_counter_add_instance (GMetricsInstanceCounter *counter,
+ const char *name,
+ size_t memory_usage)
+{
+ g_metrics_instance_counter_add_instances (counter,
+ name,
+ NULL,
+ 1,
+ memory_usage);
+}
+
+void
+g_metrics_instance_counter_free (GMetricsInstanceCounter *counter)
+{
+ size_t i;
+
+ for (i = 0; i < G_N_ELEMENTS (counter->tables); i++)
+ g_metrics_table_free (counter->tables[i]);
+
+ g_metrics_table_free (counter->interesting_instances);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, counter);
+}
+
+void
+g_metrics_instance_counter_iter_init (GMetricsInstanceCounterIter *iter,
+ GMetricsInstanceCounter *counter)
+{
+ GMetricsTable *old_table;
+
+ iter->table_index = counter->last_table_index;
+
+ old_table = counter->tables[iter->table_index];
+
+ if (old_table == NULL)
+ {
+ memset (&iter->table_iter, 0, sizeof (iter->table_iter));
+ return;
+ }
+
+ g_metrics_table_sorted_iter_init (&iter->table_iter, old_table, (GCompareFunc)
g_metrics_instance_counter_metrics_sort);
+}
+
+gboolean
+g_metrics_instance_counter_iter_next (GMetricsInstanceCounterIter *iter,
+ const char **name,
+ GMetricsInstanceCounterMetrics **metrics)
+{
+ while (g_metrics_table_iter_next (&iter->table_iter, name, metrics))
+ {
+ if ((*metrics)->instance_change == 0)
+ continue;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+g_metrics_requested (const char *name)
+{
+ if (!g_metrics_enabled ())
+ return FALSE;
+
+ if (strstr (metrics_config.included_metrics, name) != NULL)
+ return TRUE;
+
+ if (strstr (metrics_config.skipped_metrics, name) != NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+void
+g_metrics_start_timeout (GMetricsTimeoutFunc timeout_handler)
+{
+ G_LOCK (timeouts);
+
+ if (timeout_fd < 0)
+ {
+ struct itimerspec timer_spec = { { 0 } };
+
+ timer_spec.it_interval.tv_sec = metrics_config.collection_interval;
+ timer_spec.it_value.tv_sec = metrics_config.collection_interval;
+
+ timeout_fd = timerfd_create (CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
+ if (timeout_fd >= 0)
+ {
+ int result;
+ result = timerfd_settime (timeout_fd, 0, &timer_spec, NULL);
+
+ if (result < 0)
+ {
+ close (timeout_fd);
+ timeout_fd = -1;
+ }
+ }
+ }
+
+ g_metrics_list_add_item (timeout_handlers, timeout_handler);
+
+ G_UNLOCK (timeouts);
+}
+
+static void
+init_allocation_block_stores_metrics (void)
+{
+ static gboolean initialized;
+ static gboolean needs_allocation_block_store_metrics;
+
+ if (initialized)
+ return;
+
+ initialized = TRUE;
+ needs_allocation_block_store_metrics = g_metrics_requested ("allocation-block-stores");
+
+ if (!needs_allocation_block_store_metrics)
+ return;
+
+ allocation_block_store_metrics_file = g_metrics_file_new ("allocation-block-stores",
+ "name", "%s",
+ "thread name", "%s",
+ "number of allocations", "%ld",
+ "total size", "%ld",
+ "stack trace", "%s",
+ NULL);
+ g_metrics_start_timeout (on_allocation_block_stores_metrics_timeout);
+}
+
+void
+g_metrics_run_timeout_handlers (void)
+{
+ GMetricsListIter iter;
+ GMetricsTimeoutFunc handler;
+
+ guint64 number_of_expirations;
+
+ read (timeout_fd, &number_of_expirations, sizeof (number_of_expirations));
+
+ init_allocation_block_stores_metrics ();
+
+ G_LOCK (timeouts);
+ g_metrics_list_iter_init (&iter, timeout_handlers);
+ while (g_metrics_list_iter_next (&iter, &handler))
+ {
+ if (handler != NULL)
+ handler ();
+ }
+ g_metrics_generation++;
+ G_UNLOCK (timeouts);
+
+ needs_flush = FALSE;
+}
+
+gulong
+g_metrics_get_generation (void)
+{
+ return g_metrics_generation;
+}
+
+struct _GMetricsListNode
+{
+ gpointer item;
+ struct _GMetricsListNode *prev;
+ struct _GMetricsListNode *next;
+};
+
+struct _GMetricsList
+{
+ struct _GMetricsListNode *first_node;
+ gsize number_of_nodes;
+};
+
+GMetricsList *
+g_metrics_list_new (void)
+{
+ GMetricsList *list;
+
+ g_metrics_init ();
+
+ list = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof
(GMetricsList), "GMetricsList");
+ memset (list, 0, sizeof (GMetricsList));
+
+ return list;
+}
+
+void
+g_metrics_list_add_item (GMetricsList *list,
+ gpointer item)
+{
+ GMetricsListNode *node;
+
+ node = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof
(GMetricsListNode), "GMetricsListNode");
+ memset (node, 0, sizeof (GMetricsListNode));
+
+ node->item = item;
+
+ DL_APPEND (list->first_node, node);
+ list->number_of_nodes++;
+}
+
+void
+g_metrics_list_remove_item (GMetricsList *list,
+ gpointer item_to_remove)
+{
+ GMetricsListNode *node = NULL;
+
+ DL_SEARCH_SCALAR (list->first_node, node, item, item_to_remove);
+
+ if (node != NULL)
+ {
+ DL_DELETE (list->first_node, node);
+ list->number_of_nodes--;
+
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, node);
+ }
+}
+
+gpointer
+g_metrics_list_get_last_item (GMetricsList *list)
+{
+ GMetricsListNode *last_node = NULL;
+
+ if (list->number_of_nodes == 0)
+ return NULL;
+
+ last_node = list->first_node->prev;
+
+ return last_node->item;
+}
+
+void
+g_metrics_list_remove_last_item (GMetricsList *list)
+{
+ GMetricsListNode *last_node = NULL;
+
+ if (list->number_of_nodes == 0)
+ return;
+
+ last_node = list->first_node->prev;
+
+ DL_DELETE (list->first_node, last_node);
+ list->number_of_nodes--;
+}
+
+gsize
+g_metrics_list_get_length (GMetricsList *list)
+{
+ return list->number_of_nodes;
+}
+
+void
+g_metrics_list_free (GMetricsList *list)
+{
+ GMetricsListNode *node, *next_node;
+
+ DL_FOREACH_SAFE (list->first_node, node, next_node)
+ {
+ DL_DELETE (list->first_node, node);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, node);
+ }
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, list);
+}
+
+void
+g_metrics_list_iter_init (GMetricsListIter *iter,
+ GMetricsList *list)
+{
+ iter->node = list->first_node;
+
+ if (iter->node != NULL)
+ iter->next_node = iter->node->next;
+}
+
+gboolean
+g_metrics_list_iter_next_without_cast (GMetricsListIter *iter,
+ gpointer *item)
+{
+ if (iter->node == NULL)
+ {
+ *item = NULL;
+ return FALSE;
+ }
+
+ *item = iter->node->item;
+
+ iter->node = iter->next_node;
+
+ if (iter->node != NULL)
+ iter->next_node = iter->node->next;
+ return TRUE;
+}
+
+struct _GMetricsStackTrace
+{
+ gpointer *frames;
+ int start_frame;
+ int number_of_frames;
+ char *delimiter;
+ char *output;
+ char *hash_key;
+ char *annotation;
+};
+
+GMetricsStackTrace *
+g_metrics_stack_trace_new (int start_frame,
+ int number_of_frames,
+ const char *delimiter)
+{
+ GMetricsStackTrace *stack_trace;
+ int total_frames_needed;
+
+ stack_trace = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof
(GMetricsStackTrace), "GMetricsStackTrace");
+ memset (stack_trace, 0, sizeof (GMetricsStackTrace));
+
+ total_frames_needed = start_frame + number_of_frames;
+ stack_trace->frames = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store,
sizeof (gpointer) * (total_frames_needed), "GMetricsStackTrace::frames");
+ stack_trace->number_of_frames = backtrace (stack_trace->frames, total_frames_needed);
+ stack_trace->start_frame = start_frame;
+ stack_trace->delimiter = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store,
delimiter, strlen (delimiter) + 1, "GMetricsStackTrace::delimiter");
+
+ return stack_trace;
+}
+
+const char *
+g_metrics_stack_trace_get_hash_key (GMetricsStackTrace *stack_trace)
+{
+ if (stack_trace->hash_key == NULL)
+ {
+ size_t i;
+ int total_frames_needed;
+ char *end;
+ size_t key_size;
+ size_t annotation_length = 0;
+
+ total_frames_needed = stack_trace->number_of_frames - stack_trace->start_frame;
+ if (stack_trace->annotation != NULL)
+ annotation_length = strlen (stack_trace->annotation);
+ key_size = strlen ("c00ldead0ldc0des") * total_frames_needed + annotation_length + 1;
+ stack_trace->hash_key = g_metrics_allocation_block_store_allocate_with_name
(metrics_allocation_block_store, key_size, "GMetricsStackTrace::hash_key");
+
+ end = stack_trace->hash_key;
+ for (i = stack_trace->start_frame; i < stack_trace->number_of_frames; i++)
+ {
+ size_t key_size_left;
+
+ key_size_left = key_size - (end - stack_trace->hash_key);
+ end = int_to_string ((uintptr_t)stack_trace->frames[i], end, key_size_left);
+ }
+
+ if (stack_trace->annotation != NULL)
+ memcpy (end, stack_trace->annotation, annotation_length + 1);
+ }
+
+ return stack_trace->hash_key;
+}
+
+const char *
+g_metrics_stack_trace_get_output (GMetricsStackTrace *stack_trace)
+{
+ if (stack_trace->output == NULL)
+ {
+ UT_string *output_string = NULL;
+ char **symbols = NULL;
+ int i;
+
+ symbols = backtrace_symbols (stack_trace->frames, stack_trace->number_of_frames);
+
+ if (symbols == NULL)
+ return NULL;
+
+ utstring_new (output_string);
+ if (stack_trace->annotation != NULL)
+ utstring_printf (output_string, "%s: ", stack_trace->annotation);
+ for (i = stack_trace->start_frame; i < stack_trace->number_of_frames; i++)
+ utstring_printf (output_string, "%s%s", symbols[i], stack_trace->delimiter);
+ stack_trace->output = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store,
utstring_body (output_string), utstring_len (output_string) + 1, "GMetricsStackTrace::output");
+ utstring_free (output_string);
+ g_metrics_free (symbols);
+ }
+
+ return stack_trace->output;
+}
+
+void
+g_metrics_stack_trace_add_annotation (GMetricsStackTrace *stack_trace,
+ const char *annotation)
+{
+ stack_trace->annotation = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store,
annotation, strlen (annotation) + 1, "GMetricsStackTrace::annotation");
+}
+
+void
+g_metrics_stack_trace_free (GMetricsStackTrace *stack_trace)
+{
+ if (stack_trace == NULL)
+ return;
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->annotation);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->hash_key);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->frames);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->output);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->delimiter);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace);
+}
+
+char *
+g_metrics_stack_trace (void)
+{
+ GMetricsStackTrace *stack_trace;
+ const char *output;
+ char *copy = NULL;
+
+ stack_trace = g_metrics_stack_trace_new (2, metrics_config.stack_trace_size, " -> ");
+ output = g_metrics_stack_trace_get_output (stack_trace);
+
+ if (output != NULL)
+ copy = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store, output, strlen
(output) + 1, __func__);
+
+ return copy;
+}
+
+struct _GMetricsStackTraceSampler
+{
+ GMetricsTable *traces_table;
+ GMetricsTable *instances_table;
+};
+
+typedef struct _GMetricsStackTraceSamplerInstanceEntry GMetricsStackTraceSamplerInstanceEntry;
+struct _GMetricsStackTraceSamplerInstanceEntry
+{
+ gpointer instance;
+ char *trace_hash_key;
+};
+
+static int
+g_metrics_stack_trace_sample_sort (GMetricsStackTraceSample *a,
+ GMetricsStackTraceSample *b)
+{
+ if (b->number_of_hits == a->number_of_hits)
+ return g_strcmp0 (a->name, b->name);
+
+ if (b->number_of_hits > a->number_of_hits)
+ return 1;
+
+ return -1;
+}
+
+void
+g_metrics_stack_trace_sampler_iter_init (GMetricsStackTraceSamplerIter *iter,
+ GMetricsStackTraceSampler *sampler)
+{
+ g_metrics_table_sorted_iter_init (&iter->table_iter, sampler->traces_table, (GCompareFunc)
g_metrics_stack_trace_sample_sort);
+}
+
+gboolean
+g_metrics_stack_trace_sampler_iter_next (GMetricsStackTraceSamplerIter *iter,
+ GMetricsStackTraceSample **sample)
+{
+ const char *key;
+
+ if (!g_metrics_table_iter_next (&iter->table_iter, &key, sample))
+ return FALSE;
+
+ return TRUE;
+}
+
+void
+g_metrics_set_stack_trace_annotation_handler (GMetricsStackTraceAnnotationHandler handler,
+ gpointer user_data)
+{
+ stack_trace_annotation_handler = handler;
+ stack_trace_annotation_handler_user_data = user_data;
+}
+
+GMetricsStackTraceSampler *
+g_metrics_stack_trace_sampler_new (void)
+{
+ GMetricsStackTraceSampler *sampler;
+
+ sampler = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof
(GMetricsStackTraceSampler), "GMetricsStackTraceSampler");
+ memset (sampler, 0, sizeof (GMetricsStackTraceSampler));
+
+ sampler->traces_table = g_metrics_table_new (sizeof (GMetricsStackTraceSample));
+ sampler->instances_table = g_metrics_table_new (sizeof (GMetricsStackTraceSamplerInstanceEntry));
+
+ return sampler;
+}
+
+void
+g_metrics_stack_trace_sampler_take_sample (GMetricsStackTraceSampler *sampler,
+ const char *name,
+ gpointer instance)
+{
+ GMetricsStackTrace *stack_trace;
+ GMetricsStackTraceSample *sample;
+ GMetricsStackTraceSamplerInstanceEntry instance_entry = { 0 };
+ const char *trace_key;
+ char instance_name[64] = "";
+
+ if ((g_random_int () % metrics_config.stack_trace_sample_interval) != 0)
+ return;
+
+ stack_trace = g_metrics_stack_trace_new (4, 5, " -> ");
+
+ if (stack_trace_annotation_handler != NULL)
+ {
+ char annotation[metrics_config.stack_trace_annotation_size];
+ gboolean annotated = FALSE;
+
+ annotation[0] = '\0';
+ annotated = stack_trace_annotation_handler(annotation, metrics_config.stack_trace_annotation_size - 1,
stack_trace_annotation_handler_user_data);
+ if (annotated)
+ {
+ annotation[metrics_config.stack_trace_annotation_size - 1] = '\0';
+ g_metrics_stack_trace_add_annotation (stack_trace, annotation);
+ }
+ }
+
+ trace_key = g_metrics_stack_trace_get_hash_key (stack_trace);
+
+ sample = g_metrics_table_get_record (sampler->traces_table, trace_key);
+
+ if (sample == NULL)
+ {
+ GMetricsStackTraceSample empty_sample = { { 0 }, 0 };
+
+ g_metrics_table_set_record (sampler->traces_table, trace_key, &empty_sample);
+
+ sample = g_metrics_table_get_record (sampler->traces_table, trace_key);
+ sample->stack_trace = stack_trace;
+ strncpy (sample->name, name, sizeof (sample->name) - 1);
+ stack_trace = NULL;
+ }
+
+ sample->number_of_hits++;
+ g_metrics_stack_trace_free (stack_trace);
+
+ int_to_string ((uintptr_t) instance, instance_name, sizeof (instance_name));
+ instance_entry.instance = instance;
+ instance_entry.trace_hash_key = g_metrics_allocation_block_store_copy_with_name
(metrics_allocation_block_store, trace_key, strlen (trace_key) + 1,
"GMetricsStackTraceSamplerInstanceEntry::trace_hash_key");
+ g_metrics_table_set_record (sampler->instances_table, instance_name, &instance_entry);
+}
+
+void
+g_metrics_stack_trace_sampler_remove_sample (GMetricsStackTraceSampler *sampler,
+ gpointer instance)
+{
+ GMetricsStackTraceSample *sample;
+ GMetricsStackTraceSamplerInstanceEntry *instance_entry;
+ char *trace_key;
+ char instance_name[64] = "";
+
+ int_to_string ((uintptr_t) instance, instance_name, sizeof (instance_name));
+ instance_entry = g_metrics_table_get_record (sampler->instances_table, instance_name);
+
+ if (instance_entry == NULL)
+ return;
+
+ trace_key = instance_entry->trace_hash_key;
+ g_metrics_table_remove_record (sampler->instances_table, instance_name);
+
+ sample = g_metrics_table_get_record (sampler->traces_table, trace_key);
+
+ if (sample == NULL)
+ {
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, trace_key);
+ return;
+ }
+
+ sample->number_of_hits--;
+
+ if (sample->number_of_hits == 0)
+ {
+ g_metrics_stack_trace_free (sample->stack_trace);
+ g_metrics_table_remove_record (sampler->traces_table, trace_key);
+ }
+
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, trace_key);
+}
+
+void
+g_metrics_stack_trace_sampler_clear (GMetricsStackTraceSampler *sampler)
+{
+ GMetricsTableIter iter;
+ const char *name;
+ GMetricsStackTraceSample *sample;
+ GMetricsStackTraceSamplerInstanceEntry *instance_entry;
+
+ g_metrics_table_iter_init (&iter, sampler->traces_table);
+ while (g_metrics_table_iter_next (&iter, &name, &sample))
+ g_metrics_stack_trace_free (sample->stack_trace);
+ g_metrics_table_clear (sampler->traces_table);
+
+ g_metrics_table_iter_init (&iter, sampler->instances_table);
+ while (g_metrics_table_iter_next (&iter, &name, &instance_entry))
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store,
+ instance_entry->trace_hash_key);
+ g_metrics_table_clear (sampler->instances_table);
+}
+
+void
+g_metrics_stack_trace_sampler_free (GMetricsStackTraceSampler *sampler)
+{
+ if (sampler == NULL)
+ return;
+
+ g_metrics_stack_trace_sampler_clear (sampler);
+ g_metrics_table_free (sampler->traces_table);
+ g_metrics_table_free (sampler->instances_table);
+ g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, sampler);
+}
+
+int
+g_metrics_get_timeout_fd (void)
+{
+ return timeout_fd;
+}
+
+const char *
+g_metrics_get_log_dir (void)
+{
+ return metrics_config.log_dir;
+}
diff --git a/glib/gmetrics.h b/glib/gmetrics.h
new file mode 100644
index 000000000..af061e67f
--- /dev/null
+++ b/glib/gmetrics.h
@@ -0,0 +1,333 @@
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GLib Team and others 1997-2000. See the AUTHORS
+ * file for a list of people on the GLib Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GLib at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __G_METRICS_H__
+#define __G_METRICS_H__
+
+#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
+#error "Only <glib.h> can be included directly."
+#endif
+
+#include <stdarg.h>
+#include <glib/gtypes.h>
+#include <glib/gmacros.h>
+#include <glib/gmain.h>
+
+G_BEGIN_DECLS
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_init (void);
+
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_enabled (void);
+
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_requested (const char *name);
+
+typedef void (*GMetricsTimeoutFunc) (void);
+
+GLIB_AVAILABLE_IN_ALL
+int g_metrics_get_timeout_fd (void);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_run_timeout_handlers (void);
+
+GLIB_AVAILABLE_IN_ALL
+gulong g_metrics_get_generation (void);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_start_timeout (GMetricsTimeoutFunc timeout_handler);
+
+GLIB_AVAILABLE_IN_ALL
+const char *g_metrics_get_log_dir (void);
+
+typedef struct _GMetricsFile GMetricsFile;
+GLIB_AVAILABLE_IN_ALL
+GMetricsFile *g_metrics_file_new (const char *name,
+ const char *first_column_name,
+ const char *first_column_format,
+ ...);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_file_start_record (GMetricsFile *metrics_file);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_file_add_row_without_cast (GMetricsFile *metrics_file,
+ gconstpointer first_column_value,
+ ...);
+#define g_metrics_file_add_row(_file,_first,_rest...) g_metrics_file_add_row_without_cast ((_file),
(gpointer) (_first), _rest)
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_file_end_record (GMetricsFile *metrics_file);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_file_free (GMetricsFile *metrics_file);
+
+typedef struct _GMetricsTable GMetricsTable;
+GLIB_AVAILABLE_IN_ALL
+GMetricsTable *g_metrics_table_new (gsize record_size);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_set_record (GMetricsTable *metrics_table,
+ const char *name,
+ gpointer record);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_table_get_record (GMetricsTable *metrics_table,
+ const char *name);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_remove_record (GMetricsTable *metrics_table,
+ const char *name);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_clear (GMetricsTable *metrics_table);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_free (GMetricsTable *metrics_table);
+
+typedef struct _GMetricsTableIter GMetricsTableIter;
+
+typedef struct _GMetricsTableEntry GMetricsTableEntry;
+struct _GMetricsTableIter
+{
+ /*< private >*/
+ GMetricsTableEntry *entry;
+};
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_iter_init (GMetricsTableIter *iter,
+ GMetricsTable *table);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_sorted_iter_init (GMetricsTableIter *iter,
+ GMetricsTable *table,
+ GCompareFunc comparison_function);
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_table_iter_next_without_cast (GMetricsTableIter *iter,
+ const char **name,
+ gpointer *record);
+#define g_metrics_table_iter_next(_iter,_name,_record) g_metrics_table_iter_next_without_cast ((_iter),
(_name), (gpointer *) (_record))
+
+struct _GMetricsInstanceCounterMetrics
+{
+ char comment[64];
+ gsize total_memory_usage;
+ gsize instance_count;
+ gssize instance_change;
+ gsize instance_watermark;
+ gssize average_instance_change;
+ gsize number_of_samples;
+};
+typedef struct _GMetricsInstanceCounterMetrics GMetricsInstanceCounterMetrics;
+
+typedef struct _GMetricsInstanceCounter GMetricsInstanceCounter;
+
+GLIB_AVAILABLE_IN_ALL
+GMetricsInstanceCounter *g_metrics_instance_counter_new (void);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_start_record (GMetricsInstanceCounter *counter);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_add_instance (GMetricsInstanceCounter *counter,
+ const char *name,
+ gsize memory_usage);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_add_instances (GMetricsInstanceCounter *counter,
+ const char *name,
+ const char *comment,
+ gsize number_of_instances,
+ gsize total_usage);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_end_record (GMetricsInstanceCounter *counter);
+
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_instance_counter_instance_is_interesting (GMetricsInstanceCounter *counter,
+ const char *name);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_free (GMetricsInstanceCounter *counter);
+
+typedef struct _GMetricsInstanceCounterIter GMetricsInstanceCounterIter;
+
+typedef struct _GMetricsInstanceCounterEntry GMetricsInstanceCounterEntry;
+struct _GMetricsInstanceCounterIter
+{
+ /*< private >*/
+ gssize table_index;
+ GMetricsTableIter table_iter;
+};
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_iter_init (GMetricsInstanceCounterIter *iter,
+ GMetricsInstanceCounter *table);
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_instance_counter_iter_next (GMetricsInstanceCounterIter *iter,
+ const char **name,
+ GMetricsInstanceCounterMetrics **metrics);
+
+typedef struct _GMetricsList GMetricsList;
+GLIB_AVAILABLE_IN_ALL
+GMetricsList *g_metrics_list_new (void);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_add_item (GMetricsList *list,
+ gpointer item);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_remove_item (GMetricsList *list,
+ gpointer item);
+GLIB_AVAILABLE_IN_ALL
+gsize g_metrics_list_get_length (GMetricsList *list);
+
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_list_get_last_item (GMetricsList *list);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_remove_last_item (GMetricsList *list);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_free (GMetricsList *list);
+
+typedef struct _GMetricsListIter GMetricsListIter;
+typedef struct _GMetricsListNode GMetricsListNode;
+struct _GMetricsListIter
+{
+ /*< private >*/
+ GMetricsListNode *node;
+ GMetricsListNode *next_node;
+};
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_iter_init (GMetricsListIter *iter,
+ GMetricsList *list);
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_list_iter_next_without_cast (GMetricsListIter *iter,
+ gpointer *item);
+#define g_metrics_list_iter_next(_iter,_item) g_metrics_list_iter_next_without_cast ((_iter), (gpointer *)
(_item))
+
+typedef struct _GMetricsStackTrace GMetricsStackTrace;
+
+GLIB_AVAILABLE_IN_ALL
+GMetricsStackTrace *g_metrics_stack_trace_new (int start_frame,
+ int number_of_frames,
+ const char *delimiter);
+GLIB_AVAILABLE_IN_ALL
+const char *g_metrics_stack_trace_get_hash_key (GMetricsStackTrace *stack_trace);
+
+GLIB_AVAILABLE_IN_ALL
+const char *g_metrics_stack_trace_get_output (GMetricsStackTrace *stack_trace);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_add_annotation (GMetricsStackTrace *stack_trace,
+ const char *annotation);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_free (GMetricsStackTrace *stack_trace);
+
+GLIB_AVAILABLE_IN_ALL
+char *g_metrics_stack_trace (void);
+
+typedef struct _GMetricsStackTraceSampler GMetricsStackTraceSampler;
+typedef struct _GMetricsStackTraceSample GMetricsStackTraceSample;
+typedef struct _GMetricsStackTraceSamplerIter GMetricsStackTraceSamplerIter;
+
+struct _GMetricsStackTraceSamplerIter
+{
+ /*< private >*/
+ GMetricsTableIter table_iter;
+};
+
+struct _GMetricsStackTraceSample
+{
+ char name[64];
+ GMetricsStackTrace *stack_trace;
+ gsize number_of_hits;
+};
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_iter_init (GMetricsStackTraceSamplerIter *iter,
+ GMetricsStackTraceSampler *sampler);
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_stack_trace_sampler_iter_next (GMetricsStackTraceSamplerIter *iter,
+ GMetricsStackTraceSample **sample);
+
+typedef gboolean (* GMetricsStackTraceAnnotationHandler) (char *annotation, gsize annotation_size, gpointer
user_data);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_set_stack_trace_annotation_handler (GMetricsStackTraceAnnotationHandler handler,
+ gpointer user_data);
+GLIB_AVAILABLE_IN_ALL
+GMetricsStackTraceSampler *g_metrics_stack_trace_sampler_new (void);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_take_sample (GMetricsStackTraceSampler *sample,
+ const char *name,
+ gpointer instance);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_remove_sample (GMetricsStackTraceSampler *sampler,
+ gpointer instance);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_clear (GMetricsStackTraceSampler *sampler);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_free (GMetricsStackTraceSampler *sampler);
+
+typedef struct _GMetricsAllocationBlockStore GMetricsAllocationBlockStore;
+GLIB_AVAILABLE_IN_ALL
+GMetricsAllocationBlockStore *g_metrics_allocation_block_store_new (const char *name,
+ gsize size);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_allocation_block_store_free (GMetricsAllocationBlockStore *block_store);
+
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_allocate (GMetricsAllocationBlockStore *block_store,
+ gsize size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_allocate_with_name (GMetricsAllocationBlockStore *block_store,
+ gsize size,
+ const char *name);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_reallocate (GMetricsAllocationBlockStore *block_store,
+ gpointer allocation,
+ gsize size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_copy (GMetricsAllocationBlockStore *block_store,
+ gconstpointer allocation,
+ gsize size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_copy_with_name (GMetricsAllocationBlockStore *block_store,
+ gconstpointer allocation,
+ gsize size,
+ const char *name);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_allocation_block_store_deallocate (GMetricsAllocationBlockStore *block_store,
+ gpointer allocation);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_push_default_allocation_block_store (GMetricsAllocationBlockStore *block_store);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_pop_default_allocation_block_store (void);
+
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocate (gsize size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_reallocate (gpointer allocation,
+ gsize size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_copy (gconstpointer allocation,
+ gsize size);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_free (gpointer allocation);
+G_END_DECLS
+
+#endif /* __G_METRICS_H__ */
diff --git a/glib/uthash.h b/glib/uthash.h
new file mode 100644
index 000000000..10780f2d5
--- /dev/null
+++ b/glib/uthash.h
@@ -0,0 +1,1138 @@
+/*
+Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTHASH_H
+#define UTHASH_H
+
+#define UTHASH_VERSION 2.3.0
+
+#include <string.h> /* memcmp, memset, strlen */
+#include <stddef.h> /* ptrdiff_t */
+#include <stdlib.h> /* exit */
+
+#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT
+/* This codepath is provided for backward compatibility, but I plan to remove it. */
+#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead"
+typedef unsigned int uint32_t;
+typedef unsigned char uint8_t;
+#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT
+#else
+#include <stdint.h> /* uint8_t, uint32_t */
+#endif
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+ As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+ when compiling c++ source) this code uses whatever method is needed
+ or, for VS2008 where neither is available, uses casting workarounds. */
+#if !defined(DECLTYPE) && !defined(NO_DECLTYPE)
+#if defined(_MSC_VER) /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */
+#define DECLTYPE(x) (decltype(x))
+#else /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#endif
+#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#else /* GNU, Sun and other compilers */
+#define DECLTYPE(x) (__typeof(x))
+#endif
+#endif
+
+#ifdef NO_DECLTYPE
+#define DECLTYPE(x)
+#define DECLTYPE_ASSIGN(dst,src) \
+do { \
+ char **_da_dst = (char**)(&(dst)); \
+ *_da_dst = (char*)(src); \
+} while (0)
+#else
+#define DECLTYPE_ASSIGN(dst,src) \
+do { \
+ (dst) = DECLTYPE(dst)(src); \
+} while (0)
+#endif
+
+#ifndef uthash_malloc
+#define uthash_malloc(sz) malloc(sz) /* malloc fcn */
+#endif
+#ifndef uthash_free
+#define uthash_free(ptr,sz) free(ptr) /* free fcn */
+#endif
+#ifndef uthash_bzero
+#define uthash_bzero(a,n) memset(a,'\0',n)
+#endif
+#ifndef uthash_strlen
+#define uthash_strlen(s) strlen(s)
+#endif
+
+#ifndef HASH_FUNCTION
+#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv)
+#endif
+
+#ifndef HASH_KEYCMP
+#define HASH_KEYCMP(a,b,n) memcmp(a,b,n)
+#endif
+
+#ifndef uthash_noexpand_fyi
+#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */
+#endif
+#ifndef uthash_expand_fyi
+#define uthash_expand_fyi(tbl) /* can be defined to log expands */
+#endif
+
+#ifndef HASH_NONFATAL_OOM
+#define HASH_NONFATAL_OOM 0
+#endif
+
+#if HASH_NONFATAL_OOM
+/* malloc failures can be recovered from */
+
+#ifndef uthash_nonfatal_oom
+#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */
+#endif
+
+#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0)
+#define IF_HASH_NONFATAL_OOM(x) x
+
+#else
+/* malloc failures result in lost memory, hash tables are unusable */
+
+#ifndef uthash_fatal
+#define uthash_fatal(msg) exit(-1) /* fatal OOM error */
+#endif
+
+#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory")
+#define IF_HASH_NONFATAL_OOM(x)
+
+#endif
+
+/* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS 512U /* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS_LOG2 9U /* lg2 of initial number of buckets */
+#define HASH_BKT_CAPACITY_THRESH 128U /* expand when bucket count reaches */
+
+/* calculate the element whose hash handle address is hhp */
+#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
+/* calculate the hash handle from element address elp */
+#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho)))
+
+#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \
+do { \
+ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \
+ unsigned _hd_bkt; \
+ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \
+ (head)->hh.tbl->buckets[_hd_bkt].count++; \
+ _hd_hh_item->hh_next = NULL; \
+ _hd_hh_item->hh_prev = NULL; \
+} while (0)
+
+#define HASH_VALUE(keyptr,keylen,hashv) \
+do { \
+ HASH_FUNCTION(keyptr, keylen, hashv); \
+} while (0)
+
+#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \
+do { \
+ (out) = NULL; \
+ if (head) { \
+ unsigned _hf_bkt; \
+ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \
+ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \
+ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval,
out); \
+ } \
+ } \
+} while (0)
+
+#define HASH_FIND(hh,head,keyptr,keylen,out) \
+do { \
+ (out) = NULL; \
+ if (head) { \
+ unsigned _hf_hashv; \
+ HASH_VALUE(keyptr, keylen, _hf_hashv); \
+ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \
+ } \
+} while (0)
+
+#ifdef HASH_BLOOM
+#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM)
+#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL)
+#define HASH_BLOOM_MAKE(tbl,oomed) \
+do { \
+ (tbl)->bloom_nbits = HASH_BLOOM; \
+ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \
+ if (!(tbl)->bloom_bv) { \
+ HASH_RECORD_OOM(oomed); \
+ } else { \
+ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \
+ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \
+ } \
+} while (0)
+
+#define HASH_BLOOM_FREE(tbl) \
+do { \
+ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \
+} while (0)
+
+#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U)))
+#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U)))
+
+#define HASH_BLOOM_ADD(tbl,hashv) \
+ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))
+
+#define HASH_BLOOM_TEST(tbl,hashv) \
+ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))
+
+#else
+#define HASH_BLOOM_MAKE(tbl,oomed)
+#define HASH_BLOOM_FREE(tbl)
+#define HASH_BLOOM_ADD(tbl,hashv)
+#define HASH_BLOOM_TEST(tbl,hashv) (1)
+#define HASH_BLOOM_BYTELEN 0U
+#endif
+
+#define HASH_MAKE_TABLE(hh,head,oomed) \
+do { \
+ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \
+ if (!(head)->hh.tbl) { \
+ HASH_RECORD_OOM(oomed); \
+ } else { \
+ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head)->hh.tbl->tail = &((head)->hh); \
+ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \
+ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \
+ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \
+ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \
+ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \
+ (head)->hh.tbl->signature = HASH_SIGNATURE; \
+ if (!(head)->hh.tbl->buckets) { \
+ HASH_RECORD_OOM(oomed); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ } else { \
+ uthash_bzero((head)->hh.tbl->buckets, \
+ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \
+ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \
+ IF_HASH_NONFATAL_OOM( \
+ if (oomed) { \
+ uthash_free((head)->hh.tbl->buckets, \
+ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ } \
+ ) \
+ } \
+ } \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \
+do { \
+ (replaced) = NULL; \
+ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+ if (replaced) { \
+ HASH_DELETE(hh, head, replaced); \
+ } \
+ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \
+do { \
+ (replaced) = NULL; \
+ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+ if (replaced) { \
+ HASH_DELETE(hh, head, replaced); \
+ } \
+ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \
+} while (0)
+
+#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \
+do { \
+ unsigned _hr_hashv; \
+ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \
+ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \
+} while (0)
+
+#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \
+do { \
+ unsigned _hr_hashv; \
+ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \
+ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \
+} while (0)
+
+#define HASH_APPEND_LIST(hh, head, add) \
+do { \
+ (add)->hh.next = NULL; \
+ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \
+ (head)->hh.tbl->tail->next = (add); \
+ (head)->hh.tbl->tail = &((add)->hh); \
+} while (0)
+
+#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \
+do { \
+ do { \
+ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \
+ break; \
+ } \
+ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \
+} while (0)
+
+#ifdef NO_DECLTYPE
+#undef HASH_AKBI_INNER_LOOP
+#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \
+do { \
+ char *_hs_saved_head = (char*)(head); \
+ do { \
+ DECLTYPE_ASSIGN(head, _hs_iter); \
+ if (cmpfcn(head, add) > 0) { \
+ DECLTYPE_ASSIGN(head, _hs_saved_head); \
+ break; \
+ } \
+ DECLTYPE_ASSIGN(head, _hs_saved_head); \
+ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \
+} while (0)
+#endif
+
+#if HASH_NONFATAL_OOM
+
+#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \
+do { \
+ if (!(oomed)) { \
+ unsigned _ha_bkt; \
+ (head)->hh.tbl->num_items++; \
+ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \
+ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \
+ if (oomed) { \
+ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \
+ HASH_DELETE_HH(hh, head, &(add)->hh); \
+ (add)->hh.tbl = NULL; \
+ uthash_nonfatal_oom(add); \
+ } else { \
+ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \
+ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \
+ } \
+ } else { \
+ (add)->hh.tbl = NULL; \
+ uthash_nonfatal_oom(add); \
+ } \
+} while (0)
+
+#else
+
+#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \
+do { \
+ unsigned _ha_bkt; \
+ (head)->hh.tbl->num_items++; \
+ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \
+ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \
+ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \
+ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \
+} while (0)
+
+#endif
+
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \
+do { \
+ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \
+ (add)->hh.hashv = (hashval); \
+ (add)->hh.key = (char*) (keyptr); \
+ (add)->hh.keylen = (unsigned) (keylen_in); \
+ if (!(head)) { \
+ (add)->hh.next = NULL; \
+ (add)->hh.prev = NULL; \
+ HASH_MAKE_TABLE(hh, add, _ha_oomed); \
+ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \
+ (head) = (add); \
+ IF_HASH_NONFATAL_OOM( } ) \
+ } else { \
+ void *_hs_iter = (head); \
+ (add)->hh.tbl = (head)->hh.tbl; \
+ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \
+ if (_hs_iter) { \
+ (add)->hh.next = _hs_iter; \
+ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \
+ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \
+ } else { \
+ (head) = (add); \
+ } \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \
+ } else { \
+ HASH_APPEND_LIST(hh, head, add); \
+ } \
+ } \
+ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \
+ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \
+} while (0)
+
+#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \
+do { \
+ unsigned _hs_hashv; \
+ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \
+ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \
+ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn)
+
+#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \
+ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn)
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \
+do { \
+ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \
+ (add)->hh.hashv = (hashval); \
+ (add)->hh.key = (const void*) (keyptr); \
+ (add)->hh.keylen = (unsigned) (keylen_in); \
+ if (!(head)) { \
+ (add)->hh.next = NULL; \
+ (add)->hh.prev = NULL; \
+ HASH_MAKE_TABLE(hh, add, _ha_oomed); \
+ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \
+ (head) = (add); \
+ IF_HASH_NONFATAL_OOM( } ) \
+ } else { \
+ (add)->hh.tbl = (head)->hh.tbl; \
+ HASH_APPEND_LIST(hh, head, add); \
+ } \
+ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \
+ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \
+} while (0)
+
+#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \
+do { \
+ unsigned _ha_hashv; \
+ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \
+ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \
+ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add)
+
+#define HASH_ADD(hh,head,fieldname,keylen_in,add) \
+ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add)
+
+#define HASH_TO_BKT(hashv,num_bkts,bkt) \
+do { \
+ bkt = ((hashv) & ((num_bkts) - 1U)); \
+} while (0)
+
+/* delete "delptr" from the hash table.
+ * "the usual" patch-up process for the app-order doubly-linked-list.
+ * The use of _hd_hh_del below deserves special explanation.
+ * These used to be expressed using (delptr) but that led to a bug
+ * if someone used the same symbol for the head and deletee, like
+ * HASH_DELETE(hh,users,users);
+ * We want that to work, but by changing the head (users) below
+ * we were forfeiting our ability to further refer to the deletee (users)
+ * in the patch-up process. Solution: use scratch space to
+ * copy the deletee pointer, then the latter references are via that
+ * scratch pointer rather than through the repointed (users) symbol.
+ */
+#define HASH_DELETE(hh,head,delptr) \
+ HASH_DELETE_HH(hh, head, &(delptr)->hh)
+
+#define HASH_DELETE_HH(hh,head,delptrhh) \
+do { \
+ struct UT_hash_handle *_hd_hh_del = (delptrhh); \
+ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \
+ HASH_BLOOM_FREE((head)->hh.tbl); \
+ uthash_free((head)->hh.tbl->buckets, \
+ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head) = NULL; \
+ } else { \
+ unsigned _hd_bkt; \
+ if (_hd_hh_del == (head)->hh.tbl->tail) { \
+ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \
+ } \
+ if (_hd_hh_del->prev != NULL) { \
+ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \
+ } else { \
+ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \
+ } \
+ if (_hd_hh_del->next != NULL) { \
+ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \
+ } \
+ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \
+ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \
+ (head)->hh.tbl->num_items--; \
+ } \
+ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \
+} while (0)
+
+/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */
+#define HASH_FIND_STR(head,findstr,out) \
+do { \
+ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \
+ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \
+} while (0)
+#define HASH_ADD_STR(head,strfield,add) \
+do { \
+ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \
+ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \
+} while (0)
+#define HASH_REPLACE_STR(head,strfield,add,replaced) \
+do { \
+ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \
+ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \
+} while (0)
+#define HASH_FIND_INT(head,findint,out) \
+ HASH_FIND(hh,head,findint,sizeof(int),out)
+#define HASH_ADD_INT(head,intfield,add) \
+ HASH_ADD(hh,head,intfield,sizeof(int),add)
+#define HASH_REPLACE_INT(head,intfield,add,replaced) \
+ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)
+#define HASH_FIND_PTR(head,findptr,out) \
+ HASH_FIND(hh,head,findptr,sizeof(void *),out)
+#define HASH_ADD_PTR(head,ptrfield,add) \
+ HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
+#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \
+ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)
+#define HASH_DEL(head,delptr) \
+ HASH_DELETE(hh,head,delptr)
+
+/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.
+ * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.
+ */
+#ifdef HASH_DEBUG
+#include <stdio.h> /* fprintf, stderr */
+#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0)
+#define HASH_FSCK(hh,head,where) \
+do { \
+ struct UT_hash_handle *_thh; \
+ if (head) { \
+ unsigned _bkt_i; \
+ unsigned _count = 0; \
+ char *_prev; \
+ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \
+ unsigned _bkt_count = 0; \
+ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \
+ _prev = NULL; \
+ while (_thh) { \
+ if (_prev != (char*)(_thh->hh_prev)) { \
+ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \
+ (where), (void*)_thh->hh_prev, (void*)_prev); \
+ } \
+ _bkt_count++; \
+ _prev = (char*)(_thh); \
+ _thh = _thh->hh_next; \
+ } \
+ _count += _bkt_count; \
+ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \
+ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \
+ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \
+ } \
+ } \
+ if (_count != (head)->hh.tbl->num_items) { \
+ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \
+ (where), (head)->hh.tbl->num_items, _count); \
+ } \
+ _count = 0; \
+ _prev = NULL; \
+ _thh = &(head)->hh; \
+ while (_thh) { \
+ _count++; \
+ if (_prev != (char*)_thh->prev) { \
+ HASH_OOPS("%s: invalid prev %p, actual %p\n", \
+ (where), (void*)_thh->prev, (void*)_prev); \
+ } \
+ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \
+ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \
+ } \
+ if (_count != (head)->hh.tbl->num_items) { \
+ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \
+ (where), (head)->hh.tbl->num_items, _count); \
+ } \
+ } \
+} while (0)
+#else
+#define HASH_FSCK(hh,head,where)
+#endif
+
+/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to
+ * the descriptor to which this macro is defined for tuning the hash function.
+ * The app can #include <unistd.h> to get the prototype for write(2). */
+#ifdef HASH_EMIT_KEYS
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \
+do { \
+ unsigned _klen = fieldlen; \
+ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \
+ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \
+} while (0)
+#else
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)
+#endif
+
+/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */
+#define HASH_BER(key,keylen,hashv) \
+do { \
+ unsigned _hb_keylen = (unsigned)keylen; \
+ const unsigned char *_hb_key = (const unsigned char*)(key); \
+ (hashv) = 0; \
+ while (_hb_keylen-- != 0U) { \
+ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \
+ } \
+} while (0)
+
+
+/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at
+ * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */
+#define HASH_SAX(key,keylen,hashv) \
+do { \
+ unsigned _sx_i; \
+ const unsigned char *_hs_key = (const unsigned char*)(key); \
+ hashv = 0; \
+ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \
+ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \
+ } \
+} while (0)
+/* FNV-1a variation */
+#define HASH_FNV(key,keylen,hashv) \
+do { \
+ unsigned _fn_i; \
+ const unsigned char *_hf_key = (const unsigned char*)(key); \
+ (hashv) = 2166136261U; \
+ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \
+ hashv = hashv ^ _hf_key[_fn_i]; \
+ hashv = hashv * 16777619U; \
+ } \
+} while (0)
+
+#define HASH_OAT(key,keylen,hashv) \
+do { \
+ unsigned _ho_i; \
+ const unsigned char *_ho_key=(const unsigned char*)(key); \
+ hashv = 0; \
+ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \
+ hashv += _ho_key[_ho_i]; \
+ hashv += (hashv << 10); \
+ hashv ^= (hashv >> 6); \
+ } \
+ hashv += (hashv << 3); \
+ hashv ^= (hashv >> 11); \
+ hashv += (hashv << 15); \
+} while (0)
+
+#define HASH_JEN_MIX(a,b,c) \
+do { \
+ a -= b; a -= c; a ^= ( c >> 13 ); \
+ b -= c; b -= a; b ^= ( a << 8 ); \
+ c -= a; c -= b; c ^= ( b >> 13 ); \
+ a -= b; a -= c; a ^= ( c >> 12 ); \
+ b -= c; b -= a; b ^= ( a << 16 ); \
+ c -= a; c -= b; c ^= ( b >> 5 ); \
+ a -= b; a -= c; a ^= ( c >> 3 ); \
+ b -= c; b -= a; b ^= ( a << 10 ); \
+ c -= a; c -= b; c ^= ( b >> 15 ); \
+} while (0)
+
+#define HASH_JEN(key,keylen,hashv) \
+do { \
+ unsigned _hj_i,_hj_j,_hj_k; \
+ unsigned const char *_hj_key=(unsigned const char*)(key); \
+ hashv = 0xfeedbeefu; \
+ _hj_i = _hj_j = 0x9e3779b9u; \
+ _hj_k = (unsigned)(keylen); \
+ while (_hj_k >= 12U) { \
+ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \
+ + ( (unsigned)_hj_key[2] << 16 ) \
+ + ( (unsigned)_hj_key[3] << 24 ) ); \
+ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \
+ + ( (unsigned)_hj_key[6] << 16 ) \
+ + ( (unsigned)_hj_key[7] << 24 ) ); \
+ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \
+ + ( (unsigned)_hj_key[10] << 16 ) \
+ + ( (unsigned)_hj_key[11] << 24 ) ); \
+ \
+ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \
+ \
+ _hj_key += 12; \
+ _hj_k -= 12U; \
+ } \
+ hashv += (unsigned)(keylen); \
+ switch ( _hj_k ) { \
+ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \
+ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \
+ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \
+ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \
+ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \
+ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \
+ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \
+ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \
+ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \
+ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \
+ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \
+ default: ; \
+ } \
+ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \
+} while (0)
+
+/* The Paul Hsieh hash function */
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \
+ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \
+ +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+#define HASH_SFH(key,keylen,hashv) \
+do { \
+ unsigned const char *_sfh_key=(unsigned const char*)(key); \
+ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \
+ \
+ unsigned _sfh_rem = _sfh_len & 3U; \
+ _sfh_len >>= 2; \
+ hashv = 0xcafebabeu; \
+ \
+ /* Main loop */ \
+ for (;_sfh_len > 0U; _sfh_len--) { \
+ hashv += get16bits (_sfh_key); \
+ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \
+ hashv = (hashv << 16) ^ _sfh_tmp; \
+ _sfh_key += 2U*sizeof (uint16_t); \
+ hashv += hashv >> 11; \
+ } \
+ \
+ /* Handle end cases */ \
+ switch (_sfh_rem) { \
+ case 3: hashv += get16bits (_sfh_key); \
+ hashv ^= hashv << 16; \
+ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \
+ hashv += hashv >> 11; \
+ break; \
+ case 2: hashv += get16bits (_sfh_key); \
+ hashv ^= hashv << 11; \
+ hashv += hashv >> 17; \
+ break; \
+ case 1: hashv += *_sfh_key; \
+ hashv ^= hashv << 10; \
+ hashv += hashv >> 1; \
+ break; \
+ default: ; \
+ } \
+ \
+ /* Force "avalanching" of final 127 bits */ \
+ hashv ^= hashv << 3; \
+ hashv += hashv >> 5; \
+ hashv ^= hashv << 4; \
+ hashv += hashv >> 17; \
+ hashv ^= hashv << 25; \
+ hashv += hashv >> 6; \
+} while (0)
+
+/* iterate over items in a known bucket to find desired item */
+#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \
+do { \
+ if ((head).hh_head != NULL) { \
+ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \
+ } else { \
+ (out) = NULL; \
+ } \
+ while ((out) != NULL) { \
+ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \
+ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \
+ break; \
+ } \
+ } \
+ if ((out)->hh.hh_next != NULL) { \
+ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \
+ } else { \
+ (out) = NULL; \
+ } \
+ } \
+} while (0)
+
+/* add an item to a bucket */
+#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \
+do { \
+ UT_hash_bucket *_ha_head = &(head); \
+ _ha_head->count++; \
+ (addhh)->hh_next = _ha_head->hh_head; \
+ (addhh)->hh_prev = NULL; \
+ if (_ha_head->hh_head != NULL) { \
+ _ha_head->hh_head->hh_prev = (addhh); \
+ } \
+ _ha_head->hh_head = (addhh); \
+ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \
+ && !(addhh)->tbl->noexpand) { \
+ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \
+ IF_HASH_NONFATAL_OOM( \
+ if (oomed) { \
+ HASH_DEL_IN_BKT(head,addhh); \
+ } \
+ ) \
+ } \
+} while (0)
+
+/* remove an item from a given bucket */
+#define HASH_DEL_IN_BKT(head,delhh) \
+do { \
+ UT_hash_bucket *_hd_head = &(head); \
+ _hd_head->count--; \
+ if (_hd_head->hh_head == (delhh)) { \
+ _hd_head->hh_head = (delhh)->hh_next; \
+ } \
+ if ((delhh)->hh_prev) { \
+ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \
+ } \
+ if ((delhh)->hh_next) { \
+ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \
+ } \
+} while (0)
+
+/* Bucket expansion has the effect of doubling the number of buckets
+ * and redistributing the items into the new buckets. Ideally the
+ * items will distribute more or less evenly into the new buckets
+ * (the extent to which this is true is a measure of the quality of
+ * the hash function as it applies to the key domain).
+ *
+ * With the items distributed into more buckets, the chain length
+ * (item count) in each bucket is reduced. Thus by expanding buckets
+ * the hash keeps a bound on the chain length. This bounded chain
+ * length is the essence of how a hash provides constant time lookup.
+ *
+ * The calculation of tbl->ideal_chain_maxlen below deserves some
+ * explanation. First, keep in mind that we're calculating the ideal
+ * maximum chain length based on the *new* (doubled) bucket count.
+ * In fractions this is just n/b (n=number of items,b=new num buckets).
+ * Since the ideal chain length is an integer, we want to calculate
+ * ceil(n/b). We don't depend on floating point arithmetic in this
+ * hash, so to calculate ceil(n/b) with integers we could write
+ *
+ * ceil(n/b) = (n/b) + ((n%b)?1:0)
+ *
+ * and in fact a previous version of this hash did just that.
+ * But now we have improved things a bit by recognizing that b is
+ * always a power of two. We keep its base 2 log handy (call it lb),
+ * so now we can write this with a bit shift and logical AND:
+ *
+ * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
+ *
+ */
+#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \
+do { \
+ unsigned _he_bkt; \
+ unsigned _he_bkt_i; \
+ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \
+ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \
+ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \
+ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \
+ if (!_he_new_buckets) { \
+ HASH_RECORD_OOM(oomed); \
+ } else { \
+ uthash_bzero(_he_new_buckets, \
+ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \
+ (tbl)->ideal_chain_maxlen = \
+ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \
+ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \
+ (tbl)->nonideal_items = 0; \
+ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \
+ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \
+ while (_he_thh != NULL) { \
+ _he_hh_nxt = _he_thh->hh_next; \
+ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \
+ _he_newbkt = &(_he_new_buckets[_he_bkt]); \
+ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \
+ (tbl)->nonideal_items++; \
+ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \
+ _he_newbkt->expand_mult++; \
+ } \
+ } \
+ _he_thh->hh_prev = NULL; \
+ _he_thh->hh_next = _he_newbkt->hh_head; \
+ if (_he_newbkt->hh_head != NULL) { \
+ _he_newbkt->hh_head->hh_prev = _he_thh; \
+ } \
+ _he_newbkt->hh_head = _he_thh; \
+ _he_thh = _he_hh_nxt; \
+ } \
+ } \
+ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \
+ (tbl)->num_buckets *= 2U; \
+ (tbl)->log2_num_buckets++; \
+ (tbl)->buckets = _he_new_buckets; \
+ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \
+ ((tbl)->ineff_expands+1U) : 0U; \
+ if ((tbl)->ineff_expands > 1U) { \
+ (tbl)->noexpand = 1; \
+ uthash_noexpand_fyi(tbl); \
+ } \
+ uthash_expand_fyi(tbl); \
+ } \
+} while (0)
+
+
+/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */
+/* Note that HASH_SORT assumes the hash handle name to be hh.
+ * HASH_SRT was added to allow the hash handle name to be passed in. */
+#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)
+#define HASH_SRT(hh,head,cmpfcn) HASH_SRT_DATA(hh,head,cmpfcn,NULL)
+#define HASH_SRT_DATA(hh,head,cmpfcn,data) \
+do { \
+ unsigned _hs_i; \
+ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \
+ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \
+ if (head != NULL) { \
+ _hs_insize = 1; \
+ _hs_looping = 1; \
+ _hs_list = &((head)->hh); \
+ while (_hs_looping != 0U) { \
+ _hs_p = _hs_list; \
+ _hs_list = NULL; \
+ _hs_tail = NULL; \
+ _hs_nmerges = 0; \
+ while (_hs_p != NULL) { \
+ _hs_nmerges++; \
+ _hs_q = _hs_p; \
+ _hs_psize = 0; \
+ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \
+ _hs_psize++; \
+ _hs_q = ((_hs_q->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \
+ if (_hs_q == NULL) { \
+ break; \
+ } \
+ } \
+ _hs_qsize = _hs_insize; \
+ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \
+ if (_hs_psize == 0U) { \
+ _hs_e = _hs_q; \
+ _hs_q = ((_hs_q->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \
+ _hs_qsize--; \
+ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \
+ _hs_e = _hs_p; \
+ if (_hs_p != NULL) { \
+ _hs_p = ((_hs_p->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \
+ } \
+ _hs_psize--; \
+ } else if ((cmpfcn( \
+ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \
+ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)), \
+ data \
+ )) <= 0) { \
+ _hs_e = _hs_p; \
+ if (_hs_p != NULL) { \
+ _hs_p = ((_hs_p->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \
+ } \
+ _hs_psize--; \
+ } else { \
+ _hs_e = _hs_q; \
+ _hs_q = ((_hs_q->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \
+ _hs_qsize--; \
+ } \
+ if ( _hs_tail != NULL ) { \
+ _hs_tail->next = ((_hs_e != NULL) ? \
+ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \
+ } else { \
+ _hs_list = _hs_e; \
+ } \
+ if (_hs_e != NULL) { \
+ _hs_e->prev = ((_hs_tail != NULL) ? \
+ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \
+ } \
+ _hs_tail = _hs_e; \
+ } \
+ _hs_p = _hs_q; \
+ } \
+ if (_hs_tail != NULL) { \
+ _hs_tail->next = NULL; \
+ } \
+ if (_hs_nmerges <= 1U) { \
+ _hs_looping = 0; \
+ (head)->hh.tbl->tail = _hs_tail; \
+ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \
+ } \
+ _hs_insize *= 2U; \
+ } \
+ HASH_FSCK(hh, head, "HASH_SRT"); \
+ } \
+} while (0)
+
+/* This function selects items from one hash into another hash.
+ * The end result is that the selected items have dual presence
+ * in both hashes. There is no copy of the items made; rather
+ * they are added into the new hash through a secondary hash
+ * hash handle that must be present in the structure. */
+#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \
+do { \
+ unsigned _src_bkt, _dst_bkt; \
+ void *_last_elt = NULL, *_elt; \
+ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \
+ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \
+ if ((src) != NULL) { \
+ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \
+ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \
+ _src_hh != NULL; \
+ _src_hh = _src_hh->hh_next) { \
+ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \
+ if (cond(_elt)) { \
+ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \
+ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \
+ _dst_hh->key = _src_hh->key; \
+ _dst_hh->keylen = _src_hh->keylen; \
+ _dst_hh->hashv = _src_hh->hashv; \
+ _dst_hh->prev = _last_elt; \
+ _dst_hh->next = NULL; \
+ if (_last_elt_hh != NULL) { \
+ _last_elt_hh->next = _elt; \
+ } \
+ if ((dst) == NULL) { \
+ DECLTYPE_ASSIGN(dst, _elt); \
+ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \
+ IF_HASH_NONFATAL_OOM( \
+ if (_hs_oomed) { \
+ uthash_nonfatal_oom(_elt); \
+ (dst) = NULL; \
+ continue; \
+ } \
+ ) \
+ } else { \
+ _dst_hh->tbl = (dst)->hh_dst.tbl; \
+ } \
+ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \
+ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \
+ (dst)->hh_dst.tbl->num_items++; \
+ IF_HASH_NONFATAL_OOM( \
+ if (_hs_oomed) { \
+ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \
+ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \
+ _dst_hh->tbl = NULL; \
+ uthash_nonfatal_oom(_elt); \
+ continue; \
+ } \
+ ) \
+ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \
+ _last_elt = _elt; \
+ _last_elt_hh = _dst_hh; \
+ } \
+ } \
+ } \
+ } \
+ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \
+} while (0)
+
+#define HASH_CLEAR(hh,head) \
+do { \
+ if ((head) != NULL) { \
+ HASH_BLOOM_FREE((head)->hh.tbl); \
+ uthash_free((head)->hh.tbl->buckets, \
+ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head) = NULL; \
+ } \
+} while (0)
+
+#define HASH_OVERHEAD(hh,head) \
+ (((head) != NULL) ? ( \
+ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \
+ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \
+ sizeof(UT_hash_table) + \
+ (HASH_BLOOM_BYTELEN))) : 0U)
+
+#ifdef NO_DECLTYPE
+#define HASH_ITER(hh,head,el,tmp) \
+for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \
+ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#else
+#define HASH_ITER(hh,head,el,tmp) \
+for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \
+ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#endif
+
+/* obtain a count of items in the hash */
+#define HASH_COUNT(head) HASH_CNT(hh,head)
+#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)
+
+typedef struct UT_hash_bucket {
+ struct UT_hash_handle *hh_head;
+ unsigned count;
+
+ /* expand_mult is normally set to 0. In this situation, the max chain length
+ * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If
+ * the bucket's chain exceeds this length, bucket expansion is triggered).
+ * However, setting expand_mult to a non-zero value delays bucket expansion
+ * (that would be triggered by additions to this particular bucket)
+ * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.
+ * (The multiplier is simply expand_mult+1). The whole idea of this
+ * multiplier is to reduce bucket expansions, since they are expensive, in
+ * situations where we know that a particular bucket tends to be overused.
+ * It is better to let its chain length grow to a longer yet-still-bounded
+ * value, than to do an O(n) bucket expansion too often.
+ */
+ unsigned expand_mult;
+
+} UT_hash_bucket;
+
+/* random signature used only to find hash tables in external analysis */
+#define HASH_SIGNATURE 0xa0111fe1u
+#define HASH_BLOOM_SIGNATURE 0xb12220f2u
+
+typedef struct UT_hash_table {
+ UT_hash_bucket *buckets;
+ unsigned num_buckets, log2_num_buckets;
+ unsigned num_items;
+ struct UT_hash_handle *tail; /* tail hh in app order, for fast append */
+ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
+
+ /* in an ideal situation (all buckets used equally), no bucket would have
+ * more than ceil(#items/#buckets) items. that's the ideal chain length. */
+ unsigned ideal_chain_maxlen;
+
+ /* nonideal_items is the number of items in the hash whose chain position
+ * exceeds the ideal chain maxlen. these items pay the penalty for an uneven
+ * hash distribution; reaching them in a chain traversal takes >ideal steps */
+ unsigned nonideal_items;
+
+ /* ineffective expands occur when a bucket doubling was performed, but
+ * afterward, more than half the items in the hash had nonideal chain
+ * positions. If this happens on two consecutive expansions we inhibit any
+ * further expansion, as it's not helping; this happens when the hash
+ * function isn't a good fit for the key domain. When expansion is inhibited
+ * the hash will still work, albeit no longer in constant time. */
+ unsigned ineff_expands, noexpand;
+
+ uint32_t signature; /* used only to find hash tables in external analysis */
+#ifdef HASH_BLOOM
+ uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
+ uint8_t *bloom_bv;
+ uint8_t bloom_nbits;
+#endif
+
+} UT_hash_table;
+
+typedef struct UT_hash_handle {
+ struct UT_hash_table *tbl;
+ void *prev; /* prev element in app order */
+ void *next; /* next element in app order */
+ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */
+ struct UT_hash_handle *hh_next; /* next hh in bucket order */
+ const void *key; /* ptr to enclosing struct's key */
+ unsigned keylen; /* enclosing struct's key len */
+ unsigned hashv; /* result of hash-fcn(key) */
+} UT_hash_handle;
+
+#endif /* UTHASH_H */
diff --git a/glib/utlist.h b/glib/utlist.h
new file mode 100644
index 000000000..1979448a7
--- /dev/null
+++ b/glib/utlist.h
@@ -0,0 +1,1073 @@
+/*
+Copyright (c) 2007-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTLIST_H
+#define UTLIST_H
+
+#define UTLIST_VERSION 2.3.0
+
+#include <assert.h>
+
+/*
+ * This file contains macros to manipulate singly and doubly-linked lists.
+ *
+ * 1. LL_ macros: singly-linked lists.
+ * 2. DL_ macros: doubly-linked lists.
+ * 3. CDL_ macros: circular doubly-linked lists.
+ *
+ * To use singly-linked lists, your structure must have a "next" pointer.
+ * To use doubly-linked lists, your structure must "prev" and "next" pointers.
+ * Either way, the pointer to the head of the list must be initialized to NULL.
+ *
+ * ----------------.EXAMPLE -------------------------
+ * struct item {
+ * int id;
+ * struct item *prev, *next;
+ * }
+ *
+ * struct item *list = NULL:
+ *
+ * int main() {
+ * struct item *item;
+ * ... allocate and populate item ...
+ * DL_APPEND(list, item);
+ * }
+ * --------------------------------------------------
+ *
+ * For doubly-linked lists, the append and delete macros are O(1)
+ * For singly-linked lists, append and delete are O(n) but prepend is O(1)
+ * The sort macro is O(n log(n)) for all types of single/double/circular lists.
+ */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+ As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+ when compiling c++ source) this code uses whatever method is needed
+ or, for VS2008 where neither is available, uses casting workarounds. */
+#if !defined(LDECLTYPE) && !defined(NO_DECLTYPE)
+#if defined(_MSC_VER) /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */
+#define LDECLTYPE(x) decltype(x)
+#else /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#endif
+#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#else /* GNU, Sun and other compilers */
+#define LDECLTYPE(x) __typeof(x)
+#endif
+#endif
+
+/* for VS2008 we use some workarounds to get around the lack of decltype,
+ * namely, we always reassign our tmp variable to the list head if we need
+ * to dereference its prev/next pointers, and save/restore the real head.*/
+#ifdef NO_DECLTYPE
+#define IF_NO_DECLTYPE(x) x
+#define LDECLTYPE(x) char*
+#define UTLIST_SV(elt,list) _tmp = (char*)(list); {char **_alias = (char**)&(list); *_alias = (elt); }
+#define UTLIST_NEXT(elt,list,next) ((char*)((list)->next))
+#define UTLIST_NEXTASGN(elt,list,to,next) { char **_alias = (char**)&((list)->next); *_alias=(char*)(to); }
+/* #define UTLIST_PREV(elt,list,prev) ((char*)((list)->prev)) */
+#define UTLIST_PREVASGN(elt,list,to,prev) { char **_alias = (char**)&((list)->prev); *_alias=(char*)(to); }
+#define UTLIST_RS(list) { char **_alias = (char**)&(list); *_alias=_tmp; }
+#define UTLIST_CASTASGN(a,b) { char **_alias = (char**)&(a); *_alias=(char*)(b); }
+#else
+#define IF_NO_DECLTYPE(x)
+#define UTLIST_SV(elt,list)
+#define UTLIST_NEXT(elt,list,next) ((elt)->next)
+#define UTLIST_NEXTASGN(elt,list,to,next) ((elt)->next)=(to)
+/* #define UTLIST_PREV(elt,list,prev) ((elt)->prev) */
+#define UTLIST_PREVASGN(elt,list,to,prev) ((elt)->prev)=(to)
+#define UTLIST_RS(list)
+#define UTLIST_CASTASGN(a,b) (a)=(b)
+#endif
+
+/******************************************************************************
+ * The sort macro is an adaptation of Simon Tatham's O(n log(n)) mergesort *
+ * Unwieldy variable names used here to avoid shadowing passed-in variables. *
+ *****************************************************************************/
+#define LL_SORT(list, cmp) \
+ LL_SORT2(list, cmp, next)
+
+#define LL_SORT2(list, cmp, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;) \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ UTLIST_CASTASGN(_ls_p,list); \
+ (list) = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ } else { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ } \
+ if (_ls_tail) { \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+ } else { \
+ UTLIST_CASTASGN(list,_ls_e); \
+ } \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ if (_ls_tail) { \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list); \
+ } \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+
+#define DL_SORT(list, cmp) \
+ DL_SORT2(list, cmp, prev, next)
+
+#define DL_SORT2(list, cmp, prev, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;) \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ UTLIST_CASTASGN(_ls_p,list); \
+ (list) = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while ((_ls_psize > 0) || ((_ls_qsize > 0) && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ } else if ((_ls_qsize == 0) || (!_ls_q)) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ } else { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ } \
+ if (_ls_tail) { \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+ } else { \
+ UTLIST_CASTASGN(list,_ls_e); \
+ } \
+ UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list); \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ UTLIST_CASTASGN((list)->prev, _ls_tail); \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list); \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+#define CDL_SORT(list, cmp) \
+ CDL_SORT2(list, cmp, prev, next)
+
+#define CDL_SORT2(list, cmp, prev, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ LDECLTYPE(list) _ls_oldhead; \
+ LDECLTYPE(list) _tmp; \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ UTLIST_CASTASGN(_ls_p,list); \
+ UTLIST_CASTASGN(_ls_oldhead,list); \
+ (list) = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ UTLIST_SV(_ls_q,list); \
+ if (UTLIST_NEXT(_ls_q,list,next) == _ls_oldhead) { \
+ _ls_q = NULL; \
+ } else { \
+ _ls_q = UTLIST_NEXT(_ls_q,list,next); \
+ } \
+ UTLIST_RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \
+ } else { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \
+ } \
+ if (_ls_tail) { \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+ } else { \
+ UTLIST_CASTASGN(list,_ls_e); \
+ } \
+ UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list); \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ UTLIST_CASTASGN((list)->prev,_ls_tail); \
+ UTLIST_CASTASGN(_tmp,list); \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_tmp,next); UTLIST_RS(list); \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+/******************************************************************************
+ * singly linked list macros (non-circular) *
+ *****************************************************************************/
+#define LL_PREPEND(head,add) \
+ LL_PREPEND2(head,add,next)
+
+#define LL_PREPEND2(head,add,next) \
+do { \
+ (add)->next = (head); \
+ (head) = (add); \
+} while (0)
+
+#define LL_CONCAT(head1,head2) \
+ LL_CONCAT2(head1,head2,next)
+
+#define LL_CONCAT2(head1,head2,next) \
+do { \
+ LDECLTYPE(head1) _tmp; \
+ if (head1) { \
+ _tmp = (head1); \
+ while (_tmp->next) { _tmp = _tmp->next; } \
+ _tmp->next=(head2); \
+ } else { \
+ (head1)=(head2); \
+ } \
+} while (0)
+
+#define LL_APPEND(head,add) \
+ LL_APPEND2(head,add,next)
+
+#define LL_APPEND2(head,add,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ (add)->next=NULL; \
+ if (head) { \
+ _tmp = (head); \
+ while (_tmp->next) { _tmp = _tmp->next; } \
+ _tmp->next=(add); \
+ } else { \
+ (head)=(add); \
+ } \
+} while (0)
+
+#define LL_INSERT_INORDER(head,add,cmp) \
+ LL_INSERT_INORDER2(head,add,cmp,next)
+
+#define LL_INSERT_INORDER2(head,add,cmp,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if (head) { \
+ LL_LOWER_BOUND2(head, _tmp, add, cmp, next); \
+ LL_APPEND_ELEM2(head, _tmp, add, next); \
+ } else { \
+ (head) = (add); \
+ (head)->next = NULL; \
+ } \
+} while (0)
+
+#define LL_LOWER_BOUND(head,elt,like,cmp) \
+ LL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define LL_LOWER_BOUND2(head,elt,like,cmp,next) \
+ do { \
+ if ((head) == NULL || (cmp(head, like)) >= 0) { \
+ (elt) = NULL; \
+ } else { \
+ for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) { \
+ if (cmp((elt)->next, like) >= 0) { \
+ break; \
+ } \
+ } \
+ } \
+ } while (0)
+
+#define LL_DELETE(head,del) \
+ LL_DELETE2(head,del,next)
+
+#define LL_DELETE2(head,del,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if ((head) == (del)) { \
+ (head)=(head)->next; \
+ } else { \
+ _tmp = (head); \
+ while (_tmp->next && (_tmp->next != (del))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (del)->next; \
+ } \
+ } \
+} while (0)
+
+#define LL_COUNT(head,el,counter) \
+ LL_COUNT2(head,el,counter,next) \
+
+#define LL_COUNT2(head,el,counter,next) \
+do { \
+ (counter) = 0; \
+ LL_FOREACH2(head,el,next) { ++(counter); } \
+} while (0)
+
+#define LL_FOREACH(head,el) \
+ LL_FOREACH2(head,el,next)
+
+#define LL_FOREACH2(head,el,next) \
+ for ((el) = (head); el; (el) = (el)->next)
+
+#define LL_FOREACH_SAFE(head,el,tmp) \
+ LL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define LL_FOREACH_SAFE2(head,el,tmp,next) \
+ for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp))
+
+#define LL_SEARCH_SCALAR(head,out,field,val) \
+ LL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define LL_SEARCH_SCALAR2(head,out,field,val,next) \
+do { \
+ LL_FOREACH2(head,out,next) { \
+ if ((out)->field == (val)) break; \
+ } \
+} while (0)
+
+#define LL_SEARCH(head,out,elt,cmp) \
+ LL_SEARCH2(head,out,elt,cmp,next)
+
+#define LL_SEARCH2(head,out,elt,cmp,next) \
+do { \
+ LL_FOREACH2(head,out,next) { \
+ if ((cmp(out,elt))==0) break; \
+ } \
+} while (0)
+
+#define LL_REPLACE_ELEM2(head, el, add, next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ assert((head) != NULL); \
+ assert((el) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el)->next; \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ _tmp = (head); \
+ while (_tmp->next && (_tmp->next != (el))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (add); \
+ } \
+ } \
+} while (0)
+
+#define LL_REPLACE_ELEM(head, el, add) \
+ LL_REPLACE_ELEM2(head, el, add, next)
+
+#define LL_PREPEND_ELEM2(head, el, add, next) \
+do { \
+ if (el) { \
+ LDECLTYPE(head) _tmp; \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ _tmp = (head); \
+ while (_tmp->next && (_tmp->next != (el))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (add); \
+ } \
+ } \
+ } else { \
+ LL_APPEND2(head, add, next); \
+ } \
+} while (0) \
+
+#define LL_PREPEND_ELEM(head, el, add) \
+ LL_PREPEND_ELEM2(head, el, add, next)
+
+#define LL_APPEND_ELEM2(head, el, add, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el)->next; \
+ (el)->next = (add); \
+ } else { \
+ LL_PREPEND2(head, add, next); \
+ } \
+} while (0) \
+
+#define LL_APPEND_ELEM(head, el, add) \
+ LL_APPEND_ELEM2(head, el, add, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef LL_CONCAT2
+#define LL_CONCAT2(head1,head2,next) \
+do { \
+ char *_tmp; \
+ if (head1) { \
+ _tmp = (char*)(head1); \
+ while ((head1)->next) { (head1) = (head1)->next; } \
+ (head1)->next = (head2); \
+ UTLIST_RS(head1); \
+ } else { \
+ (head1)=(head2); \
+ } \
+} while (0)
+
+#undef LL_APPEND2
+#define LL_APPEND2(head,add,next) \
+do { \
+ if (head) { \
+ (add)->next = head; /* use add->next as a temp variable */ \
+ while ((add)->next->next) { (add)->next = (add)->next->next; } \
+ (add)->next->next=(add); \
+ } else { \
+ (head)=(add); \
+ } \
+ (add)->next=NULL; \
+} while (0)
+
+#undef LL_INSERT_INORDER2
+#define LL_INSERT_INORDER2(head,add,cmp,next) \
+do { \
+ if ((head) == NULL || (cmp(head, add)) >= 0) { \
+ (add)->next = (head); \
+ (head) = (add); \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((head)->next != NULL && (cmp((head)->next, add)) < 0) { \
+ (head) = (head)->next; \
+ } \
+ (add)->next = (head)->next; \
+ (head)->next = (add); \
+ UTLIST_RS(head); \
+ } \
+} while (0)
+
+#undef LL_DELETE2
+#define LL_DELETE2(head,del,next) \
+do { \
+ if ((head) == (del)) { \
+ (head)=(head)->next; \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((head)->next && ((head)->next != (del))) { \
+ (head) = (head)->next; \
+ } \
+ if ((head)->next) { \
+ (head)->next = ((del)->next); \
+ } \
+ UTLIST_RS(head); \
+ } \
+} while (0)
+
+#undef LL_REPLACE_ELEM2
+#define LL_REPLACE_ELEM2(head, el, add, next) \
+do { \
+ assert((head) != NULL); \
+ assert((el) != NULL); \
+ assert((add) != NULL); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ (add)->next = head; \
+ while ((add)->next->next && ((add)->next->next != (el))) { \
+ (add)->next = (add)->next->next; \
+ } \
+ if ((add)->next->next) { \
+ (add)->next->next = (add); \
+ } \
+ } \
+ (add)->next = (el)->next; \
+} while (0)
+
+#undef LL_PREPEND_ELEM2
+#define LL_PREPEND_ELEM2(head, el, add, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ (add)->next = (head); \
+ while ((add)->next->next && ((add)->next->next != (el))) { \
+ (add)->next = (add)->next->next; \
+ } \
+ if ((add)->next->next) { \
+ (add)->next->next = (add); \
+ } \
+ } \
+ (add)->next = (el); \
+ } else { \
+ LL_APPEND2(head, add, next); \
+ } \
+} while (0) \
+
+#endif /* NO_DECLTYPE */
+
+/******************************************************************************
+ * doubly linked list macros (non-circular) *
+ *****************************************************************************/
+#define DL_PREPEND(head,add) \
+ DL_PREPEND2(head,add,prev,next)
+
+#define DL_PREPEND2(head,add,prev,next) \
+do { \
+ (add)->next = (head); \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (head)->prev = (add); \
+ } else { \
+ (add)->prev = (add); \
+ } \
+ (head) = (add); \
+} while (0)
+
+#define DL_APPEND(head,add) \
+ DL_APPEND2(head,add,prev,next)
+
+#define DL_APPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (head)->prev->next = (add); \
+ (head)->prev = (add); \
+ (add)->next = NULL; \
+ } else { \
+ (head)=(add); \
+ (head)->prev = (head); \
+ (head)->next = NULL; \
+ } \
+} while (0)
+
+#define DL_INSERT_INORDER(head,add,cmp) \
+ DL_INSERT_INORDER2(head,add,cmp,prev,next)
+
+#define DL_INSERT_INORDER2(head,add,cmp,prev,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if (head) { \
+ DL_LOWER_BOUND2(head, _tmp, add, cmp, next); \
+ DL_APPEND_ELEM2(head, _tmp, add, prev, next); \
+ } else { \
+ (head) = (add); \
+ (head)->prev = (head); \
+ (head)->next = NULL; \
+ } \
+} while (0)
+
+#define DL_LOWER_BOUND(head,elt,like,cmp) \
+ DL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define DL_LOWER_BOUND2(head,elt,like,cmp,next) \
+do { \
+ if ((head) == NULL || (cmp(head, like)) >= 0) { \
+ (elt) = NULL; \
+ } else { \
+ for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) { \
+ if ((cmp((elt)->next, like)) >= 0) { \
+ break; \
+ } \
+ } \
+ } \
+} while (0)
+
+#define DL_CONCAT(head1,head2) \
+ DL_CONCAT2(head1,head2,prev,next)
+
+#define DL_CONCAT2(head1,head2,prev,next) \
+do { \
+ LDECLTYPE(head1) _tmp; \
+ if (head2) { \
+ if (head1) { \
+ UTLIST_CASTASGN(_tmp, (head2)->prev); \
+ (head2)->prev = (head1)->prev; \
+ (head1)->prev->next = (head2); \
+ UTLIST_CASTASGN((head1)->prev, _tmp); \
+ } else { \
+ (head1)=(head2); \
+ } \
+ } \
+} while (0)
+
+#define DL_DELETE(head,del) \
+ DL_DELETE2(head,del,prev,next)
+
+#define DL_DELETE2(head,del,prev,next) \
+do { \
+ assert((head) != NULL); \
+ assert((del)->prev != NULL); \
+ if ((del)->prev == (del)) { \
+ (head)=NULL; \
+ } else if ((del)==(head)) { \
+ (del)->next->prev = (del)->prev; \
+ (head) = (del)->next; \
+ } else { \
+ (del)->prev->next = (del)->next; \
+ if ((del)->next) { \
+ (del)->next->prev = (del)->prev; \
+ } else { \
+ (head)->prev = (del)->prev; \
+ } \
+ } \
+} while (0)
+
+#define DL_COUNT(head,el,counter) \
+ DL_COUNT2(head,el,counter,next) \
+
+#define DL_COUNT2(head,el,counter,next) \
+do { \
+ (counter) = 0; \
+ DL_FOREACH2(head,el,next) { ++(counter); } \
+} while (0)
+
+#define DL_FOREACH(head,el) \
+ DL_FOREACH2(head,el,next)
+
+#define DL_FOREACH2(head,el,next) \
+ for ((el) = (head); el; (el) = (el)->next)
+
+/* this version is safe for deleting the elements during iteration */
+#define DL_FOREACH_SAFE(head,el,tmp) \
+ DL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define DL_FOREACH_SAFE2(head,el,tmp,next) \
+ for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp))
+
+/* these are identical to their singly-linked list counterparts */
+#define DL_SEARCH_SCALAR LL_SEARCH_SCALAR
+#define DL_SEARCH LL_SEARCH
+#define DL_SEARCH_SCALAR2 LL_SEARCH_SCALAR2
+#define DL_SEARCH2 LL_SEARCH2
+
+#define DL_REPLACE_ELEM2(head, el, add, prev, next) \
+do { \
+ assert((head) != NULL); \
+ assert((el) != NULL); \
+ assert((add) != NULL); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ (add)->next = (el)->next; \
+ if ((el)->next == NULL) { \
+ (add)->prev = (add); \
+ } else { \
+ (add)->prev = (el)->prev; \
+ (add)->next->prev = (add); \
+ } \
+ } else { \
+ (add)->next = (el)->next; \
+ (add)->prev = (el)->prev; \
+ (add)->prev->next = (add); \
+ if ((el)->next == NULL) { \
+ (head)->prev = (add); \
+ } else { \
+ (add)->next->prev = (add); \
+ } \
+ } \
+} while (0)
+
+#define DL_REPLACE_ELEM(head, el, add) \
+ DL_REPLACE_ELEM2(head, el, add, prev, next)
+
+#define DL_PREPEND_ELEM2(head, el, add, prev, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el); \
+ (add)->prev = (el)->prev; \
+ (el)->prev = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ (add)->prev->next = (add); \
+ } \
+ } else { \
+ DL_APPEND2(head, add, prev, next); \
+ } \
+} while (0) \
+
+#define DL_PREPEND_ELEM(head, el, add) \
+ DL_PREPEND_ELEM2(head, el, add, prev, next)
+
+#define DL_APPEND_ELEM2(head, el, add, prev, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el)->next; \
+ (add)->prev = (el); \
+ (el)->next = (add); \
+ if ((add)->next) { \
+ (add)->next->prev = (add); \
+ } else { \
+ (head)->prev = (add); \
+ } \
+ } else { \
+ DL_PREPEND2(head, add, prev, next); \
+ } \
+} while (0) \
+
+#define DL_APPEND_ELEM(head, el, add) \
+ DL_APPEND_ELEM2(head, el, add, prev, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef DL_INSERT_INORDER2
+#define DL_INSERT_INORDER2(head,add,cmp,prev,next) \
+do { \
+ if ((head) == NULL) { \
+ (add)->prev = (add); \
+ (add)->next = NULL; \
+ (head) = (add); \
+ } else if ((cmp(head, add)) >= 0) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (head)->prev = (add); \
+ (head) = (add); \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((head)->next && (cmp((head)->next, add)) < 0) { \
+ (head) = (head)->next; \
+ } \
+ (add)->prev = (head); \
+ (add)->next = (head)->next; \
+ (head)->next = (add); \
+ UTLIST_RS(head); \
+ if ((add)->next) { \
+ (add)->next->prev = (add); \
+ } else { \
+ (head)->prev = (add); \
+ } \
+ } \
+} while (0)
+#endif /* NO_DECLTYPE */
+
+/******************************************************************************
+ * circular doubly linked list macros *
+ *****************************************************************************/
+#define CDL_APPEND(head,add) \
+ CDL_APPEND2(head,add,prev,next)
+
+#define CDL_APPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (head)->prev = (add); \
+ (add)->prev->next = (add); \
+ } else { \
+ (add)->prev = (add); \
+ (add)->next = (add); \
+ (head) = (add); \
+ } \
+} while (0)
+
+#define CDL_PREPEND(head,add) \
+ CDL_PREPEND2(head,add,prev,next)
+
+#define CDL_PREPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (head)->prev = (add); \
+ (add)->prev->next = (add); \
+ } else { \
+ (add)->prev = (add); \
+ (add)->next = (add); \
+ } \
+ (head) = (add); \
+} while (0)
+
+#define CDL_INSERT_INORDER(head,add,cmp) \
+ CDL_INSERT_INORDER2(head,add,cmp,prev,next)
+
+#define CDL_INSERT_INORDER2(head,add,cmp,prev,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if (head) { \
+ CDL_LOWER_BOUND2(head, _tmp, add, cmp, next); \
+ CDL_APPEND_ELEM2(head, _tmp, add, prev, next); \
+ } else { \
+ (head) = (add); \
+ (head)->next = (head); \
+ (head)->prev = (head); \
+ } \
+} while (0)
+
+#define CDL_LOWER_BOUND(head,elt,like,cmp) \
+ CDL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define CDL_LOWER_BOUND2(head,elt,like,cmp,next) \
+do { \
+ if ((head) == NULL || (cmp(head, like)) >= 0) { \
+ (elt) = NULL; \
+ } else { \
+ for ((elt) = (head); (elt)->next != (head); (elt) = (elt)->next) { \
+ if ((cmp((elt)->next, like)) >= 0) { \
+ break; \
+ } \
+ } \
+ } \
+} while (0)
+
+#define CDL_DELETE(head,del) \
+ CDL_DELETE2(head,del,prev,next)
+
+#define CDL_DELETE2(head,del,prev,next) \
+do { \
+ if (((head)==(del)) && ((head)->next == (head))) { \
+ (head) = NULL; \
+ } else { \
+ (del)->next->prev = (del)->prev; \
+ (del)->prev->next = (del)->next; \
+ if ((del) == (head)) (head)=(del)->next; \
+ } \
+} while (0)
+
+#define CDL_COUNT(head,el,counter) \
+ CDL_COUNT2(head,el,counter,next) \
+
+#define CDL_COUNT2(head, el, counter,next) \
+do { \
+ (counter) = 0; \
+ CDL_FOREACH2(head,el,next) { ++(counter); } \
+} while (0)
+
+#define CDL_FOREACH(head,el) \
+ CDL_FOREACH2(head,el,next)
+
+#define CDL_FOREACH2(head,el,next) \
+ for ((el)=(head);el;(el)=(((el)->next==(head)) ? NULL : (el)->next))
+
+#define CDL_FOREACH_SAFE(head,el,tmp1,tmp2) \
+ CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next)
+
+#define CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next) \
+ for ((el) = (head), (tmp1) = (head) ? (head)->prev : NULL; \
+ (el) && ((tmp2) = (el)->next, 1); \
+ (el) = ((el) == (tmp1) ? NULL : (tmp2)))
+
+#define CDL_SEARCH_SCALAR(head,out,field,val) \
+ CDL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define CDL_SEARCH_SCALAR2(head,out,field,val,next) \
+do { \
+ CDL_FOREACH2(head,out,next) { \
+ if ((out)->field == (val)) break; \
+ } \
+} while (0)
+
+#define CDL_SEARCH(head,out,elt,cmp) \
+ CDL_SEARCH2(head,out,elt,cmp,next)
+
+#define CDL_SEARCH2(head,out,elt,cmp,next) \
+do { \
+ CDL_FOREACH2(head,out,next) { \
+ if ((cmp(out,elt))==0) break; \
+ } \
+} while (0)
+
+#define CDL_REPLACE_ELEM2(head, el, add, prev, next) \
+do { \
+ assert((head) != NULL); \
+ assert((el) != NULL); \
+ assert((add) != NULL); \
+ if ((el)->next == (el)) { \
+ (add)->next = (add); \
+ (add)->prev = (add); \
+ (head) = (add); \
+ } else { \
+ (add)->next = (el)->next; \
+ (add)->prev = (el)->prev; \
+ (add)->next->prev = (add); \
+ (add)->prev->next = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } \
+ } \
+} while (0)
+
+#define CDL_REPLACE_ELEM(head, el, add) \
+ CDL_REPLACE_ELEM2(head, el, add, prev, next)
+
+#define CDL_PREPEND_ELEM2(head, el, add, prev, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el); \
+ (add)->prev = (el)->prev; \
+ (el)->prev = (add); \
+ (add)->prev->next = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } \
+ } else { \
+ CDL_APPEND2(head, add, prev, next); \
+ } \
+} while (0)
+
+#define CDL_PREPEND_ELEM(head, el, add) \
+ CDL_PREPEND_ELEM2(head, el, add, prev, next)
+
+#define CDL_APPEND_ELEM2(head, el, add, prev, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el)->next; \
+ (add)->prev = (el); \
+ (el)->next = (add); \
+ (add)->next->prev = (add); \
+ } else { \
+ CDL_PREPEND2(head, add, prev, next); \
+ } \
+} while (0)
+
+#define CDL_APPEND_ELEM(head, el, add) \
+ CDL_APPEND_ELEM2(head, el, add, prev, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef CDL_INSERT_INORDER2
+#define CDL_INSERT_INORDER2(head,add,cmp,prev,next) \
+do { \
+ if ((head) == NULL) { \
+ (add)->prev = (add); \
+ (add)->next = (add); \
+ (head) = (add); \
+ } else if ((cmp(head, add)) >= 0) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (add)->prev->next = (add); \
+ (head)->prev = (add); \
+ (head) = (add); \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((char*)(head)->next != _tmp && (cmp((head)->next, add)) < 0) { \
+ (head) = (head)->next; \
+ } \
+ (add)->prev = (head); \
+ (add)->next = (head)->next; \
+ (add)->next->prev = (add); \
+ (head)->next = (add); \
+ UTLIST_RS(head); \
+ } \
+} while (0)
+#endif /* NO_DECLTYPE */
+
+#endif /* UTLIST_H */
diff --git a/glib/utstring.h b/glib/utstring.h
new file mode 100644
index 000000000..be8904949
--- /dev/null
+++ b/glib/utstring.h
@@ -0,0 +1,411 @@
+/*
+Copyright (c) 2008-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* a dynamic string implementation using macros
+ */
+#ifndef UTSTRING_H
+#define UTSTRING_H
+
+#define UTSTRING_VERSION 2.3.0
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#ifdef __GNUC__
+#define UTSTRING_UNUSED __attribute__((__unused__))
+#else
+#define UTSTRING_UNUSED
+#endif
+
+#ifdef oom
+#error "The name of macro 'oom' has been changed to 'utstring_oom'. Please update your code."
+#define utstring_oom() oom()
+#endif
+
+#ifndef utstring_oom
+#define utstring_oom() exit(-1)
+#endif
+
+extern void *__libc_malloc (size_t size);
+extern void *__libc_realloc (void *ptr, size_t size);
+extern void __libc_free (void *ptr);
+
+typedef struct {
+ char *d; /* pointer to allocated buffer */
+ size_t n; /* allocated capacity */
+ size_t i; /* index of first unused byte */
+} UT_string;
+
+#define utstring_reserve(s,amt) \
+do { \
+ if (((s)->n - (s)->i) < (size_t)(amt)) { \
+ char *utstring_tmp = (char*)__libc_realloc( \
+ (s)->d, (s)->n + (amt)); \
+ if (!utstring_tmp) { \
+ utstring_oom(); \
+ } \
+ (s)->d = utstring_tmp; \
+ (s)->n += (amt); \
+ } \
+} while(0)
+
+#define utstring_init(s) \
+do { \
+ (s)->n = 0; (s)->i = 0; (s)->d = NULL; \
+ utstring_reserve(s,100); \
+ (s)->d[0] = '\0'; \
+} while(0)
+
+#define utstring_done(s) \
+do { \
+ if ((s)->d != NULL) __libc_free((s)->d); \
+ (s)->n = 0; \
+} while(0)
+
+#define utstring_free(s) \
+do { \
+ utstring_done(s); \
+ __libc_free(s); \
+} while(0)
+
+#define utstring_new(s) \
+do { \
+ (s) = (UT_string*)__libc_malloc(sizeof(UT_string)); \
+ if (!(s)) { \
+ utstring_oom(); \
+ } \
+ utstring_init(s); \
+} while(0)
+
+#define utstring_renew(s) \
+do { \
+ if (s) { \
+ utstring_clear(s); \
+ } else { \
+ utstring_new(s); \
+ } \
+} while(0)
+
+#define utstring_clear(s) \
+do { \
+ (s)->i = 0; \
+ (s)->d[0] = '\0'; \
+} while(0)
+
+#define utstring_bincpy(s,b,l) \
+do { \
+ utstring_reserve((s),(l)+1); \
+ if (l) memcpy(&(s)->d[(s)->i], b, l); \
+ (s)->i += (l); \
+ (s)->d[(s)->i]='\0'; \
+} while(0)
+
+#define utstring_concat(dst,src) \
+do { \
+ utstring_reserve((dst),((src)->i)+1); \
+ if ((src)->i) memcpy(&(dst)->d[(dst)->i], (src)->d, (src)->i); \
+ (dst)->i += (src)->i; \
+ (dst)->d[(dst)->i]='\0'; \
+} while(0)
+
+#define utstring_len(s) ((s)->i)
+
+#define utstring_body(s) ((s)->d)
+
+UTSTRING_UNUSED static void utstring_printf_va(UT_string *s, const char *fmt, va_list ap) {
+ int n;
+ va_list cp;
+ for (;;) {
+#ifdef _WIN32
+ cp = ap;
+#else
+ va_copy(cp, ap);
+#endif
+ n = vsnprintf (&s->d[s->i], s->n-s->i, fmt, cp);
+ va_end(cp);
+
+ if ((n > -1) && ((size_t) n < (s->n-s->i))) {
+ s->i += n;
+ return;
+ }
+
+ /* Else try again with more space. */
+ if (n > -1) utstring_reserve(s,n+1); /* exact */
+ else utstring_reserve(s,(s->n)*2); /* 2x */
+ }
+}
+#ifdef __GNUC__
+/* support printf format checking (2=the format string, 3=start of varargs) */
+static void utstring_printf(UT_string *s, const char *fmt, ...)
+ __attribute__ (( format( printf, 2, 3) ));
+#endif
+UTSTRING_UNUSED static void utstring_printf(UT_string *s, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap,fmt);
+ utstring_printf_va(s,fmt,ap);
+ va_end(ap);
+}
+
+/*******************************************************************************
+ * begin substring search functions *
+ ******************************************************************************/
+/* Build KMP table from left to right. */
+UTSTRING_UNUSED static void _utstring_BuildTable(
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+
+ i = 0;
+ j = i - 1;
+ P_KMP_Table[i] = j;
+ while (i < (long) P_NeedleLen)
+ {
+ while ( (j > -1) && (P_Needle[i] != P_Needle[j]) )
+ {
+ j = P_KMP_Table[j];
+ }
+ i++;
+ j++;
+ if (i < (long) P_NeedleLen)
+ {
+ if (P_Needle[i] == P_Needle[j])
+ {
+ P_KMP_Table[i] = P_KMP_Table[j];
+ }
+ else
+ {
+ P_KMP_Table[i] = j;
+ }
+ }
+ else
+ {
+ P_KMP_Table[i] = j;
+ }
+ }
+
+ return;
+}
+
+
+/* Build KMP table from right to left. */
+UTSTRING_UNUSED static void _utstring_BuildTableR(
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+
+ i = P_NeedleLen - 1;
+ j = i + 1;
+ P_KMP_Table[i + 1] = j;
+ while (i >= 0)
+ {
+ while ( (j < (long) P_NeedleLen) && (P_Needle[i] != P_Needle[j]) )
+ {
+ j = P_KMP_Table[j + 1];
+ }
+ i--;
+ j--;
+ if (i >= 0)
+ {
+ if (P_Needle[i] == P_Needle[j])
+ {
+ P_KMP_Table[i + 1] = P_KMP_Table[j + 1];
+ }
+ else
+ {
+ P_KMP_Table[i + 1] = j;
+ }
+ }
+ else
+ {
+ P_KMP_Table[i + 1] = j;
+ }
+ }
+
+ return;
+}
+
+
+/* Search data from left to right. ( Multiple search mode. ) */
+UTSTRING_UNUSED static long _utstring_find(
+ const char *P_Haystack,
+ size_t P_HaystackLen,
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+ long V_FindPosition = -1;
+
+ /* Search from left to right. */
+ i = j = 0;
+ while ( (j < (int)P_HaystackLen) && (((P_HaystackLen - j) + i) >= P_NeedleLen) )
+ {
+ while ( (i > -1) && (P_Needle[i] != P_Haystack[j]) )
+ {
+ i = P_KMP_Table[i];
+ }
+ i++;
+ j++;
+ if (i >= (int)P_NeedleLen)
+ {
+ /* Found. */
+ V_FindPosition = j - i;
+ break;
+ }
+ }
+
+ return V_FindPosition;
+}
+
+
+/* Search data from right to left. ( Multiple search mode. ) */
+UTSTRING_UNUSED static long _utstring_findR(
+ const char *P_Haystack,
+ size_t P_HaystackLen,
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+ long V_FindPosition = -1;
+
+ /* Search from right to left. */
+ j = (P_HaystackLen - 1);
+ i = (P_NeedleLen - 1);
+ while ( (j >= 0) && (j >= i) )
+ {
+ while ( (i < (int)P_NeedleLen) && (P_Needle[i] != P_Haystack[j]) )
+ {
+ i = P_KMP_Table[i + 1];
+ }
+ i--;
+ j--;
+ if (i < 0)
+ {
+ /* Found. */
+ V_FindPosition = j + 1;
+ break;
+ }
+ }
+
+ return V_FindPosition;
+}
+
+
+/* Search data from left to right. ( One time search mode. ) */
+UTSTRING_UNUSED static long utstring_find(
+ UT_string *s,
+ long P_StartPosition, /* Start from 0. -1 means last position. */
+ const char *P_Needle,
+ size_t P_NeedleLen)
+{
+ long V_StartPosition;
+ long V_HaystackLen;
+ long *V_KMP_Table;
+ long V_FindPosition = -1;
+
+ if (P_StartPosition < 0)
+ {
+ V_StartPosition = s->i + P_StartPosition;
+ }
+ else
+ {
+ V_StartPosition = P_StartPosition;
+ }
+ V_HaystackLen = s->i - V_StartPosition;
+ if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
+ {
+ V_KMP_Table = (long *)__libc_malloc(sizeof(long) * (P_NeedleLen + 1));
+ if (V_KMP_Table != NULL)
+ {
+ _utstring_BuildTable(P_Needle, P_NeedleLen, V_KMP_Table);
+
+ V_FindPosition = _utstring_find(s->d + V_StartPosition,
+ V_HaystackLen,
+ P_Needle,
+ P_NeedleLen,
+ V_KMP_Table);
+ if (V_FindPosition >= 0)
+ {
+ V_FindPosition += V_StartPosition;
+ }
+
+ __libc_free(V_KMP_Table);
+ }
+ }
+
+ return V_FindPosition;
+}
+
+
+/* Search data from right to left. ( One time search mode. ) */
+UTSTRING_UNUSED static long utstring_findR(
+ UT_string *s,
+ long P_StartPosition, /* Start from 0. -1 means last position. */
+ const char *P_Needle,
+ size_t P_NeedleLen)
+{
+ long V_StartPosition;
+ long V_HaystackLen;
+ long *V_KMP_Table;
+ long V_FindPosition = -1;
+
+ if (P_StartPosition < 0)
+ {
+ V_StartPosition = s->i + P_StartPosition;
+ }
+ else
+ {
+ V_StartPosition = P_StartPosition;
+ }
+ V_HaystackLen = V_StartPosition + 1;
+ if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
+ {
+ V_KMP_Table = (long *)__libc_malloc(sizeof(long) * (P_NeedleLen + 1));
+ if (V_KMP_Table != NULL)
+ {
+ _utstring_BuildTableR(P_Needle, P_NeedleLen, V_KMP_Table);
+
+ V_FindPosition = _utstring_findR(s->d,
+ V_HaystackLen,
+ P_Needle,
+ P_NeedleLen,
+ V_KMP_Table);
+
+ __libc_free(V_KMP_Table);
+ }
+ }
+
+ return V_FindPosition;
+}
+/*******************************************************************************
+ * end substring search functions *
+ ******************************************************************************/
+
+#endif /* UTSTRING_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]