[Perl] DAQ system for the FAPG.
perf cache chart series by rollup bucket
- reuse generated series until the current source rollup expires - bound and prune the per-worker chart cache - advertise a matching private browser cache lifetime - expose and test chart cache hit diagnostics
Changed files
roles/dashboard/lib/FAPG/DAQ/Dashboard/Controller/Reading.pm
@@ -10,6 +10,8 @@
10
10
my $MAX_SERIES_RANGE = max_series_range();
11
11
my $SERIES_GAP_SECONDS = 30;
12
12
my $MINUTE_SECONDS = 60;
13
Added:
my $MAX_SERIES_CACHE_ENTRIES = 256;
14
Added:
my %SERIES_CACHE;
13
15
14
16
sub list ($self) {
15
17
my $probe = $self->param('probe') // '';
@@ -95,6 +97,18 @@
95
97
my $unit = ( grep { $_->{key} eq $probe } $self->probes->@* )[0]{unit};
96
98
my $rollup_seconds
97
99
= $window->{bucket_stride} >= 3_600 ? 3_600 : $MINUTE_SECONDS;
100
Added:
my $now = time;
101
Added:
my $expires_epoch
102
Added:
= int( $now / $rollup_seconds ) * $rollup_seconds + $rollup_seconds;
103
Added:
my $cache_key = join q{:}, $probe, $timeframe, $range, $smooth,
104
Added:
$window->{start_epoch}, $window->{end_epoch};
105
Added:
my $cached = $SERIES_CACHE{$cache_key};
106
Added:
107
Added:
if ( $cached && $cached->{expires_epoch} > $now ) {
108
Added:
return render_series_payload( $self, $cached->{payload}, 'HIT',
109
Added:
$cached->{expires_epoch} );
110
Added:
}
111
Added:
98
112
my $rollups = $self->sqlite->db->query(
99
113
q{
100
114
WITH ordered AS (
@@ -139,20 +153,52 @@
139
153
my $points = aggregate_series( $rollups, $window, $probe, $unit, $smooth,
140
154
$rollup_seconds );
141
155
142
Removed:
$self->render(
143
Removed:
json => {
144
Removed:
probe => $probe,
145
Removed:
timeframe => $timeframe,
146
Removed:
range => 0 + $range,
147
Removed:
smooth => 0 + $smooth,
148
Removed:
band => {
149
Removed:
lower_statistic => 'minimum',
150
Removed:
upper_statistic => 'maximum',
151
Removed:
},
152
Removed:
gap_seconds => $SERIES_GAP_SECONDS,
153
Removed:
readings => $points,
156
Added:
my $payload = {
157
Added:
probe => $probe,
158
Added:
timeframe => $timeframe,
159
Added:
range => 0 + $range,
160
Added:
smooth => 0 + $smooth,
161
Added:
band => {
162
Added:
lower_statistic => 'minimum',
163
Added:
upper_statistic => 'maximum',
154
164
},
155
Removed:
);
165
Added:
gap_seconds => $SERIES_GAP_SECONDS,
166
Added:
readings => $points,
167
Added:
};
168
Added:
169
Added:
prune_series_cache($now);
170
Added:
$SERIES_CACHE{$cache_key} = {
171
Added:
expires_epoch => $expires_epoch,
172
Added:
payload => $payload,
173
Added:
};
174
Added:
175
Added:
return render_series_payload( $self, $payload, 'MISS', $expires_epoch );
176
Added:
}
177
Added:
178
Added:
sub prune_series_cache ($now) {
179
Added:
for my $key ( keys %SERIES_CACHE ) {
180
Added:
delete $SERIES_CACHE{$key}
181
Added:
if $SERIES_CACHE{$key}{expires_epoch} <= $now;
182
Added:
}
183
Added:
184
Added:
if ( keys(%SERIES_CACHE) >= $MAX_SERIES_CACHE_ENTRIES ) {
185
Added:
my ($oldest) = sort {
186
Added:
$SERIES_CACHE{$a}{expires_epoch}
187
Added:
<=> $SERIES_CACHE{$b}{expires_epoch}
188
Added:
} keys %SERIES_CACHE;
189
Added:
delete $SERIES_CACHE{$oldest};
190
Added:
}
191
Added:
192
Added:
return;
193
Added:
}
194
Added:
195
Added:
sub render_series_payload ( $self, $payload, $cache_status, $expires_epoch ) {
196
Added:
my $max_age = int( $expires_epoch - time );
197
Added:
$max_age = 0 if $max_age < 0;
198
Added:
199
Added:
$self->res->headers->header( 'X-FAPG-Chart-Cache' => $cache_status );
200
Added:
$self->res->headers->cache_control("private, max-age=$max_age");
201
Added:
return $self->render( json => $payload );
156
202
}
157
203
158
204
sub status ($self) {
roles/dashboard/t/04-readings-series.t
@@ -28,8 +28,14 @@
28
28
29
29
$t->get_ok('/api/readings/ec/series?timeframe=day')
30
30
->status_is(200)
31
Added:
->header_is( 'X-FAPG-Chart-Cache' => 'MISS' )
31
32
->json_hasnt('/readings/48');
32
33
assert_series_reading( $t, 1050, 1000, 1100, 2 );
34
Added:
35
Added:
$t->get_ok('/api/readings/ec/series?timeframe=day')
36
Added:
->status_is(200)
37
Added:
->header_is( 'X-FAPG-Chart-Cache' => 'HIT' )
38
Added:
->header_like( 'Cache-Control' => qr{\bmax-age=\d+\b} );
33
39
34
40
$t->get_ok('/api/readings/ec/series?timeframe=month')
35
41
->status_is(200)