blob: 9135cf4a20cbb47e126737fffbe848da1a79d2de [file] [log] [blame]
// This file creates a set of two demonstration plots for testing out the
// plotting utility.
// To run this and see a demo, run:
// bazel run -c opt //aos/network/www:web_proxy_demo
// And then open localhost:8080/graph.html
//
// The plots are just the plots of the handler latency / run times of the first
// timer in the the /aos timing report message (this message was chosen because
// it is already being published by the web proxy process, so the demo requires
// very little setup).
import * as configuration from 'org_frc971/aos/configuration_generated';
import {Line, Plot} from 'org_frc971/aos/network/www/plotter';
import * as proxy from 'org_frc971/aos/network/www/proxy';
import * as web_proxy from 'org_frc971/aos/network/web_proxy_generated';
import * as reflection from 'org_frc971/aos/network/www/reflection'
import * as flatbuffers_builder from 'org_frc971/external/com_github_google_flatbuffers/ts/builder';
import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
import Channel = configuration.aos.Channel;
import Connection = proxy.Connection;
import Configuration = configuration.aos.Configuration;
import Parser = reflection.Parser;
import Table = reflection.Table;
import SubscriberRequest = web_proxy.aos.web_proxy.SubscriberRequest;
import ChannelRequest = web_proxy.aos.web_proxy.ChannelRequest;
import TransferMethod = web_proxy.aos.web_proxy.TransferMethod;
const width = 900;
const height = 400;
const helpDiv = document.createElement('div');
helpDiv.style.top = '0';
helpDiv.style.left = '0';
helpDiv.style.position = 'absolute';
document.body.appendChild(helpDiv);
helpDiv.innerHTML =
'Help: click + drag to pan, double click to reset, scroll to zoom. ' +
'Hold the x/y keys to only pan/zoom along the x/y axes.<br>' +
'X-axes of the top two plots are linked together.';
const div = document.createElement('div');
div.style.top = '40';
div.style.left = '0';
div.style.position = 'absolute';
document.body.appendChild(div);
const div2 = document.createElement('div');
div2.style.top = (parseFloat(div.style.top) + height).toString();
div2.style.left = '0';
div2.style.position = 'absolute';
document.body.appendChild(div2);
const benchmarkDiv = document.createElement('div');
benchmarkDiv.style.top = (parseFloat(div2.style.top) + height).toString();
benchmarkDiv.style.left = '0';
benchmarkDiv.style.position = 'absolute';
document.body.appendChild(benchmarkDiv);
const handlerTimePlot = new Plot(div, width, height);
const latencyPlot = new Plot(div2, width, height);
const benchmarkPlot = new Plot(benchmarkDiv, width, height);
// Class to store the lines for plotting a Statistics message.
class StatLines {
private max: Line;
private min: Line;
private average: Line;
private maxPoints: number[] = [];
private minPoints: number[] = [];
private averagePoints: number[] = [];
constructor(plotter: Plot) {
this.max = plotter.getDrawer().addLine();
this.min = plotter.getDrawer().addLine();
this.average = plotter.getDrawer().addLine();
this.max.setLabel("Max");
this.min.setLabel("Min");
this.average.setLabel("Average");
this.max.setColor([1, 0, 0]);
this.min.setColor([0, 1, 0]);
this.average.setColor([0, 0, 1]);
}
addPoints(parser: Parser, statsTable: Table, time: number) {
this.maxPoints.push(time);
this.minPoints.push(time);
this.averagePoints.push(time);
this.maxPoints.push(parser.readScalar(statsTable, "max") * 1000);
this.minPoints.push(parser.readScalar(statsTable, "min") * 1000);
this.averagePoints.push(parser.readScalar(statsTable, "average") * 1000);
// TODO: These memory allocations absolutely kill performance.
this.max.setPoints(new Float32Array(this.maxPoints));
this.min.setPoints(new Float32Array(this.minPoints));
this.average.setPoints(new Float32Array(this.averagePoints));
}
}
function setupPlot(plotter: Plot, title: string): StatLines {
plotter.getAxisLabels().setXLabel("Monotonic send time since start (sec)");
plotter.getAxisLabels().setYLabel("Time (msec)");
plotter.getAxisLabels().setTitle(title);
return new StatLines(plotter);
}
// Sets of the two x-axes to be shared; remove this to be able to zoom/pan the
// x-axes independently.
handlerTimePlot.linkXAxis(latencyPlot);
const handlerLines = setupPlot(handlerTimePlot, "Handler Run Times");
const latencyLines = setupPlot(latencyPlot, "Handler Latencies");
const conn = new Connection();
conn.connect();
const timingReport = {
name: '/aos',
type: 'aos.timing.Report',
};
let reportParser = null;
conn.addConfigHandler((config: Configuration) => {
// Locate the timing report schema so that we can read the received messages.
let reportSchema = null;
for (let ii = 0; ii < config.channelsLength(); ++ii) {
if (config.channels(ii).type() === timingReport.type) {
reportSchema = config.channels(ii).schema();
}
}
if (reportSchema === null) {
throw new Error('Couldn\'t find timing report schema in config.');
}
reportParser = new Parser(reportSchema);
// Subscribe to the timing report message.
const builder =
new flatbuffers_builder.Builder(512) as unknown as flatbuffers.Builder;
const channels: flatbuffers.Offset[] = [];
for (const channel of [timingReport]) {
const nameFb = builder.createString(channel.name);
const typeFb = builder.createString(channel.type);
Channel.startChannel(builder);
Channel.addName(builder, nameFb);
Channel.addType(builder, typeFb);
const channelFb = Channel.endChannel(builder);
ChannelRequest.startChannelRequest(builder);
ChannelRequest.addChannel(builder, channelFb);
ChannelRequest.addMethod(builder, TransferMethod.EVERYTHING_WITH_HISTORY);
channels.push(ChannelRequest.endChannelRequest(builder));
}
const channelsFb = SubscriberRequest.createChannelsToTransferVector(builder, channels);
SubscriberRequest.startSubscriberRequest(builder);
SubscriberRequest.addChannelsToTransfer(builder, channelsFb);
const connect = SubscriberRequest.endSubscriberRequest(builder);
builder.finish(connect);
conn.sendConnectMessage(builder);
});
let startTime = null;
conn.addHandler(timingReport.type, (data: Uint8Array, time: number) => {
if (startTime === null) {
startTime = time;
}
time = time - startTime;
const table = Table.getRootTable(new ByteBuffer(data));
const timer = reportParser.readVectorOfTables(table, "timers")[0];
handlerLines.addPoints(
reportParser, reportParser.readTable(timer, 'handler_time'), time);
latencyLines.addPoints(
reportParser, reportParser.readTable(timer, 'wakeup_latency'), time);
});
// Set up and draw the benchmarking plot
benchmarkPlot.getAxisLabels().setTitle(
'Benchmarkping plot (1M points per line)');
const line1 = benchmarkPlot.getDrawer().addLine();
// For demonstration purposes, make line1 only have points and line2 only have
// lines.
line1.setDrawLine(false);
line1.setLabel('LINE ONE');
const line2 = benchmarkPlot.getDrawer().addLine();
line2.setPointSize(0);
line2.setLabel('LINE TWO');
const NUM_POINTS = 1000000;
const points1 = [];
const points2 = [];
for (let ii = 0; ii < NUM_POINTS; ++ii) {
points1.push(ii);
points2.push(ii);
points1.push(Math.sin(ii * 10 / NUM_POINTS));
points2.push(Math.cos(ii * 10 / NUM_POINTS));
}
line1.setPoints(new Float32Array(points1));
line2.setPoints(new Float32Array(points2));