Dynamic Tracing Guide
PreviousNext
Chapter 12

Output Formatting

DTrace provides built-in formatting functions printf and printa that you can use from your D programs to format output. The D compiler provides features not found in the printf(3C) library routine, so you should read this chapter even if you are already familiar with printf. This chapter also discusses the formatting behavior of the trace and print functions and the default output format used by dtrace(1M) to display aggregations.

printf

The printf function combines the ability to trace data, as if by the trace function, with the ability to output the data and other text in a specific format that you describe. The printf function tells DTrace to trace the data associated with each argument after the first argument, and then to format the results using the rules described by the first printf argument, known as a format string.

The format string is a regular string that contains any number of format conversions, each beginning with the % character, that describe how to format the corresponding argument. The first conversion in the format string corresponds to the second printf argument, the second conversion to the third argument, and so on. All of the text between conversions is printed verbatim. The character following the % conversion character describes the format to use for the corresponding argument.

Unlike printf(3C), DTrace printf is a built-in function that is recognized by the D compiler. The D compiler provides several useful services for DTrace printf that are not found in the C library printf:

  • The D compiler compares the arguments to the conversions in the format string. If an argument's type is incompatible with the format conversion, the D compiler provides an error message explaining the problem.

  • The D compiler does not require the use of size prefixes with printf format conversions. The C printf routine requires that you indicate the size of arguments by adding prefixes such as %ld for long or %lld for long long. The D compiler knows the size and type of your arguments, so these prefixes are not required in your D printf statements.

  • DTrace provides additional format characters that are useful for debugging and observability. For example, the %a format conversion can be used to print a pointer as a symbol name and offset.

In order to implement these features, the format string in the DTrace printf function must be specified as a string constant in your D program. Format strings may not be dynamic variables of type string.

Conversion Specifications

Each conversion specification in the format string is introduced by the % character, after which the following information appears in sequence:

  • Zero or more flags (in any order), that modify the meaning of the conversion specification as described in the next section.

  • An optional minimum field width. If the converted value has fewer bytes than the field width, the value will be padded with spaces on the left by default, or on the right if the left-adjustment flag (-) is specified. The field width can also be specified as an asterisk (*), in which case the field width is set dynamically based on the value of an additional argument of type int.

  • An optional precision that indicates the minimum number of digits to appear for the d, i, o, u, x, and X conversions (the field is padded with leading zeroes); the number of digits to appear after the radix character for the e, E, and f conversions, the maximum number of significant digits for the g and G conversions; or the maximum number of bytes to be printed from a string by the s conversion. The precision takes the form of a period (.) followed by either an asterisk (*), described below, or a decimal digit string.

  • An optional sequence of size prefixes that indicate the size of the corresponding argument, described in Size Prefixes. The size prefixes are not necessary in D and are provided for compatibility with the C printf function.

  • A conversion specifier that indicates the type of conversion to be applied to the argument.

The printf(3C) function also supports conversion specifications of the form %n$ where n is a decimal integer; DTrace printf does not support this type of conversion specification.

Flag Specifiers

The printf conversion flags are enabled by specifying one or more of the following characters, which may appear in any order:

'

The integer portion of the result of a decimal conversion (%i, %d, %u, %f, %g, or %G) is formatted with thousands grouping characters using the non-monetary grouping character. Some locales, including the POSIX C locale, do not provide non-monetary grouping characters for use with this flag.

-

The result of the conversion is left-justified within the field. The conversion is right-justified if this flag is not specified.

+

The result of signed conversion always begins with a sign (+ or -). If this flag is not specified, the conversion begins with a sign only when a negative value is converted.

space

If the first character of a signed conversion is not a sign or if a signed conversion results in no characters, a space is placed before the result. If the space and + flags both appear, the space flag is ignored.

#

The value is converted to an alternate form if an alternate form is defined for the selected conversion. The alternate formats for conversions are described along with the corresponding conversion.

0

For d, i, o, u, x, X, e, E, f, g, and G conversions, leading zeroes (following any indication of sign or base) are used to pad to the field width. No space padding is performed. If the 0 and - flags both appear, the 0 flag is ignored. For d, i, o, u, x, and X conversions, if a precision is specified, the 0 flag is ignored. If the 0 and ' flags both appear, the grouping characters are inserted before the zero padding.

Width and Precision Specifiers

The minimum field width can be specified as a decimal digit string following any flag specifier, in which case the field width is set to the specified number of columns. The field width can also be specified as asterisk (*) in which case an additional argument of type int is accessed to determine the field width. For example, to print an integer x in a field width determined by the value of the int variable w, you would write the D statement:

