Modules

Iprof

moduleSource

Available on macOS, Linux, Windows, Wasm

#import "Iprof";

A frame profiler with a text report and an optional overlay.

As a metaprogram plugin (+Iprof) it adds zones to your procedures for you. In manual mode (MANUAL_MODE = true) you name the zones and open and close each one yourself:

  1. Import the module as a client in manual mode. Pass USE_GRAPHICS = false to leave out the overlay.
  2. Call Prof_init_highlevel() once.
  3. Give each zone a slot in zones (up to MAX_PROFILING_ZONES_FOR_MANUAL_MODE): set its name and initted = true. A zone that is not initted is refused at run time and left out of reports.
  4. Wrap the code in zone_begin(index) and zone_end(). Zones nest, and a zone may nest in itself.
  5. Call update(true) once per frame. It closes the frame and files the times away.
  6. Read the result with create_report(), then log_text_report(report) or save_csv_report(report, path).

Zones, the open-zone stack and the history are process globals, so zones cannot be opened from several threads.

Example
#import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = true, USE_GRAPHICS = false, DO_HISTORY = false);

ZONE_FRAME :: 0;
ZONE_WORK :: 1;

main :: () {
    Prof_init_highlevel();
    zones[ZONE_FRAME].name = "frame";
    zones[ZONE_FRAME].initted = true;
    zones[ZONE_WORK].name = "work";
    zones[ZONE_WORK].initted = true;

    for 1..3 {
        zone_begin(ZONE_FRAME);
        zone_begin(ZONE_WORK);
        zone_end();
        zone_end();
        update(true);
    }

    report := create_report();
    assert(report.records.count == 2);
    log_text_report(report);
}

Doesn't run in the browser (skipped on wasm).

Module parameters#

IMPORT_MODE := Import_Mode.METAPROGRAM
.METAPROGRAM profiles the program that imports it; .CLIENT is for a program that reports to a profiler.
MANUAL_MODE := false
Zones are started and stopped by hand, with no automatic ones.
USE_GRAPHICS := true
Includes the graphical report.
DO_HISTORY := true
Keeps the history of each value across frames.

Procedures#

Iprof report#

(report: *Report, min_self_time := 0., min_hier_time := 0.)

Logs a report as text: a title line, a header, then one line per zone with its self time and hierarchical time in milliseconds, its call count and its heat. Rows below min_self_time or min_hier_time (milliseconds, 0 for all) are skipped.

#import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = true, USE_GRAPHICS = false, DO_HISTORY = false);

main :: () {
    Prof_init_highlevel();
    zones[0].name = "work";
    zones[0].initted = true;
    zone_begin(0);
    zone_end();
    update(true);
    log_text_report(create_report());
}

Doesn't run in the browser (skipped on wasm).

(report: *Report, filename: string)

Writes a report to filename as CSV: the command line, then name,self (ms),hier (ms),calls per zone. A name with a quote or a comma is quoted. A failed write logs an error.

#import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = true, USE_GRAPHICS = false, DO_HISTORY = false);

main :: () {
    Prof_init_highlevel();
    zones[0].name = "work";
    zones[0].initted = true;
    zone_begin(0);
    zone_end();
    update(true);
    save_csv_report(create_report(), "profile.csv");
}
↗

This one is only checked to compile, not run.

Iprof history values#

(x: s64)

Chooses which smoothing speed get_value reads: 0 reacts fastest, higher numbers average over more frames.

Iprof public state#

(desired: Report_Mode)

Picks what the report and overlay show for each zone.

#import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = true, USE_GRAPHICS = false, DO_HISTORY = false);

main :: () {
    set_report_mode(.HIERARCHICAL_TIME);
    assert(displayed_quantity == .HIERARCHICAL_TIME);
}

Doesn't run in the browser (skipped on wasm).

Iprof stack inspection#

Runtime zones#

()

Starts the clock and the zone tables. Everything that needs them calls it first, so calling it yourself is optional.

