Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 10 Jan 2024 23:32:51 GMT
From:      Mark Johnston <markj@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 93f27766a7e1 - main - dtrace: Add the 'oformat' libdtrace option
Message-ID:  <202401102332.40ANWpYp097859@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=93f27766a7e1af009c5b1e4ca538632857c91aa1

commit 93f27766a7e1af009c5b1e4ca538632857c91aa1
Author:     Domagoj Stolfa <domagoj.stolfa@gmail.com>
AuthorDate: 2024-01-03 14:58:01 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2024-01-10 23:14:26 +0000

    dtrace: Add the 'oformat' libdtrace option
    
    This option can be used to specify a format to use in DTrace output.
    The following formats are supported:
     - json
     - xml
     - html
     - none (default DTrace output)
    
    This is implemented using libxo and integrated into libdtrace. Client
    code only works with the following API:
    
     - dtrace_oformat_setup(dtrace_hdl_t *) -- to be called when output is starting.
     - dtrace_oformat_teardown(dtrace_hdl_t *) -- to be called when output is finished
     - dtrace_oformat(dtrace_hdl_t *) -- check if oformat is enabled.
     - dtrace_set_outfp(FILE *) -- sets the output file for oformat.
     - Ensure that oformat is correctly checked in the drop handler and record
       processing callbacks.
    
    This commit also adds tests which check if the generated output is
    valid (JSON, XML) and extends the dtrace(1) describing the structured output.
    
    Reviewed by:    markj
    Discussed with: phil
    MFC after:      2 months
    Sponsored by:   Innovate UK
    Differential Revision:  https://reviews.freebsd.org/D41745
---
 Makefile.inc1                                      |    3 +
 cddl/contrib/opensolaris/cmd/dtrace/dtrace.1       |  416 +++++-
 cddl/contrib/opensolaris/cmd/dtrace/dtrace.c       |   72 +-
 .../dtrace/test/tst/common/oformat/tst.agg.avg.ksh |   66 +
 .../test/tst/common/oformat/tst.agg.count.ksh      |   66 +
 .../test/tst/common/oformat/tst.agg.llquantize.ksh |   73 +
 .../test/tst/common/oformat/tst.agg.lquantize.ksh  |   73 +
 .../dtrace/test/tst/common/oformat/tst.agg.max.ksh |   66 +
 .../dtrace/test/tst/common/oformat/tst.agg.min.ksh |   66 +
 .../test/tst/common/oformat/tst.agg.quantize.ksh   |   73 +
 .../test/tst/common/oformat/tst.agg.stddev.ksh     |   66 +
 .../dtrace/test/tst/common/oformat/tst.agg.sum.ksh |   66 +
 .../dtrace/test/tst/common/oformat/tst.aggmod.ksh  |   66 +
 .../test/tst/common/oformat/tst.aggstack.ksh       |   66 +
 .../dtrace/test/tst/common/oformat/tst.aggsym.ksh  |   66 +
 .../test/tst/common/oformat/tst.aggustack.ksh      |   66 +
 .../dtrace/test/tst/common/oformat/tst.aggusym.ksh |   66 +
 .../dtrace/test/tst/common/oformat/tst.drop.ksh    |   61 +
 .../cmd/dtrace/test/tst/common/oformat/tst.mod.ksh |   57 +
 .../dtrace/test/tst/common/oformat/tst.print.ksh   |   60 +
 .../dtrace/test/tst/common/oformat/tst.printf.ksh  |   57 +
 .../dtrace/test/tst/common/oformat/tst.printm.ksh  |   57 +
 .../dtrace/test/tst/common/oformat/tst.stack.ksh   |   61 +
 .../cmd/dtrace/test/tst/common/oformat/tst.sym.ksh |   57 +
 .../dtrace/test/tst/common/oformat/tst.trace.ksh   |   66 +
 .../test/tst/common/oformat/tst.tracemem.ksh       |   57 +
 .../dtrace/test/tst/common/oformat/tst.umod.ksh    |   57 +
 .../dtrace/test/tst/common/oformat/tst.ustack.ksh  |   61 +
 .../dtrace/test/tst/common/oformat/tst.usym.ksh    |   57 +
 .../lib/libdtrace/common/dt_aggregate.c            |   17 +-
 .../opensolaris/lib/libdtrace/common/dt_consume.c  | 1540 +++++++++++++++++---
 .../opensolaris/lib/libdtrace/common/dt_handle.c   |   37 +-
 .../opensolaris/lib/libdtrace/common/dt_impl.h     |    7 +
 .../opensolaris/lib/libdtrace/common/dt_oformat.h  |   34 +
 .../opensolaris/lib/libdtrace/common/dt_open.c     |    3 +
 .../opensolaris/lib/libdtrace/common/dt_options.c  |   14 +
 .../opensolaris/lib/libdtrace/common/dt_print.c    |  266 +++-
 .../opensolaris/lib/libdtrace/common/dt_printf.h   |    6 +
 .../opensolaris/lib/libdtrace/common/dt_subr.c     |   42 +
 .../opensolaris/lib/libdtrace/common/dtrace.h      |   22 +
 cddl/lib/libdtrace/Makefile                        |    2 +-
 cddl/usr.sbin/dtrace/Makefile                      |    2 +-
 cddl/usr.sbin/dtrace/tests/common/Makefile         |    1 +
 cddl/usr.sbin/dtrace/tests/common/oformat/Makefile |   42 +
 cddl/usr.sbin/dtrace/tests/dtrace.test.mk          |    2 +-
 etc/mtree/BSD.tests.dist                           |    2 +
 share/mk/src.libnames.mk                           |    2 +-
 47 files changed, 3906 insertions(+), 279 deletions(-)