printf("%*d", w, x);

The field width can also be specified using a ? character to indicate that the field width should be set based on the number of characters required to format an address in hexadecimal in the data model of the operating system kernel. The width is set to 8 if the kernel is using the 32–bit data model, or to 16 if the kernel is using the 64–bit data model.

The precision for the conversion can be specified as a decimal digit string following a period (.) or by an asterisk (*) following a period. If an asterisk is used to specify the precision, an additional argument of type int prior to the conversion argument is accessed to determine the precision. If both width and precision are specified as asterisks, the order of arguments to printf for the conversion should appear in the following order: width, precision, value.

Size Prefixes

Size prefixes are required in ANSI-C programs that use printf(3C) in order to indicate the size and type of the conversion argument. The D compiler performs this processing for your printf calls automatically, so size prefixes are not required. Although size prefixes are provided for C compatibility, their use is explicitly discouraged in D programs because they bind your code to a particular data model when using derived types. For example, if a typedef is redefined to different integer base types depending on the data model, it is not possible to use a single C conversion that works in both data models without explicitly knowing the two underlying types and including a cast expression, or defining multiple format strings. The D compiler solves this problem automatically by allowing you to omit size prefixes and automatically determining the argument size.

The size prefixes can be placed just prior to the format conversion name and after any flags, widths, and precision specifiers. The size prefixes are as follows:

  • An optional h specifies that a following d, i, o, u, x, or X conversion applies to a short or unsigned short.

  • An optional l specifies that a following d, i, o, u, x, or X conversion applies to a long or unsigned long.

  • An optional ll specifies that a following d, i, o, u, x, or X conversion applies to a long long or unsigned long long.

  • An optional L specifies that a following e, E, f, g, or G conversion applies to a long double.

  • An optional l specifies that a following c conversion applies to a wint_t argument, and that a following s conversion character applies to a pointer to a wchar_t argument.

Conversion Formats

Each conversion character sequence results in fetching zero or more arguments. If insufficient arguments are provided for the format string, or if the format string is exhausted and arguments remain, the D compiler issues an appropriate error message. If an undefined conversion format is specified, the D compiler issues an appropriate error message. The conversion character sequences are:

a

