ForgeOps-Tracker

 view release on metacpan or  search on metacpan

t/metric_buffer.t  view on Meta::CPAN

    ok(eval { $buffer->flush; 1 }, 'flush did not die') or diag $@;
    is($buffer->count, 1);
    $buffer->flush for 1 .. 4;
    is($buffer->count, 0);
};

subtest 'an entry recorded while delivery is in flight is never lost' => sub {
    my (@delivered, @outcomes) :shared;
    my ($in_flight, $release) :shared;
    $in_flight = 0;
    $release = 0;
    my $buffer = ForgeOps::Tracker::MetricBuffer->new(
        new_configuration(),
        sub {
            my ($entries) = @_;
            {
                lock(@delivered);
                push @delivered, JSON::PP::encode_json([ map { $_->{metric_name} } @$entries ]);
            }
            if (@delivered == 1) {
                lock($in_flight);
                $in_flight = 1;
                cond_signal($in_flight);
            }
            if (@delivered == 1) {
                lock($release);
                cond_wait($release) until $release;
            }
            return 1;
        },
        sub { 3600 },
    );

    $buffer->record({ metric_name => 'first', value => 1 });
    my $flushing = threads->create(sub { $buffer->flush });
    { lock($in_flight); cond_wait($in_flight) until $in_flight; }
    $buffer->record({ metric_name => 'during', value => 2 });
    { lock($release); $release = 1; cond_signal($release); }
    $flushing->join;
    $buffer->flush;

    is_deeply([@delivered], ['["first"]', '["during"]']);
    $buffer->_clear;
};

subtest 'is capped and drops further entries until a flush succeeds' => sub {
    my (@delivered, @outcomes) :shared;
    my $buffer = ForgeOps::Tracker::MetricBuffer->new(new_configuration(), sub { 0 }, sub { 3600 });
    my $accepted = 0;
    $accepted += $buffer->record({ metric_name => 'm', value => 1 }) for 1 .. ForgeOps::Tracker::MetricBuffer::MAX_ENTRIES + 50;
    is($accepted, ForgeOps::Tracker::MetricBuffer::MAX_ENTRIES);
    $buffer->_clear;
};

subtest 'the metric uris swap the trailing /events segment' => sub {
    my $config = new_configuration();
    is($config->custom_metrics_uri, 'https://tracker.example.com/api/v1/custom_metrics');
    is($config->infrastructure_metrics_uri, 'https://tracker.example.com/api/v1/infrastructure_metrics');
};

subtest 'capture_metric and capture_infrastructure_metric deliver to their own endpoints end to end' => sub {
    my $server = t::lib::EchoServer->start;
    ForgeOps::Tracker::_reset_for_testing();
    ForgeOps::Tracker::init(
        dsn                  => 'http://key@127.0.0.1:' . $server->{port} . '/api/v1/events',
        environment          => 'production',
        release              => 'a1b2c3d',
        enabled_environments => { production => 1 },
        server_name          => 'web-1',
    );
    ForgeOps::Tracker::capture_metric('signup');
    ForgeOps::Tracker::capture_metric('payment', 49);
    ForgeOps::Tracker::capture_infrastructure_metric('cpu', 0.42, hostname => 'db-1');
    ForgeOps::Tracker::capture_infrastructure_metric('memory', 0.7);
    ForgeOps::Tracker::flush_metrics();

    my %by_path = map { $_->{path} => $_ } @{ $server->requests };
    my $custom = $by_path{'/api/v1/custom_metrics'};
    is($custom->{headers}{AUTHORIZATION}, 'Bearer key');
    my $body = JSON::PP::decode_json($custom->{body});
    is_deeply([ map { $_->{metric_name} } @{ $body->{metrics} } ], ['signup', 'payment']);
    is($body->{metrics}[0]{value}, 1);
    is($body->{metrics}[1]{value}, 49);
    is($body->{metrics}[0]{environment}, 'production');
    is($body->{metrics}[0]{release}, 'a1b2c3d');
    my $infrastructure = JSON::PP::decode_json($by_path{'/api/v1/infrastructure_metrics'}{body})->{metrics};
    is($infrastructure->[0]{hostname}, 'db-1');
    is($infrastructure->[1]{hostname}, 'web-1');
    ForgeOps::Tracker::_reset_for_testing();
    $server->stop;
};

subtest 'capture_metric drops a batch the plan rejects but keeps one the server failed on, end to end' => sub {
    my $server = t::lib::EchoServer->start;
    my @sent_for;
    for my $code (403, 503) {
        ForgeOps::Tracker::_reset_for_testing();
        ForgeOps::Tracker::init(
            dsn                  => 'http://key@127.0.0.1:' . $server->{port} . "/status/$code/api/v1/events",
            environment          => 'production',
            enabled_environments => { production => 1 },
        );
        ForgeOps::Tracker::capture_metric('first');
        ForgeOps::Tracker::flush_metrics();
        ForgeOps::Tracker::capture_metric('second');
        ForgeOps::Tracker::flush_metrics();
    }
    # Nothing may be left for the END block to send once this server is gone.
    ForgeOps::Tracker::_reset_for_testing();

    for my $request (@{ $server->requests }) {
        my ($code) = $request->{path} =~ m{/status/(\d{3})/};
        push @{ $sent_for[$code == 403 ? 0 : 1] }, [ map { $_->{metric_name} } @{ JSON::PP::decode_json($request->{body})->{metrics} } ];
    }
    is_deeply($sent_for[0], [ ['first'], ['second'] ], 'a 403 batch is not sent again');
    is_deeply($sent_for[1], [ ['first'], ['first', 'second'] ], 'a 503 batch is kept for the next flush');
    $server->stop;
};

subtest 'is a no-op when the client is not enabled' => sub {
    ForgeOps::Tracker::_reset_for_testing();



( run in 0.782 second using v1.01-cache-2.11-cpan-16aabcbfbcb )