diff --git a/Makefile.inc1 b/Makefile.inc1
index e6a6e9d479d3..34b49ee319ec 100644
--- a/Makefile.inc1
+++ b/Makefile.inc1
@@ -3139,6 +3139,7 @@ _prebuild_libs=	${_kerberos5_lib_libasn1} \
 		${_cddl_lib_libctf} ${_cddl_lib_libzfsbootenv} \
 		lib/libufs \
 		lib/libutil lib/libpjdlog ${_lib_libypclnt} lib/libz lib/msun \
+		lib/libxo \
 		${_secure_lib_libcrypto} ${_secure_lib_libssl} \
 		${_lib_libldns} ${_secure_lib_libssh}
 
@@ -3194,6 +3195,7 @@ _generic_libs+= ${_DIR}
 .endfor
 
 lib/libtacplus__L: lib/libmd__L lib/libpam/libpam__L
+lib/libxo__L: lib/libutil__L
 
 .if ${MK_CDDL} != "no"
 _cddl_lib_libumem= cddl/lib/libumem
@@ -3234,6 +3236,7 @@ lib/libbe__L: cddl/lib/libzfs__L cddl/lib/libzfsbootenv__L
 _cddl_lib_libctf= cddl/lib/libctf
 _cddl_lib= cddl/lib
 cddl/lib/libctf__L: lib/libz__L cddl/lib/libspl__L
+cddl/lib/libdtrace__L: lib/libxo__L
 .endif
 # cddl/lib/libdtrace requires lib/libproc and lib/librtld_db
 _prebuild_libs+=	lib/libprocstat lib/libproc lib/librtld_db
diff --git a/cddl/contrib/opensolaris/cmd/dtrace/dtrace.1 b/cddl/contrib/opensolaris/cmd/dtrace/dtrace.1
index 1745519c2c1a..ab8c672a95a1 100644
--- a/cddl/contrib/opensolaris/cmd/dtrace/dtrace.1
+++ b/cddl/contrib/opensolaris/cmd/dtrace/dtrace.1
@@ -18,7 +18,9 @@
 .\" CDDL HEADER END
 .\" Copyright (c) 2006, Sun Microsystems, Inc. All Rights Reserved.
 .\"
-.Dd February 24, 2023
+.\" $FreeBSD$
+.\"
+.Dd September 8, 2023
 .Dt DTRACE 1
 .Os
 .Sh NAME