(zone_index: s64)

Opens the zone in slot zone_index of zones (manual mode). Close it with zone_end(). The slot needs a name and initted = true first. Time until the matching zone_end counts toward the zone, and toward its parent for the hierarchical time.

#import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = true, USE_GRAPHICS = false, DO_HISTORY = false);

main :: () {
    zones[0].name = "work";
    zones[0].initted = true;
    zone_begin(0);
    // ... the code to time ...
    zone_end();
}

Doesn't run in the browser (skipped on wasm).

()

Closes the zone most recently opened with zone_begin. Calling it with no zone open fails an assert.

#import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = true, USE_GRAPHICS = false, DO_HISTORY = false);

main :: () {
    zones[0].name = "outer";
    zones[0].initted = true;
    zones[1].name = "inner";
    zones[1].initted = true;

    zone_begin(0);
    zone_begin(1);
    zone_end();  // closes "inner"
    zone_end();  // closes "outer"
}

Doesn't run in the browser (skipped on wasm).

(size: s64)

Resets the zone hash table to size slots, which must be a power of two. It fails while a zone is open.

Client setup#

Runtime traversal#

Frame history#

()

Sets up the frame history and the report state. Call it once before the first update. update and create_report call it too, so calling it twice is fine.

#import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = true, USE_GRAPHICS = false, DO_HISTORY = false);

main :: () {
    Prof_init_highlevel();
    Prof_init_highlevel();  // calling it again does nothing
}

Doesn't run in the browser (skipped on wasm).

update2 overloadsSource
(s: *History_Scalar, new_value: float64, k_array: [] float64)
(record_data: bool)

Folds new_value into a smoothed value, one factor per smoothing speed.

Frame report#

(variance: float64, value: float64) -> float64

How uneven a value is over time, from 0 (steady) to 1 (jumpy), as its spread relative to its mean.

() -> *Report

Builds a report of the frames recorded by update: one row per zone, in the current Report_Mode. The report lives in temporary storage, so print or save it before the next reset_temporary_storage. Rows of zones that were never called, or never initted in manual mode, are left out when printed.

#import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = true, USE_GRAPHICS = false, DO_HISTORY = false);

main :: () {
    Prof_init_highlevel();
    zones[0].name = "work";
    zones[0].initted = true;
    zone_begin(0);
    zone_end();
    update(true);

    report := create_report();
    assert(report.records.count == 1);
    assert(report.records[0].name == "work");
}

Doesn't run in the browser (skipped on wasm).

(num: s64)

Moves the highlighted row by num rows. The cursor is clamped when the report is built.

(num: s64)

Shows the frame num frames back in the history, clamped to the history's length.

(delta: s64)

Steps the shown frame by delta: positive goes forward in time, negative back.

()

Opens the call graph of the zone under the cursor.

()

Moves the selection to the parent of the selected zone in the call graph.

Automatic zones#

(index: s64)
(index: s64)

Opens a zone for the rest of the enclosing procedure. The plugin inserts it; a client program uses it to time a procedure without writing zone_end.

(index: s64)

Opens zone index and closes it when the procedure returns, unless a profiler call is already running.

Instrumentation#

(p: *Metaprogram_Plugin, options: [] string) -> bool

Reads the plugin options (-csv, -min_size, -modules) and prepares the plugin.

(w: Workspace, iprof_hook_name: string, iprof_hook_args: string, user_hook_name: string, user_hook_args: string)

Adds a call to the user's hook next to Iprof's own at a point in the program's life.

Graphics#

(x: float, y: float, full_width: float, requested_height: float, config: *Config)

Draws the report as an overlay with its top left corner at x, y, using the callbacks in config. Call it every frame after update.

#import "Math";
#import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = true, DO_HISTORY = true);

config: Config;
config.draw_text = (x: float, y: float, text: string, color: Vector4) { /* draw with your renderer */ };
config.text_width = (text: string) -> float { return 8.0 * text.count; };
config.draw_rectangle = (x0: float, y0: float, x1: float, y1: float, color: Vector4) { /* ... */ };

