Merge branch 'master' into feature/stats

Conflicts:
	config.h
	configure.ac
	tests/bench_str.csv
This commit is contained in:
c9s 2014-05-22 16:41:46 +08:00
commit 595dadba15
20 changed files with 787 additions and 144 deletions

1
.gitignore vendored
View file

@ -40,6 +40,7 @@ m4/
missing
test-driver
test-suite.log
config.h
tests/*.log
tests/*.trs

View file

@ -6,20 +6,27 @@ compiler:
matrix:
include:
- compiler: gcc
env: CONFIGURE_OPTION='--enable-debug' COVERALLS=yes
env: CONFIGURE_OPTION='--enable-debug --with-malloc=jemalloc' COVERALLS=yes VALGRIND=no
- compiler: gcc
env: CONFIGURE_OPTION='--enable-debug --with-malloc=jemalloc' COVERALLS=yes VALGRIND=yes LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/
install:
- sudo apt-get update -qq
- sudo apt-get install -qq automake pkg-config build-essential libtool automake autoconf m4 gnulib
- sudo apt-get install -qq check libpcre3 libpcre3-dev libjemalloc-dev libjemalloc1
- sudo apt-get install -qq graphviz-dev graphviz
- if [ x$COVERALLS == xyes ]; then sudo pip install cpp-coveralls --use-mirror; fi
- if [ "x$COVERALLS" == xyes ]; then sudo pip install cpp-coveralls; fi
- if [ "x$VALGRIND" == xyes ]; then sudo apt-get install valgrind; fi
before_script:
- sudo ldconfig
script:
- ./autogen.sh
- ./configure $CONFIGURE_OPTION
- make
- make check
- sudo make install
- if [ "x$VALGRIND" == xyes ]; then make check > /dev/null 2>&1; else make check; fi
- if [ "x$VALGRIND" == xyes ]; then valgrind ./tests/.libs/* -v --trace-children=yes --show-leak-kinds=full --leak-check=full; fi
after_success:
- if [ x$COVERALLS == xyes ]; then coveralls ; fi

View file

@ -5,8 +5,11 @@ ACLOCAL_AMFLAGS=-I m4
r3_includedir = $(includedir)/r3
r3_include_HEADERS = \
include/r3.h \
include/r3_define.h \
include/r3_list.h \
include/r3_str.h \
include/str_array.h \
include/zmalloc.h \
$(NULL)
pkgconfigdir = $(libdir)/pkgconfig

View file

@ -206,10 +206,13 @@ Install
make check # run tests
sudo make install
### Enable Graphviz
#### Enable Graphviz
./configure --enable-graphviz
#### With jemalloc
./configure --with-malloc=jemalloc
License

119
config.h Normal file
View file

@ -0,0 +1,119 @@
/* config.h. Generated from config.h.in by configure. */
/* config.h.in. Generated from configure.ac by autoheader. */
/* "whether graphviz is enable" */
#define ENABLE_GRAPHVIZ test "x$enable_graphviz" = "xyes"
/* "whether statistics is enable" */
#define ENABLE_STATS test "x$enable_stats" = "xyes"
/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1
/* Define to 1 if you have the `gettimeofday' function. */
#define HAVE_GETTIMEOFDAY 1
/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1
/* Define to 1 if you have the <jemalloc/jemalloc.h> header file. */
/* #undef HAVE_JEMALLOC_JEMALLOC_H */
/* Define to 1 if your system has a GNU libc compatible `malloc' function, and
to 0 otherwise. */
#define HAVE_MALLOC 1
/* Define to 1 if you have the <memory.h> header file. */
#define HAVE_MEMORY_H 1
/* Define to 1 if you have the `memset' function. */
#define HAVE_MEMSET 1
/* Define to 1 if your system has a GNU libc compatible `realloc' function,
and to 0 otherwise. */
#define HAVE_REALLOC 1
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the `strchr' function. */
#define HAVE_STRCHR 1
/* Define to 1 if you have the `strdup' function. */
#define HAVE_STRDUP 1
/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if you have the `strndup' function. */
#define HAVE_STRNDUP 1
/* Define to 1 if you have the `strstr' function. */
#define HAVE_STRSTR 1
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/time.h> header file. */
#define HAVE_SYS_TIME_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1
/* Define to the sub-directory in which libtool stores uninstalled libraries.
*/
#define LT_OBJDIR ".libs/"
/* Name of package */
#define PACKAGE "r3"
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT ""
/* Define to the full name of this package. */
#define PACKAGE_NAME "r3"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "r3 1.0.0"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "r3"
/* Define to the home page for this package. */
#define PACKAGE_URL ""
/* Define to the version of this package. */
#define PACKAGE_VERSION "1.0.0"
/* Define to 1 if you have the ANSI C header files. */
#define STDC_HEADERS 1
/* Define to 1 if you have the PATH_MAX macro. */
/* #undef USE_JEMALLOC */
/* Version number of package */
#define VERSION "1.0.0"
/* Define to `__inline__' or `__inline' if that's what the C compiler
calls it, or to nothing if 'inline' is not supported under any name. */
#ifndef __cplusplus
/* #undef inline */
#endif
/* Define to rpl_malloc if the replacement function should be used. */
/* #undef malloc */
/* Define to rpl_realloc if the replacement function should be used. */
/* #undef realloc */
/* Define to `unsigned int' if <sys/types.h> does not define. */
/* #undef size_t */

