File Coverage

lib/PAGI/Middleware/Runtime.pm
Criterion Covered Total %
statement 36 36 100.0
branch 4 4 100.0
condition 4 4 100.0
subroutine 8 8 100.0
pod 1 1 100.0
total 53 53 100.0


line stmt bran cond sub pod time code
1             package PAGI::Middleware::Runtime;
2              
3 1     1   425 use strict;
  1         2  
  1         33  
4 1     1   3 use warnings;
  1         1  
  1         52  
5 1     1   4 use parent 'PAGI::Middleware';
  1         2  
  1         5  
6 1     1   56 use Future::AsyncAwait;
  1         2  
  1         5  
7 1     1   49 use Time::HiRes qw(time);
  1         1  
  1         5  
8              
9             =head1 NAME
10              
11             PAGI::Middleware::Runtime - Request timing middleware
12              
13             =head1 SYNOPSIS
14              
15             use PAGI::Middleware::Builder;
16              
17             my $app = builder {
18             enable 'Runtime',
19             header => 'X-Runtime',
20             precision => 6;
21             $my_app;
22             };
23              
24             =head1 DESCRIPTION
25              
26             PAGI::Middleware::Runtime measures the time taken to process a request
27             and adds it as a response header. This is useful for performance
28             monitoring and debugging.
29              
30             =head1 CONFIGURATION
31              
32             =over 4
33              
34             =item * header (default: 'X-Runtime')
35              
36             The header name to use for the runtime value.
37              
38             =item * precision (default: 6)
39              
40             Number of decimal places for the duration in seconds.
41              
42             =back
43              
44             =cut
45              
46             sub _init {
47 3     3   4 my ($self, $config) = @_;
48              
49 3   100     29 $self->{header} = $config->{header} // 'X-Runtime';
50 3   100     13 $self->{precision} = $config->{precision} // 6;
51             }
52              
53             sub wrap {
54 3     3 1 19 my ($self, $app) = @_;
55              
56 3     3   42 return async sub {
57 3         5 my ($scope, $receive, $send) = @_;
58             # Only handle HTTP requests
59 3 100       7 if ($scope->{type} ne 'http') {
60 1         2 await $app->($scope, $receive, $send);
61 1         68 return;
62             }
63              
64 2         5 my $start_time = time();
65              
66             # Intercept send to add runtime header
67 4         190 my $wrapped_send = async sub {
68 4         7 my ($event) = @_;
69 4 100       9 if ($event->{type} eq 'http.response.start') {
70 2         5 my $duration = time() - $start_time;
71 2         15 my $formatted = sprintf('%.*f', $self->{precision}, $duration);
72 2         3 push @{$event->{headers}}, [$self->{header}, $formatted];
  2         5  
73             }
74 4         9 await $send->($event);
75 2         6 };
76              
77 2         4 await $app->($scope, $receive, $wrapped_send);
78 3         13 };
79             }
80              
81             1;
82              
83             __END__