@@ -27,7 +29,8 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl 32 | Fl 64
-.Op Fl aACdeFGhHlqSvVwZ
+.Op Fl aACdeFGhHlOqSvVwZ
+.Op Fl -libxo
 .Op Fl b Ar bufsz
 .Op Fl c Ar cmd
 .Op Fl D Ar name Op Ns = Ns value
@@ -355,6 +358,11 @@ writing D programs.
 The specified
 .Ar path
 is added after the default library search path.
+.It Fl -libxo
+Generate output via
+.Xr libxo 3 .
+This option is the same as specifying
+.Sy oformat .
 .It Fl m Oo Ar provider : Oc Ar module Oo Oo Ar predicate Oc Ar action Oc
 Specify module name to trace or list
 .Fl ( l
@@ -398,6 +406,14 @@ argument can be suffixed with an optional D probe clause.
 More than one
 .Fl n
 option can be specified on the command line at a time.
+.It Fl O
+This option causes
+.Nm
+to print all the aggregations upon exiting if
+.Sy oformat
+or
+.Fl -libxo
+are specified.
 .It Fl o Ar output
 Specify the
 .Ar output
@@ -650,6 +666,32 @@ Number of whitespace characters to use when indenting
 and
 .Fn ustack
 output.
+.It Sy oformat Ns = Ns Ar format
+Specify the format to use for output.
+Setting
+.Sy oformat
+to
+.Ql text
+makes
+.Nm
+use regular human-readable output which is its default behavior.
+The options passed to
+.Sy oformat
+are directly forwarded to
+.Xr libxo 3 .
+Some of the supported formatters include
+.Ql json ,
+.Ql xml
+and
+.Ql html .
+Note that this option will cause
+.Nm
+to not produce any output unless printing functions are explicitly called,
+or the
+.Fl O
+flag is specified.
+For more information see
+.Sx STRUCTURED OUTPUT .
 .It Sy statusrate Ns = Ns Ar time
 Rate of status checking.
 .It Sy switchrate Ns = Ns Ar time
@@ -776,6 +818,376 @@ or
 .Fl i
 options) contain descriptions that do not match any known probes.
 .El