The pointer or uintptr_t argument is printed as a kernel symbol name in the form module`symbol-name plus an optional hexadecimal byte offset. If the value does not fall within the range defined by a known kernel symbol, the value is printed as a hexadecimal integer.

c

The char, short, or int argument is printed as an ASCII character.

C

The char, short, or int argument is printed as an ASCII character if the character is a printable ASCII character. If the character is not a printable character, it is printed using the corresponding escape sequence as shown in Table 2–5.

d

The char, short, int, long, or long long argument is printed as a decimal (base 10) integer. If the argument is signed, it will be printed as a signed value. If the argument is unsigned, it will be printed as an unsigned value. This conversion has the same meaning as i.

e, E

The float, double, or long double argument is converted to the style [-]d.ddddd, where there is one digit before the radix character and the number of digits after it is equal to the precision. The radix character is non-zero if the argument is non-zero. If the precision is not specified, the default precision value is 6. If the precision is 0 and the # flag is not specified, no radix character appears. The E conversion format produces a number with E instead of e introducing the exponent. The exponent always contains at least two digits. The value is rounded up to the appropriate number of digits.

f

The float, double, or long double argument is converted to the style [-]ddd.ddd, where the number of digits after the radix character is equal to the precision specification. If the precision is not specified, the default precision value is 6. If the precision is 0 and the # flag is not specified, no radix character appears. If a radix character appears, at least one digit appears before it. The value is rounded up to the appropriate number of digits.

g, G

The float, double, or long double argument is printed in the style f or e (or in style E in the case of a G conversion character), with the precision specifying the number of significant digits. If an explicit precision is 0, it is taken as 1. The style used depends on the value converted: style e (or E) is used only if the exponent resulting from the conversion is less than -4 or greater than or equal to the precision. Trailing zeroes are removed from the fractional part of the result. A radix character appears only if it is followed by a digit. If the # flag is specified, trailing zeroes are not removed from the result.

i

The char, short, int, long, or long long argument is printed as a decimal (base 10) integer. If the argument is signed, it will be printed as a signed value. If the argument is unsigned, it will be printed as an unsigned value. This conversion has the same meaning as d.

o

The char, short, int, long, or long long argument is printed as an unsigned octal (base 8) integer. Arguments that are signed or unsigned may be used with this conversion. If the # flag is specified, the precision of the result will be increased if necessary to force the first digit of the result to be a zero.

p

The pointer or uintptr_t argument is printed as a hexadecimal (base 16) integer. D accepts pointer arguments of any type. If the # flag is specified, a non-zero result will have 0x prepended to it.

s

The argument must be an array of char or a string. Bytes from the array or string are read up to a terminating null character or the end of the data and interpreted and printed as ASCII characters. If the precision is not specified, it is taken to be infinite, so all characters up to the first null character are printed. If the precision is specified, only that portion of the character array that will display in the corresponding number of screen columns is printed. If an argument of type char * is to be formatted, it should be cast to string or prefixed with the D stringof operator to indicate that DTrace should trace the bytes of the string and format them.

S

The argument must be an array of char or a string. The argument is processed as if by the %s conversion, but any ASCII characters that are not printable are replaced by the corresponding escape sequence described in Table 2–5.

u

The char, short, int, long, or long long argument is printed as an unsigned decimal (base 10) integer. Arguments that are signed or unsigned may be used with this conversion, and the result is always formatted as unsigned.

wc

The int argument is converted to a wide character (wchar_t) and the resulting wide character is printed.

ws

The argument must be an array of wchar_t. Bytes from the array are read up to a terminating null character or the end of the data and interpreted and printed as wide characters. If the precision is not specified, it is taken to be infinite, so all wide characters up to the first null character are printed. If the precision is specified, only that portion of the wide character array that will display in the corresponding number of screen columns is printed.

x, X

The char, short, int, long, or long long argument is printed as an unsigned hexadecimal (base 16) integer. Arguments that are signed or unsigned may be used with this conversion. If the x form of the conversion is used, the letter digits abcdef are used. If the X form of the conversion is used, the letter digits ABCDEF are used. If the # flag is specified, a non-zero result will have 0x (for %x) or 0X (for %X) prepended to it.

Y

The uint64_t argument is interpreted to be the number of nanoseconds since 00:00 Universal Coordinated Time, January 1, 1970, and is printed in the following cftime(3C) form: “%Y %a %b %e %T %Z.” The current number of nanoseconds since 00:00 UTC, January 1, 1970 is available in the walltimestamp variable.

%

Print a literal % character. No argument is converted. The entire conversion specification must be %%.

printa

The printa function is used to format the results of aggregations in a D program. The function is invoked using one of two forms:

printa(@aggregation-name);
printa(format-string, @aggregation-name);

If the first form of the function is used, the dtrace(1M) command takes a consistent snapshot of the aggregation data and produces output equivalent to the default output format used for aggregations, described in Chapter 9, Aggregations.

If the second form of the function is used, the dtrace(1M) command takes a consistent snapshot of the aggregation data and produces output according to the conversions specified in the format string, according to the following rules:

  • The format conversions must match the tuple signature used to create the aggregation. Each tuple element may only appear once. For example, if you aggregate a count using the following D statements:

    @a["hello", 123] = count();
    @a["goodbye", 456] = count();
    

    and then add the D statement printa(format-string, @a) to a probe clause, dtrace will snapshot the aggregation data and produce output as if you had entered the statements:

    printf(format-string, "hello", 123);
    printf(format-string, "goodbye", 456);
    

    and so on for each tuple defined in the aggregation.

  • Unlike printf, the format string you use for printa need not include all elements of the tuple. That is, you can have a tuple of length 3 and only one format conversion. Therefore, you can omit any tuple keys from your printa output by changing your aggregation declaration to move the keys you want to omit to the end of the tuple and then omit corresponding conversion specifiers for them in the printa format string.

  • The aggregation result can be included in the output by using the additional @ format flag character, which is only valid when used with printa. The @ flag can be combined with any appropriate format conversion specifier, and may appear more than once in a format string so that your tuple result can appear anywhere in the output and can appear more than once. The set of conversion specifiers that can be used with each aggregating function are implied by the aggregating function's result type. The aggregation result types are:

    avg

    uint64_t

    count

    uint64_t

    lquantize

    int64_t

    max

    uint64_t

    min

    uint64_t

    quantize

    int64_t

    sum

    uint64_t

    For example, to format the results of avg, you can apply the %d, %i, %o, %u, or %x format conversions. The quantize and lquantize functions format their results as an ASCII table rather than as a single value.

The following D program shows a complete example of printa, using the profile provider to sample the value of caller and then formatting the results as a simple table:

profile:::profile-997
{
	@a[caller] = count();
}

END
{
	printa("%@8u %a\n", @a);
}

If you use dtrace to execute this program, wait a few seconds, and press Control-C, you will see output similar to the following example:

# dtrace -s printa.d
^C
CPU     ID                    FUNCTION:NAME
  1      2                             :END        1 0x1
       1 ohci`ohci_handle_root_hub_status_change+0x148
       1 specfs`spec_write+0xe0
       1 0xff14f950
       1 genunix`cyclic_softint+0x588
       1 0xfef2280c
       1 genunix`getf+0xdc
       1 ufs`ufs_icheck+0x50
       1 genunix`infpollinfo+0x80
       1 genunix`kmem_log_enter+0x1e8
       ...