View file

@ -9,6 +9,14 @@ AC_PROG_CC_STDC
AC_CHECK_HEADERS([stdlib.h string.h sys/time.h stdint.h])
PKG_CHECK_MODULES(CHECK,[check >= 0.9.4],:,[
ifdef([AM_PATH_CHECK],
[AM_PATH_CHECK(,[have_check="yes"])],
AC_MSG_WARN([Check not found; cannot run unit tests!])
[have_check="no"])]
])
AM_CONDITIONAL(HAVE_CHECK, test x"$have_check" = "xyes")
# Checks for typedefs, structures, and compiler characteristics.
AC_C_INLINE
AC_TYPE_SIZE_T
@ -57,13 +65,9 @@ fi
AM_CONDITIONAL(USE_JEMALLOC, test "x$have_jemalloc" = "xyes")
AM_CONDITIONAL(ENABLE_GRAPHVIZ, test "x$enable_graphviz" = "xyes")
AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable debug]), [],[enable_debug=unset])
AM_CONDITIONAL(ENABLE_DEBUG, test "x$enable_debug" = "xyes")
AC_ARG_ENABLE(stats, AS_HELP_STRING([--enable-stats], [enable statistics]), [] ,[enable_stats=unset])
AC_DEFINE(ENABLE_STATS, test "x$enable_stats" = "xyes", "whether statistics is enable")
AC_ARG_ENABLE(graphviz,
AS_HELP_STRING([--enable-graphviz],
@ -74,10 +78,15 @@ if test "x$enable_graphviz" != "xunset" ; then
AC_SUBST(GVC_DEPS_CFLAGS)
AC_SUBST(GVC_DEPS_LIBS)
fi
# include flags
AM_CONDITIONAL(ENABLE_GRAPHVIZ, test "x$enable_graphviz" = "xyes")
AC_DEFINE(ENABLE_GRAPHVIZ, test "x$enable_graphviz" = "xyes", "whether graphviz is enable")
PKG_CHECK_MODULES([CHECK], [check >= 0.9.4])
AC_ARG_ENABLE(stats, AS_HELP_STRING([--enable-stats], [enable statistics]), [] ,[enable_stats=unset])
AC_DEFINE(ENABLE_STATS, test "x$enable_stats" = "xyes", "whether statistics is enable")
PKG_CHECK_MODULES(DEPS, [libpcre])
AC_SUBST(DEPS_CFLAGS)

86
include/zmalloc.h Normal file
View file

@ -0,0 +1,86 @@
#ifndef ZMALLOC_H
#define ZMALLOC_H
/* zmalloc - total amount of allocated memory aware version of malloc()
*
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
* 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.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* 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.
*/
/* Double expansion needed for stringification of macro values. */
#define __xstr(s) __str(s)
#define __str(s) #s
#if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif
#elif defined(USE_JEMALLOC) && (JEMALLOC_VERSION_MAJOR > 2)
#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
#include <jemalloc/jemalloc.h>
#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) je_malloc_usable_size(p)
#else
#error "Newer version of jemalloc required"
#endif
#elif defined(__APPLE__)
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_size(p)
#endif
#ifndef ZMALLOC_LIB
#define ZMALLOC_LIB "libc"
#endif
void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
char *zstrdup(const char *s);
char *zstrndup(const char *s, size_t n);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
float zmalloc_get_fragmentation_ratio(size_t rss);
size_t zmalloc_get_rss(void);
size_t zmalloc_get_private_dirty(void);
void zlibc_free(void *ptr);
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr);
#endif
#endif // ZMALLOC_H

View file

@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.8)
include_directories("${PROJECT_SOURCE_DIR}/include")
# install(TARGETS swiftnav-static DESTINATION lib${LIB_SUFFIX})
set(R3_SRCS node.c str.c list.c token.c edge.c)
set(R3_SRCS node.c str.c list.c token.c edge.c zmalloc.c)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -pipe -g3 -funroll-loops -O0")
# set(LIBS ${LIBS} ${PCRE_LIBRARIES} ${Judy_LIBRARIES} ${Jemalloc_LIBRARIES} r3)

View file

@ -1,11 +1,14 @@
lib_LTLIBRARIES = libr3.la
# lib_LIBRARIES = libr3.a
libr3_la_SOURCES = node.c edge.c str.c token.c
libr3_la_SOURCES = node.c edge.c str.c token.c zmalloc.c
# libr3_la_LDFLAGS = -export-symbols-regex '^r3_|^match_'
libr3_la_LIBADD=$(DEPS_LIBS)
AM_CFLAGS=$(DEPS_CFLAGS) -I$(top_builddir) -I$(top_builddir)/include -Wall
AM_CFLAGS=$(DEPS_CFLAGS) -I$(top_builddir) -I$(top_builddir)/include -Wall -std=c99
if USE_JEMALLOC
AM_CFLAGS += -ljemalloc
endif
if ENABLE_DEBUG
AM_CFLAGS += -ggdb -fprofile-arcs -ftest-coverage

