Server-sent events
padSse sends the server's numbers every second as JSON; the component opens an EventSource in x-init and closes it when the stream says it is done.
padSseevery=
EventSourcex-init
Live
Server time
Load
Memory, kB
Events
Load, the last thirty seconds
Requests to PAD
Nothing yet - use the example above, and every request a component makes to PAD shows here.
How it is built
PAD, on the server Alpine, in the browser
<div x-data="live('?examples/live&stream')" x-init="open()">
<div class="toolbar">
<span class="badge" :class="state === 'open' ? 'is-good' : 'is-bad'" x-text="state === 'open' ? 'receiving' : state"></span>
<button class="btn-sm" @click="open()" x-show="state !== 'open'" x-cloak>Listen again</button>
<button class="btn-sm btn-ghost" @click="close()" x-show="state === 'open'" x-cloak>Stop</button>
</div>
<div class="grid tiles">
<div class="panel"><div class="panel-title">Server time</div><div class="tile" x-text="last.time ?? '-'"></div></div>
<div class="panel"><div class="panel-title">Load</div><div class="tile" x-text="last.load ?? '-'"></div></div>
<div class="panel"><div class="panel-title">Memory, kB</div><div class="tile" x-text="last.memory ?? '-'"></div></div>
<div class="panel"><div class="panel-title">Events</div><div class="tile" x-text="count"></div></div>
</div>
<div class="panel spark">
<div class="panel-title">Load, the last thirty seconds</div>
<svg viewBox="0 0 300 60" preserveAspectRatio="none" aria-hidden="true"><polyline :points="points()" fill="none" stroke="currentColor" stroke-width="2" vector-effect="non-scaling-stroke"></polyline></svg>
</div>
</div>
<script src="{$padRoot}alpine/examples/live.js"></script><?php
// ?examples/live&stream is the stream: padSse calls the producer every second for half a
// minute and sends what it answers as a 'stats' event - JSON - then a 'done' event; the
// page itself, asked without stream, is the board the numbers come into.
if ( padRequestHas ( 'stream' ) ) {
$round = 0;
padSse ( function ( $send ) use ( &$round ) {
$round++;
if ( $round > 30 ) {
$send ( 'done', 'done' );
return FALSE;
}
$load = function_exists ( 'sys_getloadavg' ) ? round ( sys_getloadavg () [0], 2 ) : 0;
return [ 'round' => $round, 'time' => date ( 'H:i:s' ), 'load' => $load,
'memory' => round ( memory_get_usage () / 1024 ) ];
}, [ 'every' => 1, 'for' => 35, 'event' => 'stats', 'retry' => 5000 ] );
}
?>// The board: an EventSource on the page's stream, opened in x-init. Every 'stats' event is
// JSON; 'done' says the stream is over, and the board closes the connection - without that
// EventSource would reconnect after the retry the server asked for.
document.addEventListener('alpine:init', () => {
Alpine.data('live', (url) => ({
source: null,
state: 'closed',
last: {},
count: 0,
loads: [],
open() {
this.close();
this.source = new EventSource(url);
this.state = 'connecting';
this.source.onopen = () => { this.state = 'open'; };
this.source.onerror = () => { if (this.state !== 'done') this.state = 'reconnecting'; };
this.source.addEventListener('stats', (event) => {
this.last = JSON.parse(event.data);
this.count++;
this.loads = [...this.loads, this.last.load].slice(-30);
});
this.source.addEventListener('done', () => { this.close(); this.state = 'done'; });
},
close() {
if (this.source) this.source.close();
this.source = null;
this.state = 'closed';
},
// between the lowest and the highest load of the line, so a small change shows
points() {
const low = Math.min(...this.loads), high = Math.max(...this.loads);
const span = Math.max(high - low, 0.05);
const step = this.loads.length > 1 ? 300 / (this.loads.length - 1) : 0;
return this.loads.map((load, at) => (at * step).toFixed(1) + ',' + (56 - (load - low) / span * 52).toFixed(1)).join(' ');
},
destroy() { this.close(); }
}));
});