trace Default Format

If the trace function is used to capture data rather than printf, the dtrace command formats the results using a default output format. If the data is 1, 2, 4, or 8 bytes in size, the result is formatted as a decimal integer value. If the data is any other size and is a sequence of printable characters if interpreted as a sequence of bytes, it will be printed as an ASCII string. If the data is any other size and is not a sequence of printable characters, it will be printed as a series of byte values formatted as hexadecimal integers.

print()

The print action formats output based on the type information that DTrace knows about. The output data is formatted based on the type. The following rules cover the output format:

  • The type always preceeds the data.

  • All integers and pointers are printed in hex.

  • Floats and doubles are printed as though the printf string "%.7e" were applied.

  • Long doubles are printed as though the printf string "%.16LE" were applied"

  • Every entry in an array is printed.

  • Every member in a structure is printed out. This is applied recursively.

The following examples demonstrate using the print action and some of its formatting characteristics. The first example shows the difference between using trace and print for a builtin variable.

# dtrace -qn 'BEGIN{ trace(timestamp); }'
1809563006895229
# dtrace -qn 'BEGIN{ print(timestamp); }'
uint64_t 0x66d997f0b030a

This example shows how a structure is printed with type information. p0 is the global address of the first process in the illumos operating system.

# dtrace -qn 'BEGIN{ print(`p0); }'
proc_t {
    struct vnode *p_exec = 0
    struct as *p_as = 0xfffffffffbc30ac0
    struct plock *p_lockp = 0xfffffffffbc6d3c8
    kmutex_t p_crlock = {
        void *[1] _opaque = [ 0 ]
    }
    struct cred *p_cred = 0xffffff0d29fd9db0
    int p_swapcnt = 0
    char p_stat = '\002'
    char p_wcode = '\0'
...

This final example shows how you might use this information with other providers.

# dtrace -n 'fbt::zio_done:entry{ print(*args[0]); }'
dtrace: description 'fbt::zio_done:entry' matched 1 probe
CPU     ID                    FUNCTION:NAME
 10  53680                   zio_done:entry zio_t {
    zbookmark_t io_bookmark = {
        uint64_t zb_objset = 0
        uint64_t zb_object = 0
        int64_t zb_level = 0
        uint64_t zb_blkid = 0x14
    }
    zio_prop_t io_prop = {
        enum zio_checksum zp_checksum = ZIO_CHECKSUM_INHERIT
        enum zio_compress zp_compress = ZIO_COMPRESS_INHERIT
        dmu_object_type_t zp_type = DMU_OT_NONE
        uint8_t zp_level = 0
        uint8_t zp_copies = 0
        uint8_t zp_dedup = 0
        uint8_t zp_dedup_verify = 0
    }
    zio_type_t io_type = ZIO_TYPE_NULL
    enum zio_child io_child_type = ZIO_CHILD_VDEV
    int io_cmd = 0
    uint8_t io_priority = 0x4
    uint8_t io_reexecute = 0
    uint8_t [2] io_state = [ 0x1, 0 ]
    uint64_t io_txg = 0x8dfb3a
    spa_t *io_spa = 0xffffff0d37f3a500
    blkptr_t *io_bp = 0xffffff0d39581a00
    blkptr_t *io_bp_override = 0
    blkptr_t io_bp_copy = {
        dva_t [3] blk_dva = [ 
            dva_t {
                uint64_t [2] dva_word = [ 0x5, 0x394c7972 ]
            }, 
...

print Type Information Sources

DTrace has three primary sources for type information for the type information used when formatting data for print action.

The D language defines fundamental types for integer and floating-point constants. These are enumerated in Chapter 2, Buffers and Buffering. Builtin actions, variables, and subroutines are defined in terms of these types. These types will match those that are being used by a user program or Operating System kernel.

Next, DTrace uses the types that are defined in a D script. These can be brought in by using the C Pre-processor or by declaring them directly. These can be used the same as any built in type.

Finally, DTrace uses the C Compact Type Information (CTF) that is embedded in the operating system kernel and its modules. (DTrace does not currently use CTF data in user binaries or modules.) The system's CTF data provides most of the types that DTrace uses via functionality such as print.

PreviousNext