Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added a small benchmarking utility and fixed some bugs.

  • Loading branch information...
commit ca54a857e6bbcaed674fadd9fa9291580d89053a 1 parent 791ec87
Nick authored
View
58 autom4te.cache/requests
@@ -75,11 +75,11 @@
'_AM_AUTOCONF_VERSION' => 1,
'AM_DISABLE_SHARED' => 1,
'_LTDL_SETUP' => 1,
- 'AM_PROG_LIBTOOL' => 1,
'_LT_AC_LANG_CXX' => 1,
- 'AM_PROG_LD' => 1,
- '_LT_AC_FILE_LTDLL_C' => 1,
+ 'AM_PROG_LIBTOOL' => 1,
'AC_LIB_LTDL' => 1,
+ '_LT_AC_FILE_LTDLL_C' => 1,
+ 'AM_PROG_LD' => 1,
'AU_DEFUN' => 1,
'AC_PROG_NM' => 1,
'AC_LIBTOOL_DLOPEN' => 1,
@@ -103,29 +103,29 @@
'AC_LTDL_OBJDIR' => 1,
'_LT_PATH_TOOL_PREFIX' => 1,
'AC_LIBTOOL_RC' => 1,
- '_LT_AC_PROG_ECHO_BACKSLASH' => 1,
'AC_DISABLE_FAST_INSTALL' => 1,
- 'include' => 1,
- '_LT_AC_TRY_DLOPEN_SELF' => 1,
+ '_LT_AC_PROG_ECHO_BACKSLASH' => 1,
'_LT_AC_SYS_LIBPATH_AIX' => 1,
+ '_LT_AC_TRY_DLOPEN_SELF' => 1,
+ 'include' => 1,
'LT_AC_PROG_SED' => 1,
'AM_ENABLE_SHARED' => 1,
'LTDL_INSTALLABLE' => 1,
'_LT_AC_LANG_GCJ_CONFIG' => 1,
'AC_ENABLE_SHARED' => 1,
- 'AC_LIBTOOL_SYS_HARD_LINK_LOCKS' => 1,
'AC_ENABLE_STATIC' => 1,
+ 'AC_LIBTOOL_SYS_HARD_LINK_LOCKS' => 1,
'_LT_AC_TAGVAR' => 1,
'AC_LIBTOOL_LANG_F77_CONFIG' => 1,
'AM_CONDITIONAL' => 1,
'LT_LIB_DLLOAD' => 1,
- 'LTVERSION_VERSION' => 1,
'LTDL_INIT' => 1,
- 'm4_include' => 1,
+ 'LTVERSION_VERSION' => 1,
'AM_PROG_INSTALL_SH' => 1,
+ 'm4_include' => 1,
'AC_PROG_EGREP' => 1,
- 'AC_PATH_MAGIC' => 1,
'_AC_AM_CONFIG_HEADER_HOOK' => 1,
+ 'AC_PATH_MAGIC' => 1,
'AC_LTDL_SYSSEARCHPATH' => 1,
'AM_MAKE_INCLUDE' => 1,
'LT_CMD_MAX_LEN' => 1,
@@ -170,11 +170,11 @@
'AM_PROG_NM' => 1,
'AC_LIBLTDL_CONVENIENCE' => 1,
'AC_DEPLIBS_CHECK_METHOD' => 1,
- 'AC_LIBLTDL_INSTALLABLE' => 1,
'AM_SET_CURRENT_AUTOMAKE_VERSION' => 1,
+ 'AC_LIBLTDL_INSTALLABLE' => 1,
'AC_LTDL_ENABLE_INSTALL' => 1,
- 'LT_PROG_GCJ' => 1,
'AC_LIBTOOL_SYS_DYNAMIC_LINKER' => 1,
+ 'LT_PROG_GCJ' => 1,
'AM_INIT_AUTOMAKE' => 1,
'AC_DISABLE_STATIC' => 1,
'LT_PATH_NM' => 1,
@@ -182,28 +182,28 @@
'_LT_AC_LOCK' => 1,
'_LT_AC_LANG_RC_CONFIG' => 1,
'LT_SYS_MODULE_PATH' => 1,
- 'LT_WITH_LTDL' => 1,
'AC_LIBTOOL_POSTDEP_PREDEP' => 1,
+ 'LT_WITH_LTDL' => 1,
'AC_LTDL_SHLIBPATH' => 1,
'AM_AUX_DIR_EXPAND' => 1,
- 'AC_LIBTOOL_PROG_COMPILER_NO_RTTI' => 1,
'_LT_AC_LANG_F77_CONFIG' => 1,
- '_LT_COMPILER_OPTION' => 1,
+ 'AC_LIBTOOL_PROG_COMPILER_NO_RTTI' => 1,
'_AM_SET_OPTIONS' => 1,
- 'AM_RUN_LOG' => 1,
+ '_LT_COMPILER_OPTION' => 1,
'_AM_OUTPUT_DEPENDENCY_COMMANDS' => 1,
- 'AC_LTDL_SYS_DLOPEN_DEPLIBS' => 1,
- 'AC_LIBTOOL_SYS_OLD_ARCHIVE' => 1,
+ 'AM_RUN_LOG' => 1,
'AC_LIBTOOL_PICMODE' => 1,
- 'AC_CHECK_LIBM' => 1,
+ 'AC_LIBTOOL_SYS_OLD_ARCHIVE' => 1,
+ 'AC_LTDL_SYS_DLOPEN_DEPLIBS' => 1,
'LT_PATH_LD' => 1,
+ 'AC_CHECK_LIBM' => 1,
'AC_LIBTOOL_SYS_LIB_STRIP' => 1,
'_AM_MANGLE_OPTION' => 1,
- 'AC_LIBTOOL_SYS_MAX_CMD_LEN' => 1,
'AC_LTDL_SYMBOL_USCORE' => 1,
+ 'AC_LIBTOOL_SYS_MAX_CMD_LEN' => 1,
'AM_SET_DEPDIR' => 1,
- '_LT_LIBSOURCES' => 1,
'_LT_CC_BASENAME' => 1,
+ '_LT_LIBSOURCES' => 1,
'_LT_LIBOBJ' => 1
}
], 'Autom4te::Request' ),
@@ -219,15 +219,15 @@
'configure.ac'
],
{
- '_LT_AC_TAGCONFIG' => 1,
'AM_PROG_F77_C_O' => 1,
- 'AC_INIT' => 1,
+ '_LT_AC_TAGCONFIG' => 1,
'm4_pattern_forbid' => 1,
+ 'AC_INIT' => 1,
'AC_CANONICAL_TARGET' => 1,
- 'AC_CONFIG_LIBOBJ_DIR' => 1,
'AC_SUBST' => 1,
- 'AC_CANONICAL_HOST' => 1,
+ 'AC_CONFIG_LIBOBJ_DIR' => 1,
'AC_FC_SRCEXT' => 1,
+ 'AC_CANONICAL_HOST' => 1,
'AC_PROG_LIBTOOL' => 1,
'AM_INIT_AUTOMAKE' => 1,
'AC_CONFIG_SUBDIRS' => 1,
@@ -235,8 +235,8 @@
'LT_CONFIG_LTDL_DIR' => 1,
'AC_CONFIG_LINKS' => 1,
'AC_REQUIRE_AUX_FILE' => 1,
- 'LT_SUPPORTED_TAG' => 1,
'm4_sinclude' => 1,
+ 'LT_SUPPORTED_TAG' => 1,
'AM_MAINTAINER_MODE' => 1,
'AM_GNU_GETTEXT_INTL_SUBDIR' => 1,
'_m4_warn' => 1,
@@ -253,11 +253,11 @@
'AH_OUTPUT' => 1,
'_AM_SUBST_NOTMAKE' => 1,
'AC_CONFIG_AUX_DIR' => 1,
- 'm4_pattern_allow' => 1,
- 'AM_PROG_CC_C_O' => 1,
'sinclude' => 1,
- 'AM_CONDITIONAL' => 1,
+ 'AM_PROG_CC_C_O' => 1,
+ 'm4_pattern_allow' => 1,
'AC_CANONICAL_SYSTEM' => 1,
+ 'AM_CONDITIONAL' => 1,
'AC_CONFIG_HEADERS' => 1,
'AC_DEFINE_TRACE_LITERAL' => 1,
'm4_include' => 1,
View
5 src/Makefile.am
@@ -1,5 +1,8 @@
## Process this file with automake to produce Makefile.in
-bin_PROGRAMS = barbershop
+bin_PROGRAMS = barbershop benchmark
barbershop_SOURCES = barbershop.c barbershop.h bst.c bst.h scores.c scores.h stats.h
barbershop_CFLAGS = $(OPTIMIZATION) -Wall $(ARCH) $(PROF)
+
+benchmark_SOURCES = benchmark.c
+benchmark_CFLAGS = $(OPTIMIZATION) -Wall $(ARCH) $(PROF)
View
32 src/Makefile.in
@@ -32,7 +32,7 @@ PRE_UNINSTALL = :
POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
-bin_PROGRAMS = barbershop$(EXEEXT)
+bin_PROGRAMS = barbershop$(EXEEXT) benchmark$(EXEEXT)
subdir = src
DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
@@ -52,6 +52,12 @@ barbershop_LDADD = $(LDADD)
barbershop_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CCLD) $(barbershop_CFLAGS) \
$(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+am_benchmark_OBJECTS = benchmark-benchmark.$(OBJEXT)
+benchmark_OBJECTS = $(am_benchmark_OBJECTS)
+benchmark_LDADD = $(LDADD)
+benchmark_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(benchmark_CFLAGS) \
+ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
DEFAULT_INCLUDES = -I. -I$(top_builddir)@am__isrc@
depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
am__depfiles_maybe = depfiles
@@ -64,8 +70,8 @@ CCLD = $(CC)
LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
--mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
$(LDFLAGS) -o $@
-SOURCES = $(barbershop_SOURCES)
-DIST_SOURCES = $(barbershop_SOURCES)
+SOURCES = $(barbershop_SOURCES) $(benchmark_SOURCES)
+DIST_SOURCES = $(barbershop_SOURCES) $(benchmark_SOURCES)
ETAGS = etags
CTAGS = ctags
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
@@ -181,6 +187,8 @@ top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
barbershop_SOURCES = barbershop.c barbershop.h bst.c bst.h scores.c scores.h stats.h
barbershop_CFLAGS = $(OPTIMIZATION) -Wall $(ARCH) $(PROF)
+benchmark_SOURCES = benchmark.c
+benchmark_CFLAGS = $(OPTIMIZATION) -Wall $(ARCH) $(PROF)
all: all-am
.SUFFIXES:
@@ -245,6 +253,9 @@ clean-binPROGRAMS:
barbershop$(EXEEXT): $(barbershop_OBJECTS) $(barbershop_DEPENDENCIES)
@rm -f barbershop$(EXEEXT)
$(barbershop_LINK) $(barbershop_OBJECTS) $(barbershop_LDADD) $(LIBS)
+benchmark$(EXEEXT): $(benchmark_OBJECTS) $(benchmark_DEPENDENCIES)
+ @rm -f benchmark$(EXEEXT)
+ $(benchmark_LINK) $(benchmark_OBJECTS) $(benchmark_LDADD) $(LIBS)
mostlyclean-compile:
-rm -f *.$(OBJEXT)
@@ -255,6 +266,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/barbershop-barbershop.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/barbershop-bst.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/barbershop-scores.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/benchmark-benchmark.Po@am__quote@
.c.o:
@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
@@ -319,6 +331,20 @@ barbershop-scores.obj: scores.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(barbershop_CFLAGS) $(CFLAGS) -c -o barbershop-scores.obj `if test -f 'scores.c'; then $(CYGPATH_W) 'scores.c'; else $(CYGPATH_W) '$(srcdir)/scores.c'; fi`
+benchmark-benchmark.o: benchmark.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(benchmark_CFLAGS) $(CFLAGS) -MT benchmark-benchmark.o -MD -MP -MF $(DEPDIR)/benchmark-benchmark.Tpo -c -o benchmark-benchmark.o `test -f 'benchmark.c' || echo '$(srcdir)/'`benchmark.c
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/benchmark-benchmark.Tpo $(DEPDIR)/benchmark-benchmark.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='benchmark.c' object='benchmark-benchmark.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(benchmark_CFLAGS) $(CFLAGS) -c -o benchmark-benchmark.o `test -f 'benchmark.c' || echo '$(srcdir)/'`benchmark.c
+
+benchmark-benchmark.obj: benchmark.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(benchmark_CFLAGS) $(CFLAGS) -MT benchmark-benchmark.obj -MD -MP -MF $(DEPDIR)/benchmark-benchmark.Tpo -c -o benchmark-benchmark.obj `if test -f 'benchmark.c'; then $(CYGPATH_W) 'benchmark.c'; else $(CYGPATH_W) '$(srcdir)/benchmark.c'; fi`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/benchmark-benchmark.Tpo $(DEPDIR)/benchmark-benchmark.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='benchmark.c' object='benchmark-benchmark.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(benchmark_CFLAGS) $(CFLAGS) -c -o benchmark-benchmark.obj `if test -f 'benchmark.c'; then $(CYGPATH_W) 'benchmark.c'; else $(CYGPATH_W) '$(srcdir)/benchmark.c'; fi`
+
mostlyclean-libtool:
-rm -f *.lo
View
14 src/barbershop.c
@@ -33,6 +33,7 @@ THE SOFTWARE.
#include <errno.h>
#include <err.h>
#include <time.h>
+#include <assert.h>
#include "scores.h"
#include "bst.h"
@@ -101,7 +102,7 @@ void on_read(int fd, short ev, void *arg) {
int item_id = atoi(tokens[KEY_TOKEN].value);
int score = atoi(tokens[VALUE_TOKEN].value);
- Position lookup = Find( item_id, items );
+ Position lookup = Find(item_id, items);
if (lookup == NULL) {
items = Insert(item_id, score, items);
scores = promoteItem(scores, score, item_id, -1);
@@ -114,13 +115,14 @@ void on_read(int fd, short ev, void *arg) {
} else {
lookup->score += score;
}
+ assert(lookup->score > old_score);
scores = promoteItem(scores, lookup->score, item_id, old_score);
}
app_stats.updates += 1;
reply(fd, "OK\r\n");
} else if (ntokens == 2 && strcmp(tokens[COMMAND_TOKEN].value, "next") == 0) {
- int next = -1;
- scores = NextItem(scores, next);
+ int next;
+ scores = NextItem(scores, &next);
if (next != -1) {
Position lookup = Find( next, items );
if (lookup != NULL) {
@@ -144,12 +146,6 @@ void on_read(int fd, short ev, void *arg) {
sprintf(out, "STAT pools_gc %d\r\n", app_stats.pools_gc); reply(fd, out);
sprintf(out, "STAT items_gc %d\r\n", app_stats.items_gc); reply(fd, out);
reply(fd, "END\r\n");
- /*
- printf("Dumping items tree:\n");
- DumpItems(items);
- printf("Dumping score buckets:\n");
- DumpScores(scores);
- */
} else {
reply(fd, "ERROR\r\n");
}
View
160 src/benchmark.c
@@ -0,0 +1,160 @@
+/*
+Copyright (c) 2010 Nick Gerakines <nick at gerakines dot net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <err.h>
+#include <string.h>
+#include <event.h>
+#include <evhttp.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <pthread.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
+const int LOW = 1;
+const int HIGH = 500;
+
+void send_command(int sd, char *command);
+
+int main(int argc, char **argv) {
+ char *ipaddress;
+ int port = 8002;
+
+ int c;
+ while (1) {
+ static struct option long_options[] = {
+ {"ip", required_argument, 0, 'i'},
+ {"port", required_argument, 0, 'p'},
+ {0, 0, 0, 0}
+ };
+ int option_index = 0;
+ c = getopt_long(argc, argv, "i:p:", long_options, &option_index);
+ if (c == -1) { break; }
+ switch (c) {
+ case 0:
+ if (long_options[option_index].flag != 0) { break; }
+ printf ("option %s", long_options[option_index].name);
+ if (optarg) { printf(" with arg %s", optarg); }
+ printf("\n");
+ break;
+ case 'i':
+ ipaddress = optarg;
+ break;
+ case 'p':
+ port = atoi(optarg);
+ break;
+ case '?':
+ /* getopt_long already printed an error message. */
+ break;
+ default:
+ abort();
+ }
+ }
+ if (ipaddress == NULL) {
+ ipaddress = "0.0.0.0";
+ }
+
+ struct hostent *hp;
+ struct sockaddr_in sin;
+ struct sockaddr_in pin;
+ int sd;
+
+ if ((hp = gethostbyname(ipaddress)) == 0) {
+ perror("gethostbyname");
+ exit(1);
+ }
+
+ memset(&pin, 0, sizeof(pin));
+ pin.sin_family = AF_INET;
+ pin.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr;
+ pin.sin_port = htons(port);
+
+ if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ perror("socket");
+ exit(1);
+ }
+
+ if (connect(sd,(struct sockaddr *) &pin, sizeof(pin)) == -1) {
+ perror("connect");
+ exit(1);
+ }
+
+
+ time_t seconds;
+ time(&seconds);
+ srand((unsigned int) seconds);
+
+ int n = 1;
+ int list[1001];
+ while (n < 1000) {
+ list[n] = rand() % (HIGH - LOW + 1) + LOW;
+ n++;
+ }
+
+ n = 1;
+ while (n < 1000) {
+ char msg[32];
+ sprintf(msg, "update %d 1\r\n", list[n]);
+ printf("Sending command 'update %d 1' ... ", list[n]);
+ send_command(sd, msg);
+ n++;
+ }
+ send_command(sd, "stats\r\n");
+
+ printf("Client-Closing sockfd\n");
+ close(sd);
+
+ return 0;
+}
+
+void send_command(int sd, char *command) {
+ if (send(sd, command, strlen(command), 0) == -1) {
+ perror("send");
+ exit(1);
+ }
+ char buf[300];
+ int numbytes;
+ if((numbytes = recv(sd, buf, 300-1, 0)) == -1) {
+ perror("recv()");
+ exit(1);
+ }
+ buf[numbytes] = '\0';
+ printf("Client-Received: %s", buf);
+}
View
17 src/scores.c
@@ -171,15 +171,22 @@ PoolNode *preparePromotion(PoolNode *head, int item, int score) {
if ((listMatch = pool_find(head, find_by_score, (void*)score))) {
if (listMatch->count == 1) {
assert(pool_remove(head, listMatch) == 0);
- return 1;
} else {
- if ((memberMatch = member_find(listMatch->members, find_item, (void*)item))) {
+ if (listMatch->members->item == item) {
+ memberMatch = listMatch->members;
+ listMatch->members = memberMatch->next;
listMatch->count -= 1;
- assert(member_remove(listMatch->members, memberMatch) == 0);
- return 1;
+ } else {
+ if ((memberMatch = member_find(listMatch->members, find_item, (void*)item))) {
+ listMatch->count -= 1;
+ assert(member_remove(listMatch->members, memberMatch) == 0);
+ } else {
+ assert(1);
+ }
}
- return 0;
}
+ } else {
+ assert(1);
}
}
return head;
Please sign in to comment.
Something went wrong with that request. Please try again.