View file

@ -22,9 +22,10 @@
#include "r3.h"
#include "r3_str.h"
#include "str_array.h"
#include "zmalloc.h"
edge * r3_edge_create(char * pattern, int pattern_len, node * child) {
edge * e = (edge*) malloc( sizeof(edge) );
edge * e = (edge*) zmalloc( sizeof(edge) );
e->pattern = pattern;
e->pattern_len = pattern_len;
e->child = child;
@ -58,7 +59,7 @@ node * r3_edge_branch(edge *e, int dl) {
// the suffix edge of the leaf
new_child = r3_tree_create(3);
s1_len = e->pattern_len - dl;
e1 = r3_edge_create(strndup(s1, s1_len), s1_len, new_child);
e1 = r3_edge_create(zstrndup(s1, s1_len), s1_len, new_child);
// Migrate the child edges to the new edge we just created.
for ( int i = 0 ; i < tmp_edge_len ; i++ ) {
@ -75,18 +76,20 @@ node * r3_edge_branch(edge *e, int dl) {
// truncate the original edge pattern
char *op = e->pattern;
e->pattern = strndup(e->pattern, dl);
e->pattern = zstrndup(e->pattern, dl);
e->pattern_len = dl;
free(op);
return new_child;
}
void r3_edge_free(edge * e) {
if (e->pattern) {
free(e->pattern);
zfree(e->pattern);
}
if ( e->child ) {
r3_tree_free(e->child);
}
// free itself
zfree(e);
}

View file

@ -10,10 +10,11 @@
#include <stdlib.h>
#include "r3.h"
#include "r3_gvc.h"
#include "zmalloc.h"
static char * node_id_str(int id) {
char * name = malloc(sizeof(char) * 20);
char * name = zmalloc(sizeof(char) * 20);
sprintf(name, "#%d", id);
return name;
}

View file

@ -6,13 +6,14 @@
*/
#include <stdlib.h>
#include "r3_list.h"
#include "zmalloc.h"
/* Naive linked list implementation */
list *
list_create()
{
list *l = (list *) malloc(sizeof(list));
list *l = (list *) zmalloc(sizeof(list));
l->count = 0;
l->head = NULL;
l->tail = NULL;
@ -24,22 +25,23 @@ void
list_free(l)
list *l;
{
list_item *li, *tmp;
if (l) {
list_item *li, *tmp;
pthread_mutex_lock(&(l->mutex));
pthread_mutex_lock(&(l->mutex));
if (l != NULL) {
li = l->head;
while (li != NULL) {
tmp = li->next;
free(li);
li = tmp;
if (l != NULL) {
li = l->head;
while (li != NULL) {
tmp = li->next;
li = tmp;
}
}
}
pthread_mutex_unlock(&(l->mutex));
pthread_mutex_destroy(&(l->mutex));
free(l);
pthread_mutex_unlock(&(l->mutex));
pthread_mutex_destroy(&(l->mutex));
zfree(l);
}
}
list_item *
@ -51,7 +53,7 @@ list_add_element(l, ptr)
pthread_mutex_lock(&(l->mutex));
li = (list_item *) malloc(sizeof(list_item));
li = (list_item *) zmalloc(sizeof(list_item));
li->value = ptr;
li->next = NULL;
li->prev = l->tail;
@ -95,7 +97,7 @@ list_remove_element(l, ptr)
li->next->prev = li->prev;
}
l->count--;
free(li);
zfree(li);
result = 1;
break;
}

View file

@ -17,6 +17,7 @@
#include "r3_define.h"
#include "r3_str.h"
#include "str_array.h"
#include "zmalloc.h"
// String value as the index http://judy.sourceforge.net/doc/JudySL_3x.htm
@ -44,9 +45,9 @@ static int strdiff(char * d1, char * d2) {
* Create a node object
*/
node * r3_tree_create(int cap) {
node * n = (node*) malloc( sizeof(node) );
node * n = (node*) zmalloc( sizeof(node) );
n->edges = (edge**) malloc( sizeof(edge*) * cap );
n->edges = (edge**) zmalloc( sizeof(edge*) * cap );
n->edge_len = 0;
n->edge_cap = cap;
@ -69,19 +70,26 @@ void r3_tree_free(node * tree) {
r3_edge_free(tree->edges[ i ]);
}
}
if (tree->edges)
free(tree->edges);
if (tree->routes)
free(tree->routes);
if (tree->edges) {
zfree(tree->edges);
}
if (tree->routes) {
zfree(tree->routes);
}
if (tree->pcre_pattern) {
pcre_free(tree->pcre_pattern);
}
#ifdef PCRE_STUDY_JIT_COMPILE
if (tree->pcre_extra) {
pcre_free_study(tree->pcre_extra);
}
#endif
if (tree->combined_pattern)
free(tree->combined_pattern);
if (tree->pcre_pattern)
free(tree->pcre_pattern);
if (tree->pcre_extra)
free(tree->pcre_extra);
zfree(tree->combined_pattern);
if (tree->ov)
free(tree->ov);
free(tree);
zfree(tree->ov);
zfree(tree);
tree = NULL;
}
@ -110,11 +118,11 @@ edge * r3_node_add_child(node * n, char * pat , node *child) {
void r3_node_append_edge(node *n, edge *e) {
if (n->edges == NULL) {
n->edge_cap = 3;
n->edges = malloc(sizeof(edge) * n->edge_cap);
n->edges = zmalloc(sizeof(edge) * n->edge_cap);
}
if (n->edge_len >= n->edge_cap) {
n->edge_cap *= 2;
edge ** p = realloc(n->edges, sizeof(edge) * n->edge_cap);
edge ** p = zrealloc(n->edges, sizeof(edge) * n->edge_cap);
if(p) {
n->edges = p;
}
@ -157,7 +165,7 @@ void r3_tree_compile_patterns(node * n) {
char * cpat;
char * p;
cpat = calloc(sizeof(char),128);
cpat = zcalloc(sizeof(char) * 128);
if (cpat==NULL)
return;
@ -189,7 +197,7 @@ void r3_tree_compile_patterns(node * n) {
info("pattern: %s\n",cpat);
n->ov_cnt = (1 + n->edge_len) * 3;
n->ov = (int*) calloc(sizeof(int), n->ov_cnt);
n->ov = (int*) zcalloc(sizeof(int) * n->ov_cnt);
n->combined_pattern = cpat;
@ -200,12 +208,9 @@ void r3_tree_compile_patterns(node * n) {
int erroffset;
unsigned int option_bits = 0;
if (n->pcre_pattern)
free(n->pcre_pattern);
if (n->pcre_extra)
free(n->pcre_extra);
// n->pcre_pattern;
if (n->pcre_pattern) {
pcre_free(n->pcre_pattern);
}
n->pcre_pattern = pcre_compile(
n->combined_pattern, /* the pattern */
option_bits, /* default options */
@ -216,16 +221,21 @@ void r3_tree_compile_patterns(node * n) {
printf("PCRE compilation failed at offset %d: %s, pattern: %s\n", erroffset, error, n->combined_pattern);
return;
}
#ifdef PCRE_STUDY_JIT_COMPILE
if (n->pcre_extra) {
pcre_free_study(n->pcre_extra);
}
n->pcre_extra = pcre_study(n->pcre_pattern, 0, &error);
if (n->pcre_extra == NULL) {
printf("PCRE study failed at offset %s\n", error);
return;
}
#endif
}
match_entry * match_entry_createl(char * path, int path_len) {
match_entry * entry = malloc(sizeof(match_entry));
match_entry * entry = zmalloc(sizeof(match_entry));
if(!entry)
return NULL;
entry->vars = str_array_create(3);
@ -236,8 +246,10 @@ match_entry * match_entry_createl(char * path, int path_len) {
}
void match_entry_free(match_entry * entry) {
str_array_free(entry->vars);
free(entry);
if (entry) {
str_array_free(entry->vars);
zfree(entry);
}
}
@ -280,11 +292,11 @@ node * r3_tree_matchl(const node * n, char * path, int path_len, match_entry * e
if (rc < 0) {
switch(rc)
{
case PCRE_ERROR_NOMATCH: printf("No match\n"); break;
case PCRE_ERROR_NOMATCH: printf("pcre: no match\n"); break;
/*
Handle other special cases if you like
*/
default: printf("Matching error %d\n", rc); break;
default: printf("pcre matching error '%d' on pattern '%s'\n", rc, n->combined_pattern); break;
}
// does not match all edges, return NULL;
return NULL;
@ -305,7 +317,7 @@ node * r3_tree_matchl(const node * n, char * path, int path_len, match_entry * e
if (entry && e->has_slug) {
// append captured token to entry
str_array_append(entry->vars , strndup(substring_start, substring_length));
str_array_append(entry->vars , zstrndup(substring_start, substring_length));
}
if (restlen == 0 ) {
return e->child && e->child->endpoint > 0 ? e->child : NULL;
@ -363,7 +375,7 @@ inline edge * r3_node_find_edge_str(const node * n, char * str, int str_len) {
node * r3_node_create() {
node * n = (node*) malloc( sizeof(node) );
node * n = (node*) zmalloc( sizeof(node) );
n->edges = NULL;
n->edge_len = 0;
n->edge_cap = 0;
@ -384,11 +396,13 @@ route * r3_route_create(char * path) {
}
void r3_route_free(route * route) {
free(route);
if (route) {
zfree(route);
}
}
route * r3_route_createl(char * path, int path_len) {
route * info = malloc(sizeof(route));
route * info = zmalloc(sizeof(route));
info->path = path;
info->path_len = path_len;
info->request_method = 0; // can be (GET || POST)
@ -458,14 +472,14 @@ node * r3_tree_insert_pathl_(node *tree, char *path, int path_len, route * route
// insert the first one edge, and break at "p"
node * child = r3_tree_create(3);
r3_node_add_child(n, strndup(path, (int)(p - path)), child);
r3_node_add_child(n, zstrndup(path, (int)(p - path)), child);
child->endpoint = 0;
// and insert the rest part to the child
return r3_tree_insert_pathl_(child, p, path_len - (int)(p - path), route, data);
} else {
node * child = r3_tree_create(3);
r3_node_add_child(n, strndup(path, path_len) , child);
r3_node_add_child(n, zstrndup(path, path_len) , child);
// info("edge not found, insert one: %s\n", path);
child->data = data;
child->endpoint++;
@ -605,11 +619,11 @@ int r3_route_cmp(route *r1, match_entry *r2) {
void r3_node_append_route(node * n, route * r) {
if (n->routes == NULL) {
n->route_cap = 3;
n->routes = malloc(sizeof(route) * n->route_cap);
n->routes = zmalloc(sizeof(route) * n->route_cap);
}
if (n->route_len >= n->route_cap) {
n->route_cap *= 2;
n->routes = realloc(n->routes, sizeof(route) * n->route_cap);
n->routes = zrealloc(n->routes, sizeof(route) * n->route_cap);
}
n->routes[ n->route_len++ ] = r;
}

View file

@ -11,6 +11,7 @@
#include "r3.h"
#include "r3_str.h"
#include "str_array.h"
#include "zmalloc.h"
/**
* provide a quick way to count slugs, simply search for '{'
@ -40,22 +41,26 @@ char * inside_slug(char * needle, int needle_len, char *offset) {
char * s1 = offset;
char * s2 = offset;
while( s1 >= needle ) {
short found_s1 = 0;
short found_s2 = 0;
while( s1 >= needle && (s1 - needle < needle_len) ) {
if ( *s1 == '{' ) {
found_s1 = 1;
break;
}
s1--;
}
char *end = needle+ needle_len;
while( s2 < end ) {
char * end = needle + needle_len;
while( (s2 + 1) < end ) {
if ( *s2 == '}' ) {
found_s2 = 1;
break;
}
s2++;
}
if ( *s1 == '{' && *s2 == '}' ) {
if (found_s1 && found_s2) {
return s1;
}
return NULL;
@ -134,11 +139,11 @@ char * slug_compile(char * str, int len)
s1 = find_slug_placeholder(str, &s1_len);
if ( s1 == NULL ) {
return strdup(str);
return zstrdup(str);
}
char * out = NULL;
if ((out = calloc(sizeof(char),200)) == NULL) {
if ((out = zcalloc(200)) == NULL) {
return (NULL);
}
@ -171,7 +176,7 @@ char * ltrim_slash(char* str)
{
char * p = str;
while (*p == '/') p++;
return strdup(p);
return zstrdup(p);
}
void str_repeat(char *s, char *c, int len) {
@ -188,13 +193,13 @@ void print_indent(int level) {
}
#ifndef HAVE_STRDUP
char *strdup(const char *s) {
char *zstrdup(const char *s) {
char *out;
int count = 0;
while( s[count] )
++count;
++count;
out = malloc(sizeof(char) * count);
out = zmalloc(sizeof(char) * count);
out[--count] = 0;
while( --count >= 0 )
out[count] = s[count];
@ -203,13 +208,13 @@ char *strdup(const char *s) {
#endif
#ifndef HAVE_STRNDUP
char *strndup(const char *s, int n) {
char *zstrndup(const char *s, int n) {
char *out;
int count = 0;
while( count < n && s[count] )
++count;
++count;
out = malloc(sizeof(char) * count);
out = zmalloc(sizeof(char) * count);
out[--count] = 0;
while( --count >= 0 )
out[count] = s[count];

View file

@ -10,22 +10,24 @@
#include <assert.h>
#include "str_array.h"
#include "r3_str.h"
#include "zmalloc.h"
str_array * str_array_create(int cap) {
str_array * list = (str_array*) malloc( sizeof(str_array) );
str_array * list = (str_array*) zmalloc( sizeof(str_array) );
list->len = 0;
list->cap = cap;
list->tokens = (char**) malloc( sizeof(char*) * cap);
list->tokens = (char**) zmalloc( sizeof(char*) * cap);
return list;
}
void str_array_free(str_array *l) {
for ( int i = 0; i < l->len ; i++ ) {
char * t = l->tokens[ i ];
free(t);
if (t) {
zfree(t);
}
}
free(l);
zfree(l);
}
bool str_array_is_full(str_array * l) {
@ -33,7 +35,7 @@ bool str_array_is_full(str_array * l) {
}
bool str_array_resize(str_array *l, int new_cap) {
l->tokens = realloc(l->tokens, sizeof(char**) * new_cap);
l->tokens = zrealloc(l->tokens, sizeof(char**) * new_cap);
l->cap = new_cap;
return l->tokens != NULL;
}

368
src/zmalloc.c Normal file
View file

@ -0,0 +1,368 @@
/* zmalloc - total amount of allocated memory aware version of malloc()
*
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
* 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.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
/* This function provide us access to the original libc free(). This is useful
* for instance to free results obtained by backtrace_symbols(). We need
* to define this function before including zmalloc.h that may shadow the
* free implementation if we use jemalloc or another non standard allocator. */
void zlibc_free(void *ptr) {
free(ptr);
}
#include <string.h>
#include <pthread.h>
#include "config.h"
#include "zmalloc.h"
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
#if defined(__sun) || defined(__sparc) || defined(__sparc__)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif
/* Explicitly override malloc/free etc when using tcmalloc. */
#if defined(USE_TCMALLOC)
#define malloc(size) tc_malloc(size)
#define calloc(count,size) tc_calloc(count,size)
#define realloc(ptr,size) tc_realloc(ptr,size)
#define free(ptr) tc_free(ptr)
#elif defined(USE_JEMALLOC) && (JEMALLOC_VERSION_MAJOR > 2)
#include <jemalloc/jemalloc.h>
#define malloc(size) je_malloc(size)
#define calloc(count,size) je_calloc(count,size)
#define realloc(ptr,size) je_realloc(ptr,size)
#define free(ptr) je_free(ptr)
#endif
#ifdef HAVE_ATOMIC
#define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))
#define update_zmalloc_stat_sub(__n) __sync_sub_and_fetch(&used_memory, (__n))
#else
#define update_zmalloc_stat_add(__n) do { \
pthread_mutex_lock(&used_memory_mutex); \
used_memory += (__n); \
pthread_mutex_unlock(&used_memory_mutex); \
} while(0)
#define update_zmalloc_stat_sub(__n) do { \
pthread_mutex_lock(&used_memory_mutex); \
used_memory -= (__n); \
pthread_mutex_unlock(&used_memory_mutex); \
} while(0)
#endif
#define update_zmalloc_stat_alloc(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
if (zmalloc_thread_safe) { \
update_zmalloc_stat_add(_n); \
} else { \
used_memory += _n; \
} \
} while(0)
#define update_zmalloc_stat_free(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
if (zmalloc_thread_safe) { \
update_zmalloc_stat_sub(_n); \
} else { \
used_memory -= _n; \
} \
} while(0)
static size_t used_memory = 0;
static int zmalloc_thread_safe = 0;
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;
static void zmalloc_default_oom(size_t size) {
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
size);
fflush(stderr);
abort();
}
static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;
void *zmalloc(size_t size) {
void *ptr = malloc(size+PREFIX_SIZE);
if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_alloc(zmalloc_size(ptr));
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)ptr+PREFIX_SIZE;
#endif
}
void *zcalloc(size_t size) {
void *ptr = calloc(1, size+PREFIX_SIZE);
if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_alloc(zmalloc_size(ptr));
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)ptr+PREFIX_SIZE;
#endif
}
void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
#endif
size_t oldsize;
void *newptr;
if (ptr == NULL) return zmalloc(size);
#ifdef HAVE_MALLOC_SIZE
oldsize = zmalloc_size(ptr);
newptr = realloc(ptr,size);
if (!newptr) zmalloc_oom_handler(size);
update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(zmalloc_size(newptr));
return newptr;
#else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
newptr = realloc(realptr,size+PREFIX_SIZE);
if (!newptr) zmalloc_oom_handler(size);
*((size_t*)newptr) = size;
update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(size);
return (char*)newptr+PREFIX_SIZE;
#endif
}
/* Provide zmalloc_size() for systems where this function is not provided by
* malloc itself, given that in that case we store a header with this
* information as the first bytes of every allocation. */
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr) {
void *realptr = (char*)ptr-PREFIX_SIZE;
size_t size = *((size_t*)realptr);
/* Assume at least that all the allocations are padded at sizeof(long) by
* the underlying allocator. */
if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
return size+PREFIX_SIZE;
}
#endif
void zfree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
size_t oldsize;
#endif
if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_free(zmalloc_size(ptr));
free(ptr);
#else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
free(realptr);
#endif
}
char *zstrdup(const char *s) {
size_t l = strlen(s)+1;
char *p = zmalloc(l);
memcpy(p,s,l);
return p;
}
char * zstrndup (const char *s, size_t n)
{
char *result;
size_t len = strlen (s);
if (n < len)
len = n;
result = (char *) zmalloc (len + 1);
if (!result)
return 0;
result[len] = '\0';
return (char *) memcpy (result, s, len);
}
size_t zmalloc_used_memory(void) {
size_t um;
if (zmalloc_thread_safe) {
#ifdef HAVE_ATOMIC
um = __sync_add_and_fetch(&used_memory, 0);
#else
pthread_mutex_lock(&used_memory_mutex);
um = used_memory;
pthread_mutex_unlock(&used_memory_mutex);
#endif
}
else {
um = used_memory;
}
return um;
}
void zmalloc_enable_thread_safeness(void) {
zmalloc_thread_safe = 1;
}
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
zmalloc_oom_handler = oom_handler;
}
/* Get the RSS information in an OS-specific way.
*
* WARNING: the function zmalloc_get_rss() is not designed to be fast
* and may not be called in the busy loops where Redis tries to release
* memory expiring or swapping out objects.
*
* For this kind of "fast RSS reporting" usages use instead the
* function RedisEstimateRSS() that is a much faster (and less precise)
* version of the function. */
#if defined(HAVE_PROC_STAT)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
size_t zmalloc_get_rss(void) {
int page = sysconf(_SC_PAGESIZE);
size_t rss;
char buf[4096];
char filename[256];
int fd, count;
char *p, *x;
snprintf(filename,256,"/proc/%d/stat",getpid());
if ((fd = open(filename,O_RDONLY)) == -1) return 0;
if (read(fd,buf,4096) <= 0) {
close(fd);
return 0;
}
close(fd);
p = buf;
count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
while(p && count--) {
p = strchr(p,' ');
if (p) p++;
}
if (!p) return 0;
x = strchr(p,' ');
if (!x) return 0;
*x = '\0';
rss = strtoll(p,NULL,10);
rss *= page;
return rss;
}
#elif defined(HAVE_TASKINFO)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/task.h>
#include <mach/mach_init.h>
size_t zmalloc_get_rss(void) {
task_t task = MACH_PORT_NULL;
struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS)
return 0;
task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
return t_info.resident_size;
}
#else
size_t zmalloc_get_rss(void) {
/* If we can't get the RSS in an OS-specific way for this system just
* return the memory usage we estimated in zmalloc()..
*
* Fragmentation will appear to be always 1 (no fragmentation)
* of course... */
return zmalloc_used_memory();
}
#endif
/* Fragmentation = RSS / allocated-bytes */
float zmalloc_get_fragmentation_ratio(size_t rss) {
return (float)rss/zmalloc_used_memory();
}
#if defined(HAVE_PROC_SMAPS)
size_t zmalloc_get_private_dirty(void) {
char line[1024];
size_t pd = 0;
FILE *fp = fopen("/proc/self/smaps","r");
if (!fp) return 0;
while(fgets(line,sizeof(line),fp) != NULL) {
if (strncmp(line,"Private_Dirty:",14) == 0) {
char *p = strchr(line,'k');
if (p) {
*p = '\0';
pd += strtol(line+14,NULL,10) * 1024;
}
}
}
fclose(fp);
return pd;
}
#else
size_t zmalloc_get_private_dirty(void) {
return 0;
}
#endif

View file

@ -8,6 +8,7 @@ TESTS = check_tree
AM_CFLAGS = -ggdb $(DEPS_CFLAGS) -I$(top_builddir) -I$(top_builddir)/include @CHECK_CFLAGS@
AM_LDFLAGS = $(DEPS_LIBS) -L$(top_builddir)/src -lr3 @CHECK_LIBS@
noinst_HEADERS = \
bench.h \
$(NULL)
@ -16,6 +17,10 @@ dist_noinst_DATA = \
bench_str.csv \
$(NULL)
if USE_JEMALLOC
AM_CFLAGS += -ljemalloc
endif
if ENABLE_GRAPHVIZ
TESTS += check_gvc
check_gvc_SOURCES = check_gvc.c bench.c

View file

@ -432,10 +432,15 @@
1400606491,14030387.83
1400606523,13157029.51
1400606567,13999364.95
1400747045,12800263.03
1400747100,13777027.60
1400747522,8879185.36
1400747540,13340093.01
1400747551,13646625.52
1400747590,13236559.80
1400747616,13166578.53
1400607671,13906437.74
1400607682,13828950.20
1400607689,13932349.19
1400607695,13583203.56
1400607698,13630627.45
1400607700,13972490.11
1400659046,19754150.71
1400668268,13174604.57
1400668574,13632260.72
1400681414,10832905.89
1400685490,13185955.87
1400748100,12609470.07

1 1400242718 5649455.80
432 1400606491 14030387.83
433 1400606523 13157029.51
434 1400606567 13999364.95
435 1400747045 1400607671 12800263.03 13906437.74
436 1400747100 1400607682 13777027.60 13828950.20
437 1400747522 1400607689 8879185.36 13932349.19
438 1400747540 1400607695 13340093.01 13583203.56
439 1400747551 1400607698 13646625.52 13630627.45
440 1400747590 1400607700 13236559.80 13972490.11
441 1400747616 1400659046 13166578.53 19754150.71
442 1400668268 13174604.57
443 1400668574 13632260.72
444 1400681414 10832905.89
445 1400685490 13185955.87
446 1400748100 12609470.07

View file

@ -11,20 +11,26 @@
#include "r3.h"
#include "r3_str.h"
#include "str_array.h"
#include "zmalloc.h"
START_TEST (test_slug_compile)
{
char * path = "/user/{id}";
ck_assert_str_eq( slug_compile(path, strlen(path) ) , "/user/([^/]+)" );
char * c = NULL;
ck_assert_str_eq( c = slug_compile(path, strlen(path) ) , "/user/([^/]+)" );
zfree(c);
char * path2 = "/what/{id}-foo";
ck_assert_str_eq( slug_compile(path2, strlen(path2) ) , "/what/([^/]+)-foo" );
ck_assert_str_eq( c = slug_compile(path2, strlen(path2) ) , "/what/([^/]+)-foo" );
zfree(c);
char * path3 = "-{id}";
ck_assert_str_eq(slug_compile(path3, strlen(path3)), "-([^/]+)" );
ck_assert_str_eq( c = slug_compile(path3, strlen(path3)), "-([^/]+)" );
zfree(c);
char * path4 = "-{idx:\\d{3}}";
ck_assert_str_eq(slug_compile(path4, strlen(path4)), "-(\\d{3})" );
ck_assert_str_eq( c = slug_compile(path4, strlen(path4)), "-(\\d{3})" );
zfree(c);
}
END_TEST
@ -63,7 +69,7 @@ START_TEST (test_inside_slug)
int slug_len = 0;
char * pattern = "/user/{name:\\s+}/to/{id}";
char * offset = strchr(pattern, '{') + 2;
ck_assert( inside_slug(pattern, strlen(pattern), offset) );
ck_assert( (int)inside_slug(pattern, strlen(pattern), offset) );
ck_assert( *(inside_slug(pattern, strlen(pattern), offset)) == '{' );
ck_assert( ! inside_slug(pattern, strlen(pattern), pattern) );
}

View file

@ -5,6 +5,7 @@
#include "r3.h"
#include "r3_str.h"
#include "str_array.h"
#include "zmalloc.h"
#include "bench.h"
@ -19,9 +20,9 @@ END_TEST
START_TEST (test_r3_node_construct_and_free)
{
node * n = r3_tree_create(10);
node * child = r3_tree_create(3);
node * another_tree = r3_tree_create(3);
r3_tree_free(n);
r3_tree_free(child);
r3_tree_free(another_tree);
}
END_TEST
@ -31,7 +32,7 @@ START_TEST (test_r3_node_find_edge)
node * child = r3_tree_create(3);
fail_if( r3_node_add_child(n, strdup("/add") , child) == FALSE );
fail_if( r3_node_add_child(n, zstrdup("/add") , child) == FALSE );
fail_if( r3_node_find_edge(n, "/add") == NULL );
fail_if( r3_node_find_edge(n, "/bar") != NULL );
@ -132,7 +133,7 @@ START_TEST (test_pcre_patterns_insert_2)
r3_tree_dump(n, 0);
node *matched;
matched = r3_tree_match(n, "/post/11/22", NULL);
ck_assert(matched);
ck_assert((int)matched);
ck_assert(matched->endpoint > 0);
}
END_TEST
@ -159,7 +160,7 @@ START_TEST (test_pcre_patterns_insert_3)
matched = r3_tree_match(n, "/post/11/22", NULL);
ck_assert(matched);
ck_assert((int)matched);
matched = r3_tree_match(n, "/post/11", NULL);
ck_assert(!matched);
@ -218,16 +219,16 @@ START_TEST (test_str_array)
str_array * l = str_array_create(3);
fail_if( l == NULL );
fail_if( FALSE == str_array_append(l, strdup("abc") ) );
fail_if( FALSE == str_array_append(l, zstrdup("abc") ) );
fail_if( l->len != 1 );
fail_if( FALSE == str_array_append(l, strdup("foo") ) );
fail_if( FALSE == str_array_append(l, zstrdup("foo") ) );
fail_if( l->len != 2 );
fail_if( FALSE == str_array_append(l, strdup("bar") ) );
fail_if( FALSE == str_array_append(l, zstrdup("bar") ) );
fail_if( l->len != 3 );
fail_if( FALSE == str_array_append(l, strdup("zoo") ) );
fail_if( FALSE == str_array_append(l, zstrdup("zoo") ) );
fail_if( l->len != 4 );
fail_if( FALSE == str_array_resize(l, l->cap * 2) );
@ -272,7 +273,7 @@ START_TEST(test_pcre_pattern_simple)
// r3_tree_dump(n, 0);
node *matched;
matched = r3_tree_matchl(n, "/user/123", strlen("/user/123"), entry);
fail_if(matched == NULL);
ck_assert(matched);
ck_assert(entry->vars->len > 0);
ck_assert_str_eq(entry->vars->tokens[0],"123");
r3_tree_free(n);
@ -305,7 +306,7 @@ START_TEST(test_pcre_pattern_more)
node *matched;
matched = r3_tree_matchl(n, "/user/123", strlen("/user/123"), entry);
fail_if(matched == NULL);
ck_assert(matched);
ck_assert(entry->vars->len > 0);
ck_assert_str_eq(entry->vars->tokens[0],"123");
@ -314,13 +315,13 @@ START_TEST(test_pcre_pattern_more)
ck_assert_int_eq( *((int*) matched->data), var1);
matched = r3_tree_matchl(n, "/user2/123", strlen("/user2/123"), entry);
fail_if(matched == NULL);
ck_assert(matched);
ck_assert(entry->vars->len > 0);
ck_assert_str_eq(entry->vars->tokens[0],"123");
ck_assert_int_eq( *((int*)matched->data), var2);
matched = r3_tree_matchl(n, "/user3/123", strlen("/user3/123"), entry);
fail_if(matched == NULL);
ck_assert(matched);
ck_assert(entry->vars->len > 0);
ck_assert_str_eq(entry->vars->tokens[0],"123");
ck_assert_int_eq( *((int*)matched->data), var3);
@ -707,7 +708,7 @@ r3_tree_insert_path(n, "/garply/grault/corge", NULL);
r3_tree_compile(n);
// r3_tree_dump(n, 0);
// match_entry *entry = calloc( sizeof(entry) , 1 );
// match_entry *entry = zcalloc( 1 );
node *m;
m = r3_tree_match(n , "/qux/bar/corge", NULL);