update(true);
draw(10, 10, 400, 300, *config);
↗

This one is only checked to compile, not run.

(sx: float, sy: float, requested_width: float, requested_height: float, config: *Config)

Draws the history graph of the selected zone inside the given rectangle.

Types#

:: enum

How the module is imported. METAPROGRAM makes it a plugin that instruments your code; CLIENT pulls the profiling runtime into the program.

METAPROGRAM
CLIENT
:: enum

What a report shows for each zone: time spent in the zone itself, time including its callees, call counts, or the call graph.

SELF_TIME
HIERARCHICAL_TIME
CALL_COUNT
CALL_GRAPH
:: struct

The table create_report builds: a title, column headers and one Report_Row per zone.

title: string
title_right: string
header: [NUM_HEADER] string
hilit_header: s32
records: [..] Report_Row
hilight: s64
:: struct

One zone's line in a report: its name, self and hierarchical time in seconds, and its call count.

indent: s64
name: string
number: s64
prefix: u8
value_flag: s64
self_time: float64
hier_time: float64
calls: float64
array_aliased_values: [3] float64
heat: float64
zone: *Zone
:: struct

A named piece of code to time. In manual mode, fill name and set initted in a slot of zones.

name: string
hash: u64
filename: string
full_path: string
line: s64
highlevel: *Report_Row
visited: := false
initted: := false
:: struct

The vertical scale the overlay's graphs are drawn at, and how fast it eases toward a new target.

target: s64
current: float
ease: float
output_factor: float64
:: struct

Where a graph sits on screen: origin and size.

x0: float
y0: float
sx: float
sy: float
:: struct

How the overlay draws: the callbacks that draw text, rectangles and lines, the sizes of the font, and the colours. Fill in draw_text, text_width and draw_rectangle at least; the others have do-nothing defaults.

line_spacing: s32
font_character_height: s32
draw_text: (x: float, y: float, str: string, color: Vector4)
text_width: (s: string) -> float
draw_rectangle: (x0: float, y0: float, x1: float, y1: float, color: Vector4)
draw_line: (p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, color: Vector4)
graph_begin: (x: float, y: float, width: float, height: float)
graph_end: (x: float, y: float, width: float, height: float)
limit_name_column_character_length: := false
zone_time_precision_digits: s32
title_background_color: := Vector4.{ .05, .05, .05, .97 }
title_text_color: := Vector4.{ .7, .7, .7, 1 }
zone_text_color_cold: := Vector4.{ .8, .15, .15, 1 }
zone_text_color_hot: := Vector4.{ 1, .9, .9, 1 }
header_color_unhilit: := Vector4.{ .6, .6, .6, 1 }
header_color_hilit: := Vector4.{ 1, 1, .7, 1 }
bar_color_a: := Vector4.{ .1, 0, .03, .93 }
bar_color_b: := Vector4.{ .14, .05, .06, .93 }
selected_bar_whiten_amount: := 0.35
graph_zone_under_cursor_text_color: := Vector4.{ .8, 1, 1, 1 }
graph_zone_under_cursor_background_color: := Vector4.{ 0, 0, .1, .9 }
graph_current_time_marker_color: := Vector4.{ 1, 1, .9, 1 }
graph_current_time_history_color: := Vector4.{ 1, .3, .3, .75 }
graph_time_cursor_color: := Vector4.{ 1, 1, .9, .5 }
selected_graph_color: := Vector4.{ 1, 1, 1, 1 }
text_y_offset: s32
graph_colors: [] Vector4
user_data: *void

Constants#

:: #string PROFILE_CLIENT_SETUP #scope_file Independent_Profile_Client :: #import "Iprof"(IMPORT_MODE = .CLIENT, MANUAL_MODE = false, USE_GRAPHICS = false, DO_HIST ...
:: "Iprof: -csv <filename>, -min_size <integer> (default 30), -modules\n"