diff options
author | Eric Wong <e@80x24.org> | 2023-01-09 07:33:34 +0000 |
---|---|---|
committer | Eric Wong <e@80x24.org> | 2023-01-09 07:35:59 +0000 |
commit | 4dc4efe067e6d09efee3e223558f303f6e4091f0 (patch) | |
tree | acf5b838d0f1769eb349258a9fc6ad23f0abf09a | |
parent | 2c7547e929737f28069eeea2b3cd4009a4f7867c (diff) | |
parent | 458c801f687fc102984fba591112b492e89c067c (diff) | |
download | mwrap-4dc4efe067e6d09efee3e223558f303f6e4091f0.tar.gz |
Mostly a few portability bits, but a few httpd UI/UX tweaks as well.
-rw-r--r-- | httpd.h | 30 | ||||
-rw-r--r-- | mwrap_core.h | 166 | ||||
-rw-r--r-- | mymalloc.h | 2 | ||||
-rw-r--r-- | t/httpd.t | 23 | ||||
-rw-r--r-- | t/test_common.perl | 20 |
5 files changed, 180 insertions, 61 deletions
@@ -38,7 +38,11 @@ #include "picohttpparser_c.h" #include <pthread.h> #include <stdbool.h> -#define URL "https://80x24.org/mwrap-perl.git/about" +#if MWRAP_PERL +# define URL "https://80x24.org/mwrap-perl.git/" +#else +# define URL "https://80x24.org/mwrap.git/" +#endif #define TYPE_HTML "text/html; charset=UTF-8" #define TYPE_CSV "text/csv" #define TYPE_PLAIN "text/plain" @@ -548,6 +552,9 @@ static void show_stats(FILE *fp) "/ files: %zu / locations: %zu", inc , inc - dec, uatomic_read(&nr_file), uatomic_read(&nr_src_loc)); +#if MWRAP_RUBY + fprintf(fp, " / GC: %zu", uatomic_read(&last_gc_count)); +#endif } /* /$PID/at/$LOCATION endpoint */ @@ -593,7 +600,12 @@ static enum mw_qev each_at(struct mw_h1 *h1, struct mw_h1req *h1r) size, h->as.live.gen, h->real); } rcu_read_unlock(); - FPUTS("</table></body></html>", fp); + FPUTS("</table><pre>\nNotes:\n" +"* 16344-byte (64-bit) or 16344-byte (32-bit) allocations in\n" +" Ruby <= 3.0 aligned to 0x4000 are likely for object heap slots.\n" +"* 4080-byte allocations in Perl 5 are likely for arenas\n" +" (set via the PERL_ARENA_SIZE compile-time macro)" +"</pre></body></html>", fp); return h1_200(h1, &html, TYPE_HTML); } @@ -602,8 +614,13 @@ static enum mw_qev each_gt(struct mw_h1 *h1, struct mw_h1req *h1r, unsigned long min, bool csv) { static const char default_sort[] = "bytes"; - const char *sort = default_sort; - size_t sort_len = sizeof(default_sort) - 1; + const char *sort; + size_t sort_len = 0; + + if (!csv) { + sort = default_sort; + sort_len = sizeof(default_sort) - 1; + } if (h1r->qstr && h1r->qlen > 5 && !memcmp(h1r->qstr, "sort=", 5)) { sort = h1r->qstr + 5; @@ -637,7 +654,8 @@ static enum mw_qev each_gt(struct mw_h1 *h1, struct mw_h1req *h1r, fprintf(fp, "<html><head><title>mwrap each >%lu" "</title></head><body><p>mwrap each >%lu " "(change `%lu' in URL to adjust filtering) - " - "MWRAP=bt:%u", min, min, min, depth); + "MWRAP=bt:%u <a href=\"%lu.csv\">.csv</a>", + min, min, min, depth, min); show_stats(fp); /* need borders to distinguish multi-level traces */ if (depth) @@ -1145,7 +1163,7 @@ static void h1d_unlink(struct mw_h1d *h1d, bool do_close) static int h1d_init(struct mw_h1d *h1d, const char *menv) { union mw_sockaddr sa = { .un = { .sun_family = AF_UNIX } }; -#ifdef HAS_SOCKADDR_SA_LEN +#if defined(HAS_SOCKADDR_SA_LEN) || defined(HAVE_STRUCT_SOCKADDR_UN_SUN_LEN) sa.un.sun_len = (unsigned char)sizeof(struct sockaddr_un); #endif const char *env = strstr(menv, "socket_dir:"); diff --git a/mwrap_core.h b/mwrap_core.h index 02b60f3..15df857 100644 --- a/mwrap_core.h +++ b/mwrap_core.h @@ -9,8 +9,8 @@ # define MWRAP_PERL 0 #endif -#if !MWRAP_PERL -typedef void COP; +#ifndef MWRAP_RUBY +# define MWRAP_RUBY 0 #endif /* set a sensible max to avoid stack overflows */ @@ -18,20 +18,13 @@ typedef void COP; # define MWRAP_BT_MAX 32 #endif - -#if MWRAP_PERL -# include "EXTERN.h" -# include "perl.h" -# include "XSUB.h" -# include "embed.h" -# include "ppport.h" +#ifndef _GNU_SOURCE +# define _GNU_SOURCE #endif - #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <dlfcn.h> #include <assert.h> #include <errno.h> #include <sys/types.h> @@ -44,6 +37,14 @@ typedef void COP; #include <urcu/rculist.h> #include <limits.h> +#if MWRAP_PERL +# include "EXTERN.h" +# include "perl.h" +# include "XSUB.h" +# include "embed.h" +# include "ppport.h" +#endif + /* * XXH3 (truncated to 32-bits) seems to provide a ~2% speedup. * XXH32 doesn't show improvements over jhash despite rculfhash @@ -75,12 +76,24 @@ static uint32_t bt_req_depth; #if MWRAP_PERL extern pthread_key_t __attribute__((weak)) PL_thr_key; extern const char __attribute__((weak)) PL_memory_wrap[]; /* needed for -O0 */ -#endif +# if !defined(PERL_IMPLICIT_CONTEXT) +static size_t *root_locating; /* determines if PL_curcop is our thread */ +# endif +#endif /* MWRAP_PERL */ +#if MWRAP_RUBY +static void mw_ruby_set_generation(size_t *, size_t); +# define SET_GENERATION(gen, size) mw_ruby_set_generation(gen, size) +static size_t last_gc_count; /* for httpd which runs in a non-GVL thread */ +#endif /* MWRAP_RUBY */ + +#ifndef SET_GENERATION /* C-only builds w/o Perl|Ruby */ +# define SET_GENERATION(gen, size) \ + *gen = uatomic_add_return(&total_bytes_inc, size) +#endif /* !SET_GENERATION */ + +/* generic stuff: */ static MWRAP_TSD size_t locating; -#if MWRAP_PERL && !defined(PERL_IMPLICIT_CONTEXT) -static size_t *root_locating; /* determines if PL_curcop is our thread */ -#endif static struct cds_lfht *files, *totals; union padded_mutex { pthread_mutex_t mtx; @@ -142,6 +155,7 @@ static void *my_mempcpy(void *dest, const void *src, size_t n) /* * only for interpreted sources (Perl/Ruby/etc), not backtrace_symbols* files + * Allocated via real_malloc / real_free */ struct src_file { struct cds_lfht_node nd; /* <=> files table */ @@ -179,7 +193,7 @@ struct alloc_hdr { struct cds_list_head anode; /* <=> src_loc.allocs */ union { struct { - size_t gen; /* global age */ + size_t gen; /* global age || rb_gc_count() */ struct src_loc *loc; } live; struct rcu_head dead; @@ -319,20 +333,6 @@ again: return l; } -static const COP *mwp_curcop(void) -{ -#if MWRAP_PERL - if (&PL_thr_key) { /* are we even in a Perl process? */ -# ifdef PERL_IMPLICIT_CONTEXT - if (aTHX) return PL_curcop; -# else /* !PERL_IMPLICIT_CONTEXT */ - if (&locating == root_locating) return PL_curcop; -# endif /* PERL_IMPLICIT_CONTEXT */ - } -#endif /* MWRAP_PERL */ - return NULL; -} - static uint32_t do_hash(const void *p, size_t len) { #if defined(XXH3_64bits) @@ -372,18 +372,43 @@ static struct src_file *src_file_get(struct cds_lfht *t, struct src_file *k, return cur ? caa_container_of(cur, struct src_file, nd) : NULL; } -#if !MWRAP_PERL -# define CopFILE(cop) NULL -# define CopLINE(cop) 0 -#endif -static struct src_loc *assign_line(size_t size, const COP *cop, - struct src_loc *sl) +#if MWRAP_PERL +static const COP *mwp_curcop(void) { - /* avoid vsnprintf or anything which could call malloc here: */ + if (&PL_thr_key) { /* are we even in a Perl process? */ +# ifdef PERL_IMPLICIT_CONTEXT + if (aTHX) return PL_curcop; +# else /* !PERL_IMPLICIT_CONTEXT */ + if (&locating == root_locating) return PL_curcop; +# endif /* PERL_IMPLICIT_CONTEXT */ + } + return NULL; +} + +static const char *mw_perl_src_file_cstr(unsigned *lineno) +{ + const COP *cop = mwp_curcop(); if (!cop) return NULL; const char *fn = CopFILE(cop); if (!fn) return NULL; - unsigned lineno = CopLINE(cop); + *lineno = CopLINE(cop); + return fn; +} +# define SRC_FILE_CSTR(lineno) mw_perl_src_file_cstr(lineno) +#endif /* MWRAP_PERL */ + +#if MWRAP_RUBY +static const char *mw_ruby_src_file_cstr(unsigned *lineno); +# define SRC_FILE_CSTR(lineno) mw_ruby_src_file_cstr(lineno) +#endif /* MWRAP_RUBY */ + +#ifndef SRC_FILE_CSTR /* for C-only compilation */ +# define SRC_FILE_CSTR(lineno) (NULL) +#endif /* !SRC_FILE_CSTR */ + +static struct src_loc *assign_line(size_t size, struct src_loc *sl, + const char *fn, unsigned lineno) +{ struct src_file *f; union stk_sf sf; struct cds_lfht_node *cur; @@ -430,7 +455,7 @@ again: } static struct src_loc * -update_stats_rcu_lock(size_t *generation, size_t size, struct src_loc *sl) +update_stats_rcu_lock(size_t *gen, size_t size, struct src_loc *sl) { struct cds_lfht *t = CMM_LOAD_SHARED(totals); struct src_loc *ret = NULL; @@ -438,11 +463,15 @@ update_stats_rcu_lock(size_t *generation, size_t size, struct src_loc *sl) if (caa_unlikely(!t)) return 0; /* not initialized */ if (locating++) goto out; /* do not recurse into another *alloc */ - *generation = uatomic_add_return(&total_bytes_inc, size); - const COP *cop = mwp_curcop(); + SET_GENERATION(gen, size); + + unsigned lineno; + const char *fn = SRC_FILE_CSTR(&lineno); + rcu_read_lock(); - ret = assign_line(size, cop, sl); - if (!ret) { /* no associated Perl code, just C/C++ */ + if (fn) + ret = assign_line(size, sl, fn, lineno); + if (!ret) { /* no associated Perl|Ruby code, just C/C++ */ sl->total = size; sl->f = NULL; sl->lineno = 0; @@ -1012,3 +1041,54 @@ __attribute__((constructor)) static void mwrap_ctor(void) } --locating; } + +#if MWRAP_RUBY +# undef _GNU_SOURCE /* ruby.h redefines it */ +# include <ruby.h> /* defines HAVE_RUBY_RACTOR_H on 3.0+ */ +# include <ruby/thread.h> +# include <ruby/io.h> +# ifdef HAVE_RUBY_RACTOR_H /* Ruby 3.0+ */ +extern MWRAP_TSD void * __attribute__((weak)) ruby_current_ec; +# else /* Ruby 2.6-2.7 */ +extern void * __attribute__((weak)) ruby_current_execution_context_ptr; +# define ruby_current_ec ruby_current_execution_context_ptr +# endif /* HAVE_RUBY_RACTOR_H */ + +extern void * __attribute__((weak)) ruby_current_vm_ptr; /* for rb_gc_count */ +extern size_t __attribute__((weak)) rb_gc_count(void); +int __attribute__((weak)) ruby_thread_has_gvl_p(void); + +const char *rb_source_location_cstr(int *line); /* requires 2.6.0dev or later */ +/* + * rb_source_location_cstr relies on GET_EC(), and it's possible + * to have a native thread but no EC during the early and late + * (teardown) phases of the Ruby process + */ +static int has_ec_p(void) +{ + return ruby_thread_has_gvl_p && ruby_thread_has_gvl_p() && + ruby_current_vm_ptr && ruby_current_ec; +} + +static void mw_ruby_set_generation(size_t *gen, size_t size) +{ + if (rb_gc_count) { + uatomic_add_return(&total_bytes_inc, size); + if (has_ec_p()) { + *gen = rb_gc_count(); + uatomic_set(&last_gc_count, *gen); + } + } else { + *gen = uatomic_add_return(&total_bytes_inc, size); + } +} + +static const char *mw_ruby_src_file_cstr(unsigned *lineno) +{ + if (!has_ec_p()) return NULL; + int line; + const char *fn = rb_source_location_cstr(&line); + *lineno = line < 0 ? UINT_MAX : (unsigned)line; + return fn; +} +#endif /* !MWRAP_RUBY */ @@ -100,6 +100,8 @@ static void *my_mmap(size_t size) #endif #include "dlmalloc_c.h" #undef ABORT /* conflicts with Perl */ +#undef NOINLINE /* conflicts with Ruby, defined by dlmalloc_c.h */ +#undef HAVE_MREMAP /* conflicts with Ruby 3.2 */ static MWRAP_TSD mstate ms_tsd; @@ -11,8 +11,10 @@ my $f1 = "$mwrap_tmp/f1"; my $f2 = "$mwrap_tmp/f2"; mkfifo($f1, 0600) // plan(skip_all => "mkfifo: $!"); mkfifo($f2, 0600) // plan(skip_all => "mkfifo: $!"); -my $pid = mwrap_run('httpd test', $env, '-e', - "open my \$f1, '>', '$f1'; close \$f1; open my \$f2, '<', '$f2'"); +my $src = $mwrap_src ? # $mwrap_src is Perl-only, Ruby otherwise + "open my \$f1, '>', '$f1'; close \$f1; open my \$f2, '<', '$f2'" : + "File.open('$f1', 'w').close; File.open('$f2', 'r').close"; +my $pid = mwrap_run('httpd test', $env, '-e', $src); my $spid; my $mw_exit; my $cleanup = sub { @@ -85,9 +87,21 @@ SKIP: { } SKIP: { + my (@rproxy, @missing); + if (-e 'script/mwrap-rproxy') { # Perl version + @rproxy = ($^X, '-w', './blib/script/mwrap-rproxy'); + } else { + my $exe = `which mwrap-rproxy`; + if ($? == 0 && defined($exe)) { + chomp($rproxy[0] = $exe); + } else { + push @missing, 'mwrap-rproxy'; + } + } for my $m (qw(Plack::Util HTTP::Tiny)) { - eval "require $m" or skip "$m missing", 1; + eval "require $m" or push(@missing, $m); } + skip join(', ', @missing).' missing', 1 if @missing; my $srv = IO::Socket::INET->new(LocalAddr => '127.0.0.1', ReuseAddr => 1, Proto => 'tcp', Type => SOCK_STREAM, @@ -103,8 +117,7 @@ SKIP: { } local $ENV{PLACK_ENV} = 'deployment' if !$ENV{V}; no warnings 'exec'; - exec $^X, '-w', './blib/script/mwrap-rproxy', - "--socket-dir=$mwrap_tmp"; + exec @rproxy, "--socket-dir=$mwrap_tmp"; _exit(1); } my $http = HTTP::Tiny->new; diff --git a/t/test_common.perl b/t/test_common.perl index 8827362..3a073cf 100644 --- a/t/test_common.perl +++ b/t/test_common.perl @@ -6,8 +6,9 @@ use v5.12; use parent qw(Exporter); use Test::More; use File::Temp 0.19 (); # 0.19 for ->newdir -our $mwrap_src = slurp('blib/script/mwrap-perl'); -our $mwrap_tmp = File::Temp->newdir('mwrap-perl-XXXX', TMPDIR => 1); +our $mwrap_src; +$mwrap_src = slurp('blib/script/mwrap-perl') if -e 'script/mwrap-perl'; +our $mwrap_tmp = File::Temp->newdir('mwrap-XXXX', TMPDIR => 1); our $mwrap_out = "$mwrap_tmp/out"; our $mwrap_err = "$mwrap_tmp/err"; our @EXPORT = qw(mwrap_run slurp $mwrap_err $mwrap_out $mwrap_src $mwrap_tmp); @@ -20,9 +21,6 @@ sub slurp { sub mwrap_run { my ($msg, $env, @args) = @_; - unless (grep(/\A-.+\bMwrap\b/, @args)) { - unshift @args, '-MDevel::Mwrap'; - } my $pid = fork; if ($pid == 0) { while (my ($k, $v) = each %$env) { @@ -30,8 +28,16 @@ sub mwrap_run { } open STDERR, '>', $mwrap_err or die "open: $!"; open STDOUT, '>', $mwrap_out or die "open: $!"; - @ARGV = ($^X, @args); - eval $mwrap_src; + if (defined $mwrap_src) { + unless (grep(/\A-.+\bMwrap\b/, @args)) { + unshift @args, '-MDevel::Mwrap'; + } + @ARGV = ($^X, @args); + eval $mwrap_src; + } else { + my $ruby = $ENV{RUBY} // 'ruby'; + exec $ruby, '-Ilib', 'bin/mwrap', $ruby, @args; + } die "fail: $! ($@)"; } if (defined(wantarray)) { |