mwrap (Perl version) user+dev discussion/patches/pulls/bugs/help
 help / color / mirror / code / Atom feed
From: Eric Wong <e@80x24.org>
To: mwrap-perl@80x24.org
Subject: [PATCH 1/4] support MWRAP=dump_csv:$FILENAME parameter
Date: Wed, 11 Jan 2023 01:12:46 +0000	[thread overview]
Message-ID: <20230111011249.2713039-2-e@80x24.org> (raw)
In-Reply-To: <20230111011249.2713039-1-e@80x24.org>

Just reusing code from httpd.
---
 httpd.h           | 217 ++++++++++++++++++++++++----------------------
 mwrap_core.h      |  35 ++++++--
 script/mwrap-perl |  12 ++-
 t/mwrap.t         |  21 ++++-
 4 files changed, 173 insertions(+), 112 deletions(-)

diff --git a/httpd.h b/httpd.h
index ef4d83c..9219d36 100644
--- a/httpd.h
+++ b/httpd.h
@@ -504,9 +504,11 @@ static off_t write_loc_name(FILE *fp, const struct src_loc *l)
 	return end - beg;
 }
 
-static struct h1_src_loc *accumulate(unsigned long min, size_t *hslc, FILE *lp)
+static struct h1_src_loc *
+accumulate(struct mw_fbuf *lb, unsigned long min, size_t *hslc)
 {
 	struct mw_fbuf fb;
+	if (!fbuf_init(lb)) return NULL;
 	if (!fbuf_init(&fb)) return NULL;
 	rcu_read_lock();
 	struct cds_lfht *t = CMM_LOAD_SHARED(totals);
@@ -528,18 +530,23 @@ static struct h1_src_loc *accumulate(unsigned long min, size_t *hslc, FILE *lp)
 			HUGE_VAL;
 		hsl.max_life = uatomic_read(&l->max_lifespan);
 		hsl.sl = l;
-		hsl.lname_len = write_loc_name(lp, l);
+		hsl.lname_len = write_loc_name(lb->fp, l);
 		fwrite(&hsl, sizeof(hsl), 1, fb.fp);
 	}
 	rcu_read_unlock();
 
-	struct h1_src_loc *hslv;
-	if (fbuf_close(&fb)) {
-		hslv = NULL;
-	} else {
-		*hslc = fb.len / sizeof(*hslv);
-		mwrap_assert((fb.len % sizeof(*hslv)) == 0);
-		hslv = (struct h1_src_loc *)fb.ptr;
+	if (fbuf_close(&fb) || fbuf_close(lb))
+		return NULL;
+
+	struct h1_src_loc *hslv = (struct h1_src_loc *)fb.ptr;
+	*hslc = fb.len / sizeof(*hslv);
+	mwrap_assert((fb.len % sizeof(*hslv)) == 0);
+	char *n = lb->ptr;
+	for (size_t i = 0; i < *hslc; ++i) {
+		hslv[i].loc_name = n;
+		n += hslv[i].lname_len;
+		if (hslv[i].lname_len < 0)
+			return NULL;
 	}
 	return hslv;
 }
@@ -609,124 +616,128 @@ static enum mw_qev each_at(struct mw_h1 *h1, struct mw_h1req *h1r)
 	return h1_200(h1, &html, TYPE_HTML);
 }
 
-/* /$PID/each/$MIN endpoint */
-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;
-	size_t sort_len = 0;
+typedef int (*cmp_fn)(const void *, const void *);
 
