Selkie.git | t/ | 81-trace.rakutest


use Test;
use lib 'lib';

use Selkie::Trace;

plan 4;

sub tmp-path(Str:D $suffix --> Str) {
    $*TMPDIR.add("selkie-trace-{$*PID}-{now.Int}-$suffix").Str;
}

subtest "off mode records nothing" => {
    plan 2;
    Selkie::Trace.init(mode => 'off');
    nok Selkie::Trace.enabled, "trace disabled";
    is Selkie::Trace.mode, 'off', "mode reports off";
    Selkie::Trace.shutdown;
};

subtest "trace mode writes Chrome Trace JSON" => {
    plan 7;
    my $path = tmp-path('trace.json');
    Selkie::Trace.init(mode => 'trace', trace-path => $path);
    ok Selkie::Trace.trace-enabled, "trace mode enabled";
    ok $path.IO.e, "trace file is created at init";
    my $span = Selkie::Trace.start('test.span', cat => 'test', args => %(thing => 'value'));
    sleep 0.01;
    $span.finish(ok => True);
    Selkie::Trace.instant('test.instant', cat => 'test', args => %(n => 1));
    Selkie::Trace.shutdown;

    ok $path.IO.e, "trace file written";
    my $json = $path.IO.slurp;
    like $json, /'"traceEvents"'/, "trace file has traceEvents";
    like $json, /'test.span'/, "trace file has span event";
    unlike $json, /'"ts":null'/, "trace timestamps are numeric";
    unlike $json, /'"dur":null'/, "trace durations are numeric";
    $path.IO.unlink if $path.IO.e;
};

subtest "slow mode writes thresholded JSONL spans" => {
    plan 4;
    my $path = tmp-path('slow.jsonl');
    Selkie::Trace.init(
        mode      => 'slow',
        slow-path => $path,
        slow-ms   => 0e0,
    );
    ok Selkie::Trace.slow-enabled, "slow mode enabled";
    my $span = Selkie::Trace.start('slow.span', cat => 'test');
    sleep 0.01;
    $span.finish(answer => 42);
    Selkie::Trace.shutdown;

    ok $path.IO.e, "slow file written";
    my $jsonl = $path.IO.slurp;
    like $jsonl, /'slow.span'/, "slow file has span";
    like $jsonl, /'"dur_ms"'/, "slow file has duration";
    $path.IO.unlink if $path.IO.e;
};

subtest "writer thread: multi-thread producers all land, shutdown drains" => {
    plan 4;
    # Trace-mode events are enqueued lock-free and written by one
    # dedicated writer thread; shutdown must close the channel, JOIN
    # the writer, and only then write the JSON tail — so a file read
    # after shutdown contains every event produced from any thread,
    # and stays structurally valid JSON.
    my $path = tmp-path('writer.json');
    Selkie::Trace.init(mode => 'trace', trace-path => $path);
    my @workers = (^4).map: -> $w {
        start {
            for ^25 -> $i {
                my $span = Selkie::Trace.start("worker.span",
                    cat => 'test', args => %(worker => $w, i => $i));
                $span.finish(ok => True);
            }
        }
    };
    await @workers;
    Selkie::Trace.shutdown;

    my $json = $path.IO.slurp;
    my $count = +$json.comb('"worker.span"');
    is $count, 100, 'all 100 events from 4 threads landed';
    like $json, /'trace/shutdown'/, 'shutdown marker is present';
    like $json, /'"displayTimeUnit"' \s* ':' \s* '"ms"' '}' $/,
        'JSON tail written after the drain';
    lives-ok {
        my $span = Selkie::Trace.start('late.span', cat => 'test');
        $span.finish(ok => True) with $span;
        Selkie::Trace.instant('late.instant', cat => 'test');
    }, 'producers racing past shutdown are safely dropped';
    $path.IO.unlink if $path.IO.e;
};