+.Sh STRUCTURED OUTPUT
+.Nm
+supports structured output using
+.Xr libxo 3 .
+The output will always have a top-level object called
+.Dq dtrace ,
+followed by a list of objects
+.Dq probes .
+Each of the probe objects will to have a timestamp which is generated at
+output time rather than probe firing time, an identifier for the CPU on
+which the probe was executed, and the probe's full specification:
+.Bd -literal
+{
+  "dtrace": {
+    "probes": [
+      {
+        "timestamp": ...,
+        "cpu": ...,
+        "id": ...,
+        "provider": ...,
+        "module": ...,
+        "function": ...,
+        "name": ...,
+        "output": [
+           ... (script-specific output)
+        ]
+      }
+    ]
+  }
+}
+
+<?xml version="1.0"?>
+<dtrace>
+  <probes>
+    <timestamp>...</timestamp>
+    <cpu>...</cpu>
+    <id>...</id>
+    <provider>...</provider>
+    <module>...</module>
+    <function>...</function>
+    <name>...</name>
+    <output>
+      ... (script-specific output)
+    </output>
+  </probes>
+</dtrace>
+.Ed
+.Pp
+It is also possible for XML output to take the following form if some
+of the fields are empty (in this example, module and function values
+are absent):
+.Bd -literal
+<?xml version="1.0"?>
+<dtrace>
+  <probes>
+    ...
+    <module/>
+    <function/>
+    ...
+    <output>
+      ... (script-specific output)
+    </output>
+  </probes>
+</dtrace>
+.Ed
+.Pp
+Similarly,
+.Sy oformat
+can be used to generate HTML:
+.Bd -literal
+<div class="line">
+<div class="data" data-tag="timestamp">...</div>
+<div class="text"></div>
+<div class="data" data-tag="cpu">...</div>
+<div class="text"></div>
+<div class="data" data-tag="id">...</div>
+<div class="text"></div>
+<div class="data" data-tag="provider">...</div>
+<div class="text"></div>
+<div class="data" data-tag="module">...</div>
+<div class="text"></div>
+<div class="data" data-tag="function">...</div>
+<div class="text"></div>
+<div class="data" data-tag="name">...</div>
+<div class="data" data-tag="... (script-specific output)">...</div>
+</div>
+.Ed
+.Pp
+Unlike JSON and XML, the
+.Dq output
+array is not present.
+Instead, data is simply formatted into a div of class
+.Dq data
+and a data-tag is associated with each of the keys.
+.Pp
+The
+.Dq output
+array's contents depend on the probes' actions and is explained below.
+The examples here are presented in JSON form as opposed to XML or HTML,
+however the conversion explained above applies for all output formats.
+.Pp
+Any scalar output, such as output produced by the
+.Fn trace
+action is of form:
+.Bd -literal
+{
+  "value": ...
+}
+.Ed
+.Pp
+The
+.Fn printf
+action begins with an object containing the formatted output of the
+.Fn printf
+action.
+Subsequent objects contains the value of each of the arguments to
+.Fn printf
+in its raw form as if the
+.Fn trace
+action was used instead.
+A
+.Fn printf
+statement which contains no arguments other than the message will only have
+one object following the message object and its value will always be 0.
+This is an artefact of the implementation and can safely be ignored.
+.Bd -literal
+# dtrace --libxo json,pretty -n 'BEGIN { printf("... %Y, ..", walltimestamp); }'
+
+{
+  "message": "... 2023 Sep  7 16:49:02, .."
+},
+{
+  "value": 1694105342633402400
+},
+{
+  ...
+}
+.Ed
+.Pp
+Scalar aggregations are aggregations which produce a single value for a given
+key.
+These aggregations include
+.Fn count ,
+.Fn min ,
+.Fn max ,
+.Fn stddev
+and
+.Fn sum .
+Each one of them is represented by the key containing their name.
+For example, the output of a
+.Fn stddev
+aggregation will contain a key
+.Dq stddev
+inside an
+.Dq aggregation-data
+object:
+.Bd -literal
+{
+  "aggregation-data": [
+    {
+      "keys": [
+        ...
+      ],
+      "stddev": ...
+    }
+  ],
+  "aggregation-name": ...
+}
+.Ed
+.Pp
+The
+.Dq keys
+field remains consistent across all aggregations, however
+.Fn quantize ,
+.Fn lquantize
+and
+.Fn llquantize
+need to be treated differently.
+.Sy oformat
+will create a new array of objects called
+.Dq buckets .
+Each of the objects contains a
+.Dq value
+and a
+.Dq count
+field which are
+the left-hand side and the right-hand side of human-readable
+.Nm
+output respectively.
+The full object has the following format:
+.Bd -literal
+{
+  "aggregation-data": [
+    ...
+    {
+      "keys": [
+        ...
+      ],
+      "buckets": [
+        {
+          "value": 32,
+          "count": 0
+        },
+        {
+          "value": 64,
+          "count": 17
+        },
+        ...
+      ],
+    },
+    ...
+  ]
+  "aggregation-name": ...
+}
+.Ed
+.Pp
+Similar to scalar aggregations, named scalar actions such as
+.Fn mod ,
+.Fn umod ,
+.Fn usym ,
+.Fn tracemem
+and
+.Fn printm
+will output an object with the key being equal to the
+name of the action.
+For example,
+.Fn printm
+output would produce the following object:
+.Bd -literal
+{
+  "printm": "0x4054171100"
+}
+.Ed
+.Pp
+.Fn sym
+is slightly different.
+While it will create a
+.Dq sym
+field which contains its value, in some cases it will also create additional
+fields
+.Dq object ,
+.Dq name
+and
+.Dq offset :
+.Bd -literal
+# dtrace -x oformat=json,pretty -On 'BEGIN { sym((uintptr_t)&`prison0); }'
+
+{
+  "sym": "kernel`prison0",
+  "object": "kernel",
+  "name": "prison0"
+}
+
+# dtrace --libxo json,pretty -On 'BEGIN { sym((uintptr_t)curthread); }'
+
+{
+  "sym": "0xfffffe00c18d2000",
+  "offset": "0xfffffe00c18d2000"
+}
+.Ed
+.Pp
+.Fn stack
+and
+.Fn ustack
+actions unroll each of the stack frames into its own object in an array.
+The only real difference between them is that the
+.Fn stack
+action will produce a list called
+.Dq stack-frames
+while
+.Fn ustack
+will produce one called
+.Dq ustack-frames .
+The following is an example of their
+.Sy oformat
+output:
+.Bd -literal
+{
+  "stack-frames": [
+    {
+      "symbol": "dtrace.ko`dtrace_dof_create+0x35",
+      "module": "dtrace.ko",
+      "name": "dtrace_dof_create",
+      "offset": "0x35"
+    },
+    {
+      "symbol": "dtrace.ko`dtrace_ioctl+0x81c",
+      "module": "dtrace.ko",
+      "name": "dtrace_ioctl",
+      "offset": "0x81c"
+    },
+    ...
+  ]
+}
+
+{
+  "ustack-frames": [
+    {
+      "symbol": "libc.so.7`ioctl+0xa",
+      "module": "libc.so.7",
+      "name": "ioctl",
+      "offset": "0xa"
+    },
+    {
+      "symbol": "libdtrace.so.2`dtrace_go+0xf3",
+      "module": "libdtrace.so.2",
+      "name": "dtrace_go",
+      "offset": "0xf3"
+    },
+    ...
+  ]
+}
+.Ed
+.Pp
+The
+.Fn print
+action produces a
+.Dq type
+list in the following form:
+.Bd -literal
+{
+  "type": [
+    {
+      "object-name": "kernel",
+      "name": "struct thread",
+      "ctfid": 2372
+    },
+    {
+      "member-name": "td_lock",
+      "name": "struct mtx *volatile",
+      "ctfid": 2035,
+      "value": "0xffffffff82158440"
+    },
+    ...
+}
+.Ed
+.Pp
+If the type is invalid, a
+.Dq warning
+object will be produced containing the diagnostic message as well as two
+possible optional fields:
+.Dq type-identifier
+which contains the CTF identifier of the type and
+.Dq size containing the size of an integer, enum or float.
+The fields generated will depend on the kind of error that was encountered
+while processing the trace data.
+.Pp
+Finally,
+.Sy oformat
+provides a special pseudo-probe to represent drops.
+As
+.Nm
+polls for various kinds of drops
+.Sy oformat
+will produce output similar to the following in order to represent drops:
+.Bd -literal
+{
+  "cpu": -1,
+  "id": -1,
+  "provider": "dtrace",
+  "module": "INTERNAL",
+  "function": "INTERNAL",
+  "name": "DROP",
+  "timestamp": ...,
+  "count": ...,
+  "total": ...,
+  "kind": 2,
+  "msg": "... dynamic variable drops\n"
+}
+.Ed
 .Sh OPERANDS
 You can specify zero or more additional arguments on the
 .Nm
diff --git a/cddl/contrib/opensolaris/cmd/dtrace/dtrace.c b/cddl/contrib/opensolaris/cmd/dtrace/dtrace.c
index cdc476a43b08..dc68c6ef5f72 100644
--- a/cddl/contrib/opensolaris/cmd/dtrace/dtrace.c
+++ b/cddl/contrib/opensolaris/cmd/dtrace/dtrace.c
@@ -26,6 +26,7 @@
 /*
  * Copyright (c) 2012 by Delphix. All rights reserved.
  * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2023, Domagoj Stolfa. All rights reserved.
  */
 
 #include <sys/types.h>
@@ -55,6 +56,9 @@
 #include <spawn.h>
 #endif
 
+#undef NORETURN /* needed because libxo redefines it */
+#include <libxo/xo.h>
+
 typedef struct dtrace_cmd {
 	void (*dc_func)(struct dtrace_cmd *);	/* function to compile arg */
 	dtrace_probespec_t dc_spec;		/* probe specifier context */
@@ -77,7 +81,7 @@ typedef struct dtrace_cmd {
 #define	E_USAGE		2
 
 static const char DTRACE_OPTSTR[] =
-	"3:6:aAb:Bc:CdD:ef:FGhHi:I:lL:m:n:o:p:P:qs:SU:vVwx:X:Z";
+	"3:6:aAb:Bc:CdD:ef:FGhHi:I:lL:m:n:o:Op:P:qs:SU:vVwx:X:Z";
 
 static char **g_argv;
 static int g_argc;
@@ -167,6 +171,7 @@ usage(FILE *fp)
 	    "\t-m  enable or list probes matching the specified module name\n"
 	    "\t-n  enable or list probes matching the specified probe name\n"
 	    "\t-o  set output file\n"
+	    "\t-O  print output upon exiting (specific to oformat)\n"
 	    "\t-p  grab specified process-ID and cache its symbol tables\n"
 	    "\t-P  enable or list probes matching the specified provider name\n"
 	    "\t-q  set quiet mode (only output explicitly traced data)\n"
@@ -907,7 +912,10 @@ errhandler(const dtrace_errdata_t *data, void *arg)
 static int
 drophandler(const dtrace_dropdata_t *data, void *arg)
 {
-	error(data->dtdda_msg);
+	if (!dtrace_oformat(g_dtp)) {
+		error(data->dtdda_msg);
+	}
+
 	return (DTRACE_HANDLE_OK);
 }
 
@@ -1126,7 +1134,9 @@ chew(const dtrace_probedata_t *data, void *arg)
 	}
 
 	if (!g_flowindent) {
-		if (!g_quiet) {
+		if (dtrace_oformat(g_dtp)) {
+			dtrace_oformat_probe(g_dtp, data, cpu, pd);
+		} else if (!g_quiet) {
 			char name[DTRACE_FUNCNAMELEN + DTRACE_NAMELEN + 2];
 
 			(void) snprintf(name, sizeof (name), "%s:%s",
@@ -1313,7 +1323,8 @@ main(int argc, char *argv[])
 
 	g_ofp = stdout;
 	int done = 0, mode = 0;
-	int err, i, c;
+	int err, i, c, new_argc, libxo_specified;
+	int print_upon_exit = 0;
 	char *p, **v;
 	struct ps_prochandle *P;
 	pid_t pid;
@@ -1336,6 +1347,15 @@ main(int argc, char *argv[])
 	    (g_psv = malloc(sizeof (struct ps_prochandle *) * argc)) == NULL)
 		fatal("failed to allocate memory for arguments");
 
+	new_argc = xo_parse_args(argc, argv);
+	if (new_argc < 0)
+		return (usage(stderr));
+
+	if (new_argc != argc)
+		libxo_specified = 1;
+
+	argc = new_argc;
+
 	g_argv[g_argc++] = argv[0];	/* propagate argv[0] to D as $0/$$0 */
 	argv[0] = g_pname;		/* rewrite argv[0] for getopt errors */
 
@@ -1532,6 +1552,10 @@ main(int argc, char *argv[])
 	} else if (g_mode == DMODE_ANON)
 		(void) dtrace_setopt(g_dtp, "linkmode", "primary");
 
+
+	if (libxo_specified)
+		dtrace_oformat_configure(g_dtp);
+
 	/*
 	 * Now that we have libdtrace open, make a second pass through argv[]
 	 * to perform any dtrace_setopt() calls and change any compiler flags.
@@ -1624,6 +1648,10 @@ main(int argc, char *argv[])
 				dcp->dc_arg = optarg;
 				break;
 
+			case 'O':
+				print_upon_exit = 1;
+				break;
+
 			case 'q':
 				if (dtrace_setopt(g_dtp, "quiet", 0) != 0)
 					dfatal("failed to set -q");
@@ -1765,6 +1793,11 @@ main(int argc, char *argv[])
 	(void) dtrace_getopt(g_dtp, "quiet", &opt);
 	g_quiet = opt != DTRACEOPT_UNSET;
 
+	if (dtrace_oformat(g_dtp)) {
+		if (dtrace_setopt(g_dtp, "quiet", 0) != 0)
+			dfatal("failed to set quiet (caused by oformat)");
+	}
+
 	/*
 	 * Now make a fifth and final pass over the options that have been
 	 * turned into programs and saved in g_cmdv[], performing any mode-
@@ -1777,6 +1810,9 @@ main(int argc, char *argv[])
 		if (g_ofile != NULL && (g_ofp = fopen(g_ofile, "a")) == NULL)
 			fatal("failed to open output file '%s'", g_ofile);
 
+		if (dtrace_oformat(g_dtp))
+			dtrace_set_outfp(g_ofp);
+
 		for (i = 0; i < g_cmdc; i++)
 			exec_prog(&g_cmdv[i]);
 
@@ -1811,6 +1847,9 @@ main(int argc, char *argv[])
 		if ((g_ofp = fopen(g_ofile, "a")) == NULL)
 			fatal("failed to open output file '%s'", g_ofile);
 
+		if (dtrace_oformat(g_dtp))
+			dtrace_set_outfp(g_ofp);
+
 		for (i = 0; i < g_cmdc; i++) {
 			anon_prog(&g_cmdv[i],
 			    dtrace_dof_create(g_dtp, g_cmdv[i].dc_prog, 0), i);
@@ -1971,12 +2010,21 @@ main(int argc, char *argv[])
 
 	g_pslive = g_psc; /* count for prochandler() */
 
+	dtrace_oformat_setup(g_dtp);
 	do {
 		if (!g_intr && !done)
 			dtrace_sleep(g_dtp);
 
 #ifdef __FreeBSD__
-		if (g_siginfo) {
+		/*
+		 * XXX: Supporting SIGINFO with oformat makes little sense, as
+		 * it can't really produce sensible DTrace output.
+		 *
+		 * If needed, we could support it by having an imaginary
+		 * "SIGINFO" probe that we can construct in the output but leave
+		 * it out for now.
+		 */
+		if (g_siginfo && !dtrace_oformat(g_dtp)) {
 			(void)dtrace_aggregate_print(g_dtp, g_ofp, NULL);
 			g_siginfo = 0;
 		}
@@ -2013,14 +2061,24 @@ main(int argc, char *argv[])
 			clearerr(g_ofp);
 	} while (!done);
 
-	oprintf("\n");
+	if (!dtrace_oformat(g_dtp))
+		oprintf("\n");
 
-	if (!g_impatient) {
+	/*
+	 * Since there is no way to format a probe here and machine-readable
+	 * output makes little sense without explicitly asking for it, we print
+	 * nothing upon Ctrl-C if oformat is specified. If the user wishes to
+	 * get output upon exit, they must write an explicit dtrace:::END probe
+	 * to do so.
+	 */
+	if ((!g_impatient && !dtrace_oformat(g_dtp)) ||
+	    (!g_impatient && print_upon_exit)) {
 		if (dtrace_aggregate_print(g_dtp, g_ofp, NULL) == -1 &&
 		    dtrace_errno(g_dtp) != EINTR)
 			dfatal("failed to print aggregations");
 	}
 
+	dtrace_oformat_teardown(g_dtp);
 	dtrace_close(g_dtp);
 	return (g_status);
 }
diff --git a/cddl/contrib/opensolaris/cmd/dtrace/test/tst/common/oformat/tst.agg.avg.ksh b/cddl/contrib/opensolaris/cmd/dtrace/test/tst/common/oformat/tst.agg.avg.ksh
new file mode 100644
index 000000000000..daf5c7f4e9bb
--- /dev/null
+++ b/cddl/contrib/opensolaris/cmd/dtrace/test/tst/common/oformat/tst.agg.avg.ksh
@@ -0,0 +1,66 @@
+#!/usr/bin/ksh
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2023 Domagoj Stolfa
+#
+
+bname=`basename $0`
+dtraceout=/tmp/dtrace.$bname
+
+script()
+{
+	$dtrace -o $dtraceout.$1 -x oformat=$1 -s /dev/stdin <<__EOF__
+syscall:::entry
+{
+        @[probefunc] = avg(tid);
+}
+
+tick-5s
+{
+        exit(0);
+}
+
+END
+{
+        printa(@);
+}
+__EOF__
+}
+
+if [ $# != 1 ]; then
+	echo expected one argument: '<'dtrace-path'>'
+	exit 2
+fi
+
+dtrace=$1
+
+script json
+jq . $dtraceout.json
+
+if [ $? != 0 ]; then
+	echo $bname: failed to produce valid JSON. see $dtraceout.json
+	exit 1
+fi
+
+script xml
+xmllint $dtraceout.xml
+
+if [ $? != 0 ]; then
+	echo $bname: failed to produce valid XML. see $dtraceout.xml
+	exit 1
+fi
+
+rm $dtraceout.json
+rm $dtraceout.xml
+
+exit 0
diff --git a/cddl/contrib/opensolaris/cmd/dtrace/test/tst/common/oformat/tst.agg.count.ksh b/cddl/contrib/opensolaris/cmd/dtrace/test/tst/common/oformat/tst.agg.count.ksh
new file mode 100644
index 000000000000..ab7c5fa28471
--- /dev/null
+++ b/cddl/contrib/opensolaris/cmd/dtrace/test/tst/common/oformat/tst.agg.count.ksh
@@ -0,0 +1,66 @@
+#!/usr/bin/ksh
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2023 Domagoj Stolfa
+#
+
+bname=`basename $0`
+dtraceout=/tmp/dtrace.$bname
+
+script()
+{
+	$dtrace -o $dtraceout.$1 -x oformat=$1 -s /dev/stdin <<__EOF__
+syscall:::entry
+{
+        @[execname] = count();
+}
+
+tick-5s
+{
+        exit(0);
+}
+
+END
+{
+        printa(@);
+}
+__EOF__
+}
+
+if [ $# != 1 ]; then
+	echo expected one argument: '<'dtrace-path'>'
+	exit 2
+fi
+
+dtrace=$1
+
+script json
+jq . $dtraceout.json
+
+if [ $? != 0 ]; then
+	echo $bname: failed to produce valid JSON. see $dtraceout.json
+	exit 1
+fi
+
+script xml
+xmllint $dtraceout.xml
+
+if [ $? != 0 ]; then
+	echo $bname: failed to produce valid XML. see $dtraceout.xml
+	exit 1
+fi
+
+rm $dtraceout.json
+rm $dtraceout.xml
+
+exit 0
diff --git a/cddl/contrib/opensolaris/cmd/dtrace/test/tst/common/oformat/tst.agg.llquantize.ksh b/cddl/contrib/opensolaris/cmd/dtrace/test/tst/common/oformat/tst.agg.llquantize.ksh
new file mode 100644
index 000000000000..380335de000a
--- /dev/null
+++ b/cddl/contrib/opensolaris/cmd/dtrace/test/tst/common/oformat/tst.agg.llquantize.ksh
@@ -0,0 +1,73 @@
+#!/usr/bin/ksh
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2023 Domagoj Stolfa
+#
+
+bname=`basename $0`
+dtraceout=/tmp/dtrace.$bname
+
+script()
+{
+	$dtrace -o $dtraceout.$1 -x oformat=$1 -s /dev/stdin <<__EOF__
+syscall:::entry
+{
+        self->ts = timestamp;
+}
+
+syscall:::return
+/self->ts/
+{
+        @[probefunc] = llquantize(timestamp - self->ts, 2, 1, 32, 32);
+        self->ts = 0;
+}
+
+tick-5s
+{
+        exit(0);
+}
+
+END
+{
+        printa(@);
+}
+__EOF__
+}
+
+if [ $# != 1 ]; then
+	echo expected one argument: '<'dtrace-path'>'
+	exit 2
+fi
+
+dtrace=$1
+
+script json
+jq . $dtraceout.json
+
+if [ $? != 0 ]; then
+	echo $bname: failed to produce valid JSON. see $dtraceout.json
+	exit 1
+fi
+
+script xml
+xmllint $dtraceout.xml
+
+if [ $? != 0 ]; then
+	echo $bname: failed to produce valid XML. see $dtraceout.xml
+	exit 1
*** 4641 LINES SKIPPED ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202401102332.40ANWpYp097859>