about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2023-01-09 07:33:34 +0000
committerEric Wong <e@80x24.org>2023-01-09 07:35:59 +0000
commit4dc4efe067e6d09efee3e223558f303f6e4091f0 (patch)
treeacf5b838d0f1769eb349258a9fc6ad23f0abf09a
parent2c7547e929737f28069eeea2b3cd4009a4f7867c (diff)
parent458c801f687fc102984fba591112b492e89c067c (diff)
downloadmwrap-4dc4efe067e6d09efee3e223558f303f6e4091f0.tar.gz
Mostly a few portability bits, but a few httpd UI/UX
tweaks as well.
-rw-r--r--httpd.h30
-rw-r--r--mwrap_core.h166
-rw-r--r--mymalloc.h2
-rw-r--r--t/httpd.t23
-rw-r--r--t/test_common.perl20
5 files changed, 180 insertions, 61 deletions
diff --git a/httpd.h b/httpd.h
index 89e366e..ef4d83c 100644
--- a/httpd.h
+++ b/httpd.h
@@ -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 &lt;= 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 &gt;%lu"
                         "</title></head><body><p>mwrap each &gt;%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 */
diff --git a/mymalloc.h b/mymalloc.h
index 6b5a22d..196ccc0 100644
--- a/mymalloc.h
+++ b/mymalloc.h
@@ -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;
 
diff --git a/t/httpd.t b/t/httpd.t
index bf9100e..76fe7d1 100644
--- a/t/httpd.t
+++ b/t/httpd.t
@@ -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)) {