-	if (!csv) {
-		sort = default_sort;
-		sort_len = sizeof(default_sort) - 1;
+static cmp_fn write_csv_header(FILE *fp, const char *sort, size_t sort_len)
+{
+	cmp_fn cmp = NULL;
+	for (size_t i = 0; i < CAA_ARRAY_SIZE(fields); i++) {
+		const char *fn = fields[i].fname;
+		if (i)
+			fputc(',', fp);
+		fputs(fn, fp);
+		if (fields[i].flen == sort_len && !memcmp(fn, sort, sort_len))
+			cmp = fields[i].cmp;
 	}
+	fputc('\n', fp);
+	return cmp;
+}
 
-	if (h1r->qstr && h1r->qlen > 5 && !memcmp(h1r->qstr, "sort=", 5)) {
-		sort = h1r->qstr + 5;
-		sort_len = h1r->qlen - 5;
+static void write_csv_data(FILE *fp, struct h1_src_loc *hslv, size_t hslc)
+{
+	for (size_t i = 0; i < hslc; i++) {
+		struct h1_src_loc *hsl = &hslv[i];
+
+		fprintf(fp, "%zu,%zu,%zu,%zu,%0.3f,%zu,",
+			hsl->bytes, hsl->allocations, hsl->frees,
+			hsl->live, hsl->mean_life, hsl->max_life);
+		write_q_csv(fp, hsl->loc_name, hsl->lname_len);
+		fputc('\n', fp);
 	}
+}
 
-	size_t hslc;
+static void *write_csv(FILE *fp, size_t min, const char *sort, size_t sort_len)
+{
 	AUTO_CLOFREE struct mw_fbuf lb;
-	if (!fbuf_init(&lb)) return h1_close(h1);
-	AUTO_FREE struct h1_src_loc *hslv = accumulate(min, &hslc, lb.fp);
-	if (!hslv)
-		return h1_close(h1);
+	size_t hslc;
+	AUTO_FREE struct h1_src_loc *hslv = accumulate(&lb, min, &hslc);
+	if (!hslv) return NULL;
 
-	if (fbuf_close(&lb))
-		return h1_close(h1);
+	cmp_fn cmp = write_csv_header(fp, sort, sort_len);
+	if (cmp)
+		qsort(hslv, hslc, sizeof(*hslv), cmp);
+	write_csv_data(fp, hslv, hslc);
+	return fp;
+}
 
-	char *n = lb.ptr;
-	for (size_t i = 0; i < hslc; ++i) {
-		hslv[i].loc_name = n;
-		n += hslv[i].lname_len;
-		if (hslv[i].lname_len < 0)
-			return h1_close(h1);
+/* /$PID/each/$MIN endpoint */
+static enum mw_qev each_gt(struct mw_h1 *h1, struct mw_h1req *h1r,
+				size_t min, bool csv)
+{
+	static const char default_sort[] = "bytes";
+	const char *sort = csv ? NULL : default_sort;
+	size_t sort_len = csv ? 0 : (sizeof(default_sort) - 1);
+
+	if (h1r->qstr && h1r->qlen > 5 && !memcmp(h1r->qstr, "sort=", 5)) {
+		sort = h1r->qstr + 5;
+		sort_len = h1r->qlen - 5;
 	}
 
 	struct mw_fbuf bdy;
 	FILE *fp = wbuf_init(&bdy);
 	if (!fp) return h1_close(h1);
-
-	if (!csv) {
-		unsigned depth = (unsigned)CMM_LOAD_SHARED(bt_req_depth);
-		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 <a href=\"%lu.csv\">.csv</a>",
-			min, min, min, depth, min);
-		show_stats(fp);
-		/* need borders to distinguish multi-level traces */
-		if (depth)
-			FPUTS("<table\nborder=1><tr>", fp);
-		else /* save screen space if only tracing one line */
-			FPUTS("<table><tr>", fp);
+	if (csv) {
+		if (write_csv(fp, min, sort, sort_len))
+			return h1_200(h1, &bdy, TYPE_CSV);
+		return h1_close(h1);
 	}
 
-	int (*cmp)(const void *, const void *) = NULL;
-	if (csv) {
-		for (size_t i = 0; i < CAA_ARRAY_SIZE(fields); i++) {
-			const char *fn = fields[i].fname;
-			if (i)
-				fputc(',', fp);
-			fputs(fn, fp);
-			if (fields[i].flen == sort_len &&
-					!memcmp(fn, sort, sort_len))
-				cmp = fields[i].cmp;
-		}
-		fputc('\n', fp);
-	} else {
-		for (size_t i = 0; i < CAA_ARRAY_SIZE(fields); i++) {
-			const char *fn = fields[i].fname;
-			FPUTS("<th>", fp);
-			if (fields[i].flen == sort_len &&
-					!memcmp(fn, sort, sort_len)) {
-				cmp = fields[i].cmp;
-				fprintf(fp, "<b>%s</b>", fields[i].fname);
-			} else {
-				fprintf(fp, "<a\nhref=\"./%lu?sort=%s\">%s</a>",
-					min, fn, fn);
-			}
-			FPUTS("</th>", fp);
+	size_t hslc;
+	AUTO_CLOFREE struct mw_fbuf lb;
+	AUTO_FREE struct h1_src_loc *hslv = accumulate(&lb, min, &hslc);
+	if (!hslv)
+		return h1_close(h1);
+
+	unsigned depth = (unsigned)CMM_LOAD_SHARED(bt_req_depth);
+	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 <a href=\"%lu.csv\">.csv</a>",
+		min, min, min, depth, min);
+	show_stats(fp);
+	/* need borders to distinguish multi-level traces */
+	if (depth)
+		FPUTS("<table\nborder=1><tr>", fp);
+	else /* save screen space if only tracing one line */
+		FPUTS("<table><tr>", fp);
+	cmp_fn cmp = NULL;
+	for (size_t i = 0; i < CAA_ARRAY_SIZE(fields); i++) {
+		const char *fn = fields[i].fname;
+		FPUTS("<th>", fp);
+		if (fields[i].flen == sort_len &&
+				!memcmp(fn, sort, sort_len)) {
+			cmp = fields[i].cmp;
+			fprintf(fp, "<b>%s</b>", fields[i].fname);
+		} else {
+			fprintf(fp, "<a\nhref=\"./%lu?sort=%s\">%s</a>",
+				min, fn, fn);
 		}
+		FPUTS("</th>", fp);
 	}
-	if (!csv)
-		FPUTS("</tr>", fp);
+	FPUTS("</tr>", fp);
 	if (cmp)
 		qsort(hslv, hslc, sizeof(*hslv), cmp);
-	else if (!csv)
+	else
 		FPUTS("<tr><td>sort= not understood</td></tr>", fp);
-	if (csv) {
-		for (size_t i = 0; i < hslc; i++) {
-			struct h1_src_loc *hsl = &hslv[i];
 
-			fprintf(fp, "%zu,%zu,%zu,%zu,%0.3f,%zu,",
-				hsl->bytes, hsl->allocations, hsl->frees,
-				hsl->live, hsl->mean_life, hsl->max_life);
-			write_q_csv(fp, hsl->loc_name, hsl->lname_len);
-			fputc('\n', fp);
-		}
-	} else {
-		for (size_t i = 0; i < hslc; i++) {
-			struct h1_src_loc *hsl = &hslv[i];
+	for (size_t i = 0; i < hslc; i++) {
+		struct h1_src_loc *hsl = &hslv[i];
 
-			fprintf(fp, "<tr><td>%zu</td><td>%zu</td><td>%zu</td>"
-				"<td>%zu</td><td>%0.3f</td><td>%zu</td>",
-				hsl->bytes, hsl->allocations, hsl->frees,
-				hsl->live, hsl->mean_life, hsl->max_life);
-			FPUTS("<td><a\nhref=\"../at/", fp);
+		fprintf(fp, "<tr><td>%zu</td><td>%zu</td><td>%zu</td>"
+			"<td>%zu</td><td>%0.3f</td><td>%zu</td>",
+			hsl->bytes, hsl->allocations, hsl->frees,
+			hsl->live, hsl->mean_life, hsl->max_life);
+		FPUTS("<td><a\nhref=\"../at/", fp);
 
-			write_b64_url(fp, src_loc_hash_tip(hsl->sl),
-					src_loc_hash_len(hsl->sl));
+		write_b64_url(fp, src_loc_hash_tip(hsl->sl),
+				src_loc_hash_len(hsl->sl));
 
-			FPUTS("\">", fp);
-			write_html(fp, hsl->loc_name, hsl->lname_len);
-			FPUTS("</a></td></tr>", fp);
-		}
-		FPUTS("</table></body></html>", fp);
+		FPUTS("\">", fp);
+		write_html(fp, hsl->loc_name, hsl->lname_len);
+		FPUTS("</a></td></tr>", fp);
 	}
-	return h1_200(h1, &bdy, csv ? TYPE_CSV : TYPE_HTML);
+	FPUTS("</table></body></html>", fp);
+	return h1_200(h1, &bdy, TYPE_HTML);
 }
 
 /* /$PID/ root endpoint */
@@ -781,7 +792,7 @@ static enum mw_qev h1_dispatch(struct mw_h1 *h1, struct mw_h1req *h1r)
 		if ((c = PATH_SKIP(h1r, "/each/"))) {
 			errno = 0;
 			char *e;
-			unsigned long min = strtoul(c, &e, 10);
+			size_t min = (size_t)strtoul(c, &e, 10);
 			if (!errno) {
 				if (*e == ' ' || *e == '?')
 					return each_gt(h1, h1r, min, false);
diff --git a/mwrap_core.h b/mwrap_core.h
index deb3bb3..fff0538 100644
--- a/mwrap_core.h
+++ b/mwrap_core.h
@@ -732,6 +732,7 @@ enomem:
 struct dump_arg {
 	FILE *fp;
 	size_t min;
+	bool dump_csv;
 };
 
 char **bt_syms(void * const *addrlist, uint32_t size)
@@ -754,12 +755,16 @@ static void cleanup_free(void *any)
 	free(*p);
 }
 
+static void *write_csv(FILE *, size_t min, const char *sort, size_t sort_len);
 static void *dump_to_file(struct dump_arg *a)
 {
 	struct cds_lfht_iter iter;
 	struct src_loc *l;
 	struct cds_lfht *t;
 
+	if (a->dump_csv)
+		return write_csv(a->fp, a->min, NULL, 0);
+
 	++locating;
 	rcu_read_lock();
 	t = CMM_LOAD_SHARED(totals);
@@ -857,7 +862,7 @@ __attribute__ ((destructor)) static void mwrap_dtor(void)
 {
 	const char *opt = getenv("MWRAP");
 	const char *modes[] = { "a", "a+", "w", "w+", "r+" };
-	struct dump_arg a = { .min = 0 };
+	struct dump_arg a = { .min = 0, .dump_csv = false };
 	size_t i;
 	int dump_fd;
 	char *dump_path;
@@ -870,9 +875,24 @@ __attribute__ ((destructor)) static void mwrap_dtor(void)
 		return;
 
 	++locating;
-	if ((dump_path = strstr(opt, "dump_path:")) &&
-			(dump_path += sizeof("dump_path")) &&
-			*dump_path) {
+
+	/* parse dump_csv:$PATHNAME */
+	if ((dump_path = strstr(opt, "dump_csv:"))) {
+		dump_path += sizeof("dump_csv");
+		if (!*dump_path)
+			dump_path = NULL;
+		else
+			a.dump_csv = true;
+	}
+	if (!dump_path) {
+		/* parse dump_path:$PATHNAME */
+		if ((dump_path = strstr(opt, "dump_path:"))) {
+			dump_path += sizeof("dump_path");
+			if (!*dump_path)
+				dump_path = NULL;
+		}
+	}
+	if (dump_path) {
 		char *end = strchr(dump_path, ',');
 		char buf[PATH_MAX];
 		if (end) {
@@ -887,10 +907,13 @@ __attribute__ ((destructor)) static void mwrap_dtor(void)
 			fprintf(stderr, "open %s failed: %m\n", dump_path);
 			goto out;
 		}
-	}
-	else if (!sscanf(opt, "dump_fd:%d", &dump_fd))
+	} else if ((s = strstr(opt, "dump_fd:")) &&
+			!sscanf(s, "dump_fd:%d", &dump_fd))
 		goto out;
 
+	/* allow dump_csv standalone for dump_fd */
+	if (!a.dump_csv && strstr(opt, "dump_csv"))
+		a.dump_csv = true;
 	if ((s = strstr(opt, "dump_min:")))
 		sscanf(s, "dump_min:%zu", &a.min);
 
diff --git a/script/mwrap-perl b/script/mwrap-perl
index 182b0bd..eb29176 100644
--- a/script/mwrap-perl
+++ b/script/mwrap-perl
@@ -76,12 +76,20 @@ Dumps the output at exit to a given filename:
 
 	total_bytes	call_count	location
 
-In the future, dumping to a self-describing CSV will be supported.
-
 =item dump_fd:$DESCRIPTOR
 
 As with dump_path, but dumps the output to a given file descriptor.
 
+=item dump_csv:$FILENAME
+
+Dump CSV to the given filename.
+
+This output matches the HTTP server output and includes column headers,
+but is subject to change in future releases.
+
+C<dump_csv> without the C<:> may also be used in conjunction with
+C<dump_fd>, such as C<MWRAP=dump_fd:2,dump_csv>.
+
 =back
 
 =head1 HTTP POST API
diff --git a/t/mwrap.t b/t/mwrap.t
index 6f99715..ccd739b 100644
--- a/t/mwrap.t
+++ b/t/mwrap.t
@@ -9,7 +9,8 @@ my $dump = "$mwrap_tmp/dump";
 {
 	my $env = { MWRAP => "dump_path:$dump,dump_min:10000" };
 	my $nr = 1000;
-	mwrap_run('dump test', $env, '-e', '$x = "hello world" x '.$nr);
+	my $script = '$x = "hello world" x '.$nr;
+	mwrap_run('dump test', $env, '-e', $script);
 	ok(-s $dump, "dump file written to");
 	my $s = slurp($dump);
 	truncate($dump, 0);
@@ -23,6 +24,24 @@ my $dump = "$mwrap_tmp/dump";
 	} else {
 		fail("$s failed to match $re");
 	}
+
+	$env->{MWRAP} = "dump_csv:$dump";
+	mwrap_run('dump_csv test', $env, '-e', $script);
+	ok(-s $dump, "CSV written to path");
+	$s = slurp($dump);
+	truncate($dump, 0);
+	my $nr_comma = ($s =~ tr/,/,/);
+	my $nr_cr = ($s =~ tr/\n/\n/);
+	ok($nr_comma > ($nr_cr * 4), 'CSV has more commas than CR');
+
+	$env->{MWRAP} = 'dump_csv,dump_fd:2';
+	mwrap_run('dump_csv,dump_fd test', $env, '-e', $script);
+	ok(-s $mwrap_err, "CSV written to stderr");
+	$s = slurp($mwrap_err);
+	truncate($mwrap_err, 0);
+	$nr_comma = ($s =~ tr/,/,/);
+	$nr_cr = ($s =~ tr/\n/\n/);
+	ok($nr_comma > ($nr_cr * 4), 'CSV has more commas than CR');
 }
 
 SKIP: { # C++ program which uses malloc via "new"

  reply	other threads:[~2023-01-11  1:12 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-01-11  1:12 [PATCH 0/4] CSV-related improvements Eric Wong
2023-01-11  1:12 ` Eric Wong [this message]
2023-01-11  1:12 ` [PATCH 2/4] add mwrap-decode-csv tool Eric Wong
2023-01-11  1:12 ` [PATCH 3/4] %p => PID expansion for dump_path + dump_csv Eric Wong
2023-01-11  1:12 ` [PATCH 4/4] rewrite README and update manpage to favor CSV Eric Wong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230111011249.2713039-2-e@80x24.org \
    --to=e@80x24.org \
    --cc=mwrap-perl@80x24.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/mwrap-perl.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).