The bpftrace Language (pre-release)
The bpftrace (bt) language is inspired by the D language used by dtrace and uses the same program structure.
Each script consists of a Preamble and one or more Action Blocks.
preamble
actionblock1
actionblock2
Action Blocks
Each action block consists of three parts:
[name=]probe[,probe]
/predicate/ {
action
}
- Probes
A probe specifies the event and event type to attach to. See the probes section for more detail. - Predicate
The predicate is an optional condition that must be met for the action to be executed. - Action
Actions are the programs that run when an event fires (and the predicate is met). An action is a semicolon (;) separated list of statements and always enclosed by brackets{}.
A basic script that traces the open(2) and openat(2) system calls can be written as follows:
begin {
printf("Tracing open syscalls... Hit Ctrl-C to end.\n");
}
tracepoint:syscalls:sys_enter_open,
tracepoint:syscalls:sys_enter_openat {
printf("%-6d %-16s %s\n", pid, comm, str(args.filename));
}
The above script has two action blocks and a total of 3 probes.
The first action block uses the special begin probe, which fires once during bpftrace startup.
This probe is used to print a header, indicating that the tracing has started.
The second action block uses two probes, one for open and one for openat, and defines an action that prints the file being open ed as well as the pid and comm of the process that execute the syscall.
See the Probes section for details on the available probe types.
Arguments
This refers to traced function and tracepoint arguments.
There are two ways to access these arguments and the way you choose depends on the probe type.
- For
uprobe,kprobe, andusdtuse theargNformat. - For
rawtracepoint,tracepoint,fentry,fexit, anduprobe(with DWARF) use theargsformat.
argN
These keywords allow access to the nth argument passed to the function being traced.
For the first argument use arg0, for the second arg1, and so forth.
The type of each arg is an int64 and will often require casting to non scalar types, e.g., $x = (struct qstr *)arg1.
These are extracted from the CPU registers.
The amount of args passed in registers depends on the CPU architecture.
args
This keyword represents the struct of all arguments of the traced function.
You can print the entire structure via print(args) or access particular fields using the dot syntax, e.g., $x = str(args.filename);.
To see the args for a particular function, you can use verbose listing mode.
Example:
# bpftrace -lv 'fentry:tcp_reset'
fentry:tcp_reset
struct sock * sk
struct sk_buff * skb
Arrays
bpftrace supports accessing one-dimensional arrays like those found in C.
Constructing arrays from scratch, like int a[] = {1,2,3} in C, is not supported.
They can only be read into a variable from a pointer.
The [] operator is used to access elements.
struct MyStruct {
int y[4];
}
kprobe:dummy {
$s = (struct MyStruct *) arg0;
print($s.y[0]);
}
Command Line Parameters
Custom options can be passed to a bpftrace program itself via positional or named parameters. It is recommended to use named parameters because they are less ambiguous in terms of their types and meaning (they have actual names).
Named Parameters
Named parameters can be accessed in a bpftrace program via the getopt function call, e.g., getopt("my_named_param", 5), getopt("my_bool_param"). The first argument is the parameter name and the second is the default value when that argument is not passed on the command line. If the second argument is not provided it indicates that the parameter is a boolean type.
Named parameters must come AFTER a double dash (--) when being passed on the command line, e.g.,
# bpftrace -e 'begin { print((getopt("aa", 1), getopt("bb"))); }' -- --aa=20 --bb
Here getopt("aa", 1) would evaluate to 20 and getopt("bb") would evaluate to true.
Named parameters require the = to set their value unless they are boolean parameters (like 'bb' above). The supported types are string, number, and boolean.
Positional Parameters
Positional parameters can be accessed in a bpftrace program via, what looks like, a numbered scratch variable, e.g. $1, $2, ..., $N. So $1 would be the first positional parameter and so on.
Positional parameters can be placed before or after a double dash, e.g.,
# bpftrace -e 'begin { print(($1, $2)); }' p1 -- 20
Here $1 would evaluate to a string p1 and $2 would evaluate to a number 20.
If a parameter is used that was not provided, it will default to zero for a numeric context, and "" for a string context.
Positional parameters may also be used in probe arguments and will be treated as a string parameter, e.g., tracepoint:block:block_rq_issue /args.bytes > $1/.
If a positional parameter is used in str(), it is interpreted as a pointer to the actual given string literal, which allows to do pointer arithmetic on it.
Only addition of a single constant, less or equal to the length of the supplied string, is allowed. Example:
# bpftrace -e 'begin { printf("I got %d, %s (%d args)\n", $1, str($2), $#); }' 42 "hello"
I got 42, hello (2 args)
# bpftrace -e 'begin { printf("%s\n", str($1 + 1)) }' "hello"
ello
$# is a special builtin that returns the number of positional arguments supplied.
Comments
Both single line and multi line comments are supported.
// A single line comment
interval:s:1 { // can also be used to comment inline
/*
a multi line comment
*/
print(/* inline comment block */ 1);
}
Conditionals
Conditional expressions are supported in the form of if/else statements and the ternary operator.
The ternary operator consists of three operands: a condition followed by a ?, the expression to execute when the condition is true followed by a : and the expression to execute if the condition is false.
condition ? ifTrue : ifFalse
Both the ifTrue and ifFalse expressions must be of the same type, mixing types is not allowed.
The ternary operator can be used as part of an assignment.
$a == 1 ? print("true") : print("false");
$b = $a > 0 ? $a : -1;
If/else statements are supported.
if (condition) {
ifblock
} else if (condition) {
if2block
} else {
elseblock
}
Config Block
To improve script portability, you can set bpftrace Config Variables via the config block, which can only be placed at the top of the script (in the preamble) before any action blocks.
config = {
stack_mode=perf;
max_map_keys=2
}
begin { ... }
uprobe:./testprogs/uprobe_test:uprobeFunction1 { ... }
The names of the config variables can be in the format of environment variables
or their lowercase equivalent without the BPFTRACE_ prefix. For example,
BPFTRACE_STACK_MODE, STACK_MODE, and stack_mode are equivalent.
Note: Environment variables for the same config take precedence over those set inside a script config block.
Config Variables
Some behavior can only be controlled through config variables, which are listed here.
These can be set via the Config Block directly in a script (before any probes) or via their environment variable equivalent, which is upper case and includes the BPFTRACE_ prefix e.g. ``stack_mode’s environment variable would be BPFTRACE_STACK_MODE`.
cache_user_symbols
Default: PER_PROGRAM if ASLR disabled or -c option given, PER_PID otherwise.
- PER_PROGRAM - each program has its own cache. If there are more processes with enabled ASLR for a single program, this might produce incorrect results.
- PER_PID - each process has its own cache. This is accurate for processes with ASLR enabled, and enables bpftrace to preload caches for processes running at probe attachment time. If there are many processes running, it will consume a lot of a memory.
- NONE - caching disabled. This saves the most memory, but at the cost of speed.
cpp_demangle
Default: true
C++ symbol demangling in userspace stack traces is enabled by default.
This feature can be turned off by setting the value of this variable to false.
lazy_symbolication
Default: false
For user space symbols, symbolicate lazily/on-demand (true) or symbolicate everything ahead of time (false).
license
Default: "GPL"
The license bpftrace will use to load BPF programs into the linux kernel. Here is the list of accepted license strings:
- GPL
- GPL v2
- GPL and additional rights
- Dual BSD/GPL
- Dual MIT/GPL
- Dual MPL/GPL
log_size
Default: 1000000
Log size in bytes.
max_bpf_progs
Default: 1024
This is the maximum number of BPF programs (functions) that bpftrace can generate. The main purpose of this limit is to prevent bpftrace from hanging since generating a lot of probes takes a lot of resources (and it should not happen often).
max_cat_bytes
Default: 10240
Maximum bytes read by cat builtin.
max_map_keys
Default: 4096
This is the maximum number of keys that can be stored in a map. Increasing the value will consume more memory and increase startup times. There are some cases where you will want to, for example: sampling stack traces, recording timestamps for each page, etc.
max_probes
Default: 1024
This is the maximum number of probes that bpftrace can attach to. Increasing the value will consume more memory, increase startup times, and can incur high performance overhead or even freeze/crash the system.
max_strlen
Default: 1024
The maximum length (in bytes) for values created by str(), buf() and path().
This limit is necessary because BPF requires the size of all dynamically-read strings (and similar) to be declared up front. This is the size for all strings (and similar) in bpftrace unless specified at the call site. There is no artificial limit on what you can tune this to. But you may be wasting resources (memory and cpu) if you make this too high.
missing_probes
Default: error
Controls handling of probes which cannot be attached because they do not exist (in the kernel or in the traced binary) or there was an issue during attachment.
The possible options are:
error- always fail on missing probeswarn- print a warning but continue executionignore- silently ignore missing probes
on_stack_limit
Default: 32
The maximum size (in bytes) of individual objects that will be stored on the BPF stack. If they are larger than this limit they will be stored in pre-allocated memory.
This exists because the BPF stack is limited to 512 bytes and large objects make it more likely that we’ll run out of space. bpftrace can store objects that are larger than the on_stack_limit in pre-allocated memory to prevent this stack error. However, storing in pre-allocated memory may be less memory efficient. Lower this default number if you are still seeing a stack memory error or increase it if you’re worried about memory consumption.
perf_rb_pages
Default: Based on available system memory
Number of pages to allocate for each created ring or perf buffer (there is only one of each max).
The minimum is: 1 * the number of cpus on your machine.
If you’re getting a lot of dropped events bpftrace may not be processing events in the ring buffer (or perf buffer if you're using skboutput) fast enough.
It may be useful to bump the value higher so more events can be queued up.
The tradeoff is that bpftrace will use more memory.
The default value is based on available system memory; max is 4096 pages (16mb) and min is 64 pages (256kb), which presumes 4k page size.
If your system has a larger page size the amount of allocated memory will be the same but we'll just use fewer pages.
show_debug_info
This is only available if the Blazesym library is available at build time. If it is available this defaults to true, meaning that when printing ustack and kstack symbols bpftrace will also show (if debug info is available) symbol file and line ('bpftrace' stack mode) and a label if the function was inlined ('bpftrace' and 'perf' stack modes).
There might be a performance difference when symbolicating, which is the only reason to disable this.
stack_mode
Default: bpftrace
Output format for ustack and kstack builtins. Available modes/formats:
- bpftrace
- perf
- raw: no symbolication (print instruction pointer)
- build_id: no symbolication (print build_id and file offset) (ustack only)
This can be overwritten at the call site.
str_trunc_trailer
Default: ..
Trailer to add to strings that were truncated. Set to empty string to disable truncation trailers.
print_maps_on_exit
Default: true
Controls whether maps are printed on exit. Set to false in order to change the default behavior and not automatically print maps at program exit.
unstable features
These are the list of unstable features:
unstable_tseries- feature flag for time series map typeunstable_addr- feature flag for address of operator (&)
All of these accept the following options:
Default: warn
error- fail if this feature is usedwarn- enable feature but print a warningenable- enable feature
Data Types
The following fundamental types are provided by the language.
| Type | Description |
| bool | true or false |
| uint8 | Unsigned 8 bit integer |
| int8 | Signed 8 bit integer |
| uint16 | Unsigned 16 bit integer |
| int16 | Signed 16 bit integer |
| uint32 | Unsigned 32 bit integer |
| int32 | Signed 32 bit integer |
| uint64 | Unsigned 64 bit integer |
| int64 | Signed 64 bit integer |
| string | See below |
begin { $x = 1<<16; printf("%d %d\n", (uint16)$x, $x); }
/*
* Output:
* 0 65536
*/
Integers are by default represented as the smallest possible
type, e.g. 1 is a uint8 and -1 is an int8. However integers,
scratch variables, and map keys/values will be automatically upcast
when necessary, e.g.
$a = 1; // starts as uint8
$b = -1000; // starts as int16
$a = $b; // $a now becomes an int16
$c = (uint64)1;
$d = (int64)-1;
$c = $d; // ERROR: type mismatch because there isn't a larger type
// that fits both.
Additionally, when vmlinux BTF is available, bpftrace supports casting to some of the kernel's fixed integer types:
$a = (uint64_t)1; // $a is a uint64
String
bpftrace also supports a string data type, which it uses for string literals, e.g. "hello".
Similar to C this is represented as a well formed char array (NULL terminated).
Additionally, all BTF char arrays (char[] or int8[]) are automatically converted to a bpftrace string but can be casted back to an int array if needed, e.g. $a = (int8[])"mystring";
It also may be necessary to utilize the str() function if bpftrace can't determine the correct address space (user or kernel).
Filters/Predicates
Filters (also known as predicates) can be added after probe names. The probe still fires, but it will skip the action unless the filter is true.
kprobe:vfs_read /arg2 < 16/ {
printf("small read: %d byte buffer\n", arg2);
}
kprobe:vfs_read /comm == "bash"/ {
printf("read by %s\n", comm);
}
Floating-point
Floating-point numbers are not supported by BPF and therefore not by bpftrace.
Identifiers
Identifiers must match the following regular expression: [_a-zA-Z][_a-zA-Z0-9]*
Keywords
break, config, continue, else, for, if, import, let, macro, offsetof, return, sizeof, unroll, while (deprecated).
return- The return keyword is used to exit the current probe. This differs fromexit()in that it doesn’t exit bpftrace.
Literals
Integer and string literals are supported.
Integer literals can be defined in the following formats:
- decimal (base 10)
- octal (base 8)
- hexadecimal (base 16)
- scientific (base 10)
Octal literals have to be prefixed with a 0 e.g. 0123.
Hexadecimal literals start with either 0x or 0X e.g. 0x10.
Scientific literals are written in the <m>e<n> format which is a shorthand for m*10^n e.g. $i = 2e3;.
Note that scientific literals are integer only due to the lack of floating point support e.g. 1e-3 is not valid.
To improve the readability of big literals an underscore _ can be used as field separator e.g. 1_000_123_000.
Integer suffixes as found in the C language are parsed by bpftrace to ensure compatibility with C headers/definitions but they’re not used as size specifiers.
123UL, 123U and 123LL all result in the same integer type with a value of 123.
These duration suffixes are also supported: ns, us, ms, s, m, h, and d. All get turned into integer values in nanoseconds, e.g.
$a = 1m;
print($a); // prints 60000000000
Character literals are not supported at this time, and the corresponding ASCII code must be used instead:
begin {
printf("Echo A: %c\n", 65);
}
String literals can be defined by enclosing the character string in double quotes e.g. $str = "Hello world";.
Strings support the following escape sequences:
| \n | Newline |
| \t | Tab |
| \0nn | Octal value nn |
| \xnn | Hexadecimal value nn |
Loops
For
for loops can be used to iterate over elements in a map, or over a range of integers, provided as two unary expressions separated by ...
for ($kv : @map) {
block;
}
for ($i : start..end) {
block;
}
The variable declared in the for loop will be initialised on each iteration.
If the iteration is over a map, the value will be a tuple containing a key and a value from the map, i.e. $kv = (key, value):
@map[10] = 20;
for ($kv : @map) {
print($kv.0); // key
print($kv.1); // value
}
If a map has multiple keys, the loop variable will be initialised with nested tuple of the form: ((key1, key2, ...), value):
@map[10,11] = 20;
for ($kv : @map) {
print($kv.0.0); // key 1
print($kv.0.1); // key 2
print($kv.1); // value
}
If an integer range is provided, the value will be an integer value for each element in the range, inclusive of the start value and exclusive of the end value:
for ($cpu : 0..ncpus) {
print($cpu); // current value in range
}
Note that you cannot adjust the range itself after the loop has started.
The for start and end values are evaluated once, not on each loop iteration.
For example, the following will print 0 through 9:
$a = 10;
for ($i : 0..$a) {
print($i);
$a--;
}
Both for loops support the following control flow statements:
| continue | skip processing of the rest of the block and proceed to the next iteration |
| break | terminate the loop |
| return | return from the current probe |
While
While loops are deprecated and may be removed in the future; please use For loops instead as these are more easily verified to be bounded.
Unroll
Loop unrolling is also supported with the unroll statement.
unroll(n) {
block;
}
The compiler will evaluate the block n times and generate the BPF code for the block n times.
As this happens at compile time n must be a constant greater than 0 (n > 0).
The following two probes compile into the same code:
interval:s:1 {
unroll(3) {
print("Unrolled")
}
}
interval:s:1 {
print("Unrolled")
print("Unrolled")
print("Unrolled")
}
Macros
bpftrace macros (as opposed to C macros) provide a way for you to structure your script. They can be useful when you want to factor out code into smaller, more understandable parts. Or if you want to share code between probes.
At a high level, macros can be thought of as semantic aware text replacement.
They accept (optional) variable, map, and expression arguments.
The body of the macro may only access maps and external variables passed in through the arguments, which is why these are often referred to as "hygienic macros", but the macro body can create new variables which only exist inside the body.
A macro's parameter signature specifies how an argument will be used.
For example macro test($a, b, @c) indicates that $a needs to be a scratch variable (which might be mutated), that b needs to be an expression that will be inserted where ever b is used in the macro body, and that @c needs to be a map (which might be mutated).
A valid use of this macro could be test($x, 1 + 2, @y).
Variables and maps can also be used for ident parameters that expect expressions and would be the same as writing { @y } (Block Expression).
Here are some valid usages of macros:
macro one() {
1
}
macro add_one(x) {
x + 1
}
macro add_one_to_each($a, @b) {
$a += 1;
@b += 1;
}
macro side_effects(x) {
x;
x;
x;
}
macro add_two(x) {
add_one(x) + 1
}
begin {
print(one()); // prints 1
print(one); // prints 1 (bare identifier works if the macro accepts 0 args)
$a = 10;
print(add_one($a)); // prints 11
print(add_one(1 + 1)); // prints 3
@b = 5;
add_one_to_each($a, @b);
print($a + @b) // prints 17
side_effects({ printf("hi") }) // prints hihihi
print(add_two(1)); // prints 3
}
Some examples of invalid macro usage:
macro unhygienic_access() {
@x++ // BAD: @x not passed in
}
macro wrong_parameter_type($x) {
$x++
}
begin {
@x = 1;
unhygienic_access();
wrong_parameter_type(@x); // BAD: macro expects a scratch variable
wrong_parameter_type(1 + 1); // BAD: macro expects a scratch variable
}
Note: If you want the passed in expression to only be executed once, simply bind it to a variable, e.g.,
macro add_one(x) {
let $x = x;
$x + 1
}
Operators and Expressions
Arithmetic Operators
The following operators are available for integer arithmetic:
| + | integer addition |
| - | integer subtraction |
| * | integer multiplication |
| / | integer division |
| % | integer modulo |
Operations between a signed and an unsigned integer are allowed providing bpftrace can statically prove a safe conversion is possible. If safe conversion is not guaranteed, the operation is undefined behavior and a corresponding warning will be emitted.
If the two operands are different size, the smaller integer is implicitly
promoted to the size of the larger one. Sign is preserved in the promotion.
For example, (uint32)5 + (uint8)3 is converted to (uint32)5 + (uint32)3
which results in (uint32)8.
Pointers may be used with arithmetic operators but only for addition and subtraction. For subtraction, the pointer must appear on the left side of the operator. Pointers may also be used with logical operators; they are considered true when non-null.
Logical Operators
| && | Logical AND |
| || | Logical OR |
| ! | Logical NOT |
Bitwise Operators
| & | AND |
| | | OR |
| ^ | XOR |
| << | Left shift the left-hand operand by the number of bits specified by the right-hand expression value |
| >> | Right shift the left-hand operand by the number of bits specified by the right-hand expression value |
Relational Operators
The following relational operators are defined for integers and pointers.
| < | left-hand expression is less than right-hand |
| <= | left-hand expression is less than or equal to right-hand |
| > | left-hand expression is bigger than right-hand |
| >= | left-hand expression is bigger or equal to than right-hand |
| == | left-hand expression equal to right-hand |
| != | left-hand expression not equal to right-hand |
The following relation operators are available for comparing strings, integer arrays, and tuples.
| == | left-hand string equal to right-hand |
| != | left-hand string not equal to right-hand |
Assignment Operators
The following assignment operators can be used on both map and scratch variables:
| = | Assignment, assign the right-hand expression to the left-hand variable |
| <<= | Update the variable with its value left shifted by the number of bits specified by the right-hand expression value |
| >>= | Update the variable with its value right shifted by the number of bits specified by the right-hand expression value |
| += | Increment the variable by the right-hand expression value |
| -= | Decrement the variable by the right-hand expression value |
| *= | Multiple the variable by the right-hand expression value |
| /= | Divide the variable by the right-hand expression value |
| %= | Modulo the variable by the right-hand expression value |
| &= | Bitwise AND the variable by the right-hand expression value |
| |= | Bitwise OR the variable by the right-hand expression value |
| ^= | Bitwise XOR the variable by the right-hand expression value |
All these operators are syntactic sugar for combining assignment with the specified operator.
@ -= 5 is equal to @ = @ - 5.
Increment and Decrement Operators
The increment (++) and decrement (--) operators can be used on integer and pointer variables to increment their value by one.
They can only be used on variables and can either be applied as prefix or suffix.
The difference is that the expression x++ returns the original value of x, before it got incremented while ++x returns the value of x post increment.
$x = 10;
$y = $x--; // y = 10; x = 9
$a = 10;
$b = --$a; // a = 9; b = 9
Note that maps will be implicitly declared and initialized to 0 if not already declared or defined. Scratch variables must be initialized before using these operators.
Note ++/-- on a shared global variable can lose updates. See count() for more details.
Block Expressions
A block can be used as expression, as long as the last statement of the block is an expression with no trailing semi-colon.
let $a = {
let $b = 1;
$b
};
This can be used anywhere an expression can be used.
Note: There will be a warning for discarded expressions, e.g.,
{ 1 } // Warning
$a = { 1 } // No Warning
has_key(@a, 1); // Warning
$b = has_key(@a, 1); // No Warning
The warning can also be silenced by utilizing the Discard Expression:
_ = has_key(@a, 1); // No Warning
Preamble
The preamble consists of multiple optional pieces:
- preprocessor definitions
- type definitions
- a config block
- map declarations
For example:
#include <linux/socket.h>
#define RED "\033[31m"
struct S {
int x;
}
config = {
stack_mode=perf
}
let @a = lruhash(100);
Probes
bpftrace supports various probe types which allow the user to attach BPF programs to different types of events.
Each probe starts with a provider (e.g. kprobe) followed by a colon (:) separated list of options.
An optional name may precede the provider with an equals sign (e.g. name=), which is reserved for internal use and future features.
The amount of options and their meaning depend on the provider and are detailed below.
The valid values for options can depend on the system or binary being traced, e.g. for uprobes it depends on the binary.
Also see Listing Probes.
It is possible to associate multiple probes with a single action as long as the action is valid for all specified probes.
Multiple probes can be specified as a comma (,) separated list:
kprobe:tcp_reset,kprobe:tcp_v4_rcv {
printf("Entered: %s\n", probe);
}
Wildcards are supported too:
kprobe:tcp_* {
printf("Entered: %s\n", probe);
}
Both can be combined:
kprobe:tcp_reset,kprobe:*socket* {
printf("Entered: %s\n", probe);
}
By default, bpftrace requires all probes to attach successfully or else an error is returned. However this can be changed using the missing_probes config variable.
Most providers also support a short name which can be used instead of the full name, e.g. kprobe:f and k:f are identical.
| Probe Name | Short Name | Description |
begin/end | - | Built-in events |
bench | - | Micro benchmarks |
self | - | Built-in events |
hardware | h | Processor-level events |
interval | i | Timed output |
iter | it | Iterators tracing |
fentry/fexit | f/fr | Kernel functions tracing with BTF support |
kprobe/kretprobe | k/kr | Kernel function start/return |
profile | p | Timed sampling |
rawtracepoint | rt | Kernel static tracepoints with raw arguments |
software | s | Kernel software events |
tracepoint | t | Kernel static tracepoints |
uprobe/uretprobe | u/ur | User-level function start/return |
usdt | U | User-level static tracepoints |
watchpoint | w | Memory watchpoints |
begin/end
These are special built-in events provided by the bpftrace runtime.
begin is triggered before all other probes are attached.
end is triggered after all other probes are detached.
Each of these probes can be used any number of times, and they will be executed in the same order they are declared.
For imports containing begin and end probes, an effort is made to preserve the partial order implied by the import graph (e.g. if A depends on B, then B will have both its begin and end probes executed first), but this is not strictly guaranteed.
Note that specifying an end probe doesn’t override the printing of 'non-empty' maps at exit.
To prevent printing all used maps need be cleared in the end probe:
end {
clear(@map1);
clear(@map2);
}
test
test is a special built-in probe type for creating tests.
bpftrace executes each test probe and checks the return value, error count and possible exit calls to determine a pass.
If multiple test probes exist in a script, bpftrace executes them sequentially in the order they are specified.
To run test probes, you must run bpftrace in test mode: bpftrace --test ...; otherwise test probes will be ignored.
test:okay {
print("I'm okay! This output will be suppressed.");
}
test:failure {
print("This is a failure! This output will be shown");
return 1;
}