mirror of
https://github.com/logos-messaging/lab.waku.org.git
synced 2026-01-02 13:53:09 +00:00
feat: add charts to dogfooding app (#141)
This commit is contained in:
parent
f7196a4bf0
commit
2636da4ba0
@ -51,9 +51,69 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="charts">
|
||||
<h2>Discovery Peers Over Time</h2>
|
||||
<canvas id="discoveryChart"></canvas>
|
||||
<div id="discoverySummaryTop" class="latency-summary"></div>
|
||||
<div class="collapsible-header" id="toggleDiscoveryTable">
|
||||
<h3>Show Discovery Table</h3>
|
||||
<button id="toggleDiscoveryTableBtn" class="btn">Show</button>
|
||||
</div>
|
||||
<div id="discoveryTableContainer" class="collapsible hidden">
|
||||
<table id="discoveryTable">
|
||||
<thead>
|
||||
<tr><th>Time</th><th>Type</th><th>Total Peers</th></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section class="charts">
|
||||
<h2>Connected Peers Over Time</h2>
|
||||
<canvas id="connectionsChart"></canvas>
|
||||
<div id="ttfcSummary" class="latency-summary"></div>
|
||||
<div class="collapsible-header" id="toggleConnectionsTable">
|
||||
<h3>Show Connections Table</h3>
|
||||
<button id="toggleConnectionsTableBtn" class="btn">Show</button>
|
||||
</div>
|
||||
<div id="connectionsTableContainer" class="collapsible hidden">
|
||||
<table id="connectionsTable">
|
||||
<thead>
|
||||
<tr><th>Time</th><th>Connected Peers</th></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="charts">
|
||||
<h2>Message Delivery Latency</h2>
|
||||
<canvas id="latencyChart"></canvas>
|
||||
<div id="latencySummaryTop" class="latency-summary"></div>
|
||||
<div class="collapsible-header" id="toggleLatencyTable">
|
||||
<h3>Latency Table</h3>
|
||||
<button id="toggleLatencyTableBtn" class="btn">Show</button>
|
||||
</div>
|
||||
<div id="latencyTableContainer" class="collapsible hidden">
|
||||
<table id="latencyTable">
|
||||
<thead>
|
||||
<tr><th>Message ID</th><th>Sent</th><th>Received</th><th>Latency (ms)</th></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="message-display">
|
||||
<h2>Message Log</h2>
|
||||
<div id="messageList" class="message-list">
|
||||
<div class="collapsible-header" id="toggleLog">
|
||||
<h2>Message Log</h2>
|
||||
<button id="toggleLogBtn" class="btn">Show</button>
|
||||
</div>
|
||||
<div id="messageLogContainer" class="collapsible hidden">
|
||||
<div id="messageList" class="message-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
48
examples/dogfooding/package-lock.json
generated
48
examples/dogfooding/package-lock.json
generated
@ -11,6 +11,8 @@
|
||||
"@libp2p/crypto": "^5.0.5",
|
||||
"@multiformats/multiaddr": "^12.3.1",
|
||||
"@waku/sdk": "0.0.34",
|
||||
"chart.js": "^4.4.1",
|
||||
"chartjs-plugin-zoom": "^2.0.1",
|
||||
"libp2p": "^2.1.10",
|
||||
"protobufjs": "^7.3.0",
|
||||
"uint8arrays": "^5.1.0"
|
||||
@ -331,6 +333,12 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@leichtgewicht/ip-codec": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
|
||||
@ -995,6 +1003,12 @@
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hammerjs": {
|
||||
"version": "2.0.46",
|
||||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz",
|
||||
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/html-minifier-terser": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
|
||||
@ -2749,6 +2763,31 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
|
||||
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-plugin-zoom": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.2.0.tgz",
|
||||
"integrity": "sha512-in6kcdiTlP6npIVLMd4zXZ08PDUXC52gZ4FAy5oyjk1zX3gKarXMAof7B9eFiisf9WOC3bh2saHg+J5WtLXZeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hammerjs": "^2.0.45",
|
||||
"hammerjs": "^2.0.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"chart.js": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/check-error": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
|
||||
@ -4822,6 +4861,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/hammerjs": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
|
||||
"integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/handle-thing": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
"@libp2p/crypto": "^5.0.5",
|
||||
"@multiformats/multiaddr": "^12.3.1",
|
||||
"@waku/sdk": "0.0.34",
|
||||
"chart.js": "^4.4.1",
|
||||
"chartjs-plugin-zoom": "^2.0.1",
|
||||
"libp2p": "^2.1.10",
|
||||
"protobufjs": "^7.3.0",
|
||||
"uint8arrays": "^5.1.0"
|
||||
|
||||
@ -115,6 +115,43 @@ h2 {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.collapsible-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.collapsible.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
text-align: left;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.latency-summary {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
/* Scrollable tables */
|
||||
#discoveryTableContainer,
|
||||
#connectionsTableContainer,
|
||||
#latencyTableContainer {
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
@ -20,6 +20,13 @@ import {
|
||||
addMessageToLog,
|
||||
renderMessages,
|
||||
getSearchTerm,
|
||||
initCharts,
|
||||
onDiscoveryUpdate,
|
||||
onConnectionsUpdate,
|
||||
wireUiToggles,
|
||||
trackMessageSent,
|
||||
trackMessageReceived,
|
||||
recordLatency,
|
||||
} from "./ui-manager";
|
||||
|
||||
const NUM_MESSAGES_PER_BATCH = 5;
|
||||
@ -66,6 +73,7 @@ async function initializeApp() {
|
||||
console.log(`Message ${i + 1} (ID: ${chatMessage.id}) sent successfully.`);
|
||||
incrementSentByMe();
|
||||
addMessageToLog(chatMessage, 'sent');
|
||||
trackMessageSent(chatMessage.id, chatMessage.timestamp);
|
||||
} else {
|
||||
console.warn(`Failed to send message ${i + 1} (ID: ${chatMessage.id}):`, result.failures);
|
||||
const failureReason = result.failures.length > 0
|
||||
@ -133,6 +141,7 @@ async function initializeApp() {
|
||||
console.log(`Continuous message (ID: ${chatMessage.id}) sent successfully.`);
|
||||
incrementSentByMe();
|
||||
addMessageToLog(chatMessage, 'sent');
|
||||
trackMessageSent(chatMessage.id, chatMessage.timestamp);
|
||||
} else {
|
||||
console.warn(`Failed to send continuous message (ID: ${chatMessage.id}):`, result.failures);
|
||||
}
|
||||
@ -184,6 +193,12 @@ async function initializeApp() {
|
||||
addMessageToLog(chatMessage, 'received-other');
|
||||
console.log("Received message from other peer:", chatMessage.id);
|
||||
}
|
||||
// Use encoded timestamp when available for more accurate latency
|
||||
if (chatMessage.timestamp) {
|
||||
recordLatency(chatMessage.id, chatMessage.timestamp, Date.now());
|
||||
} else {
|
||||
trackMessageReceived(chatMessage.id, Date.now());
|
||||
}
|
||||
} else {
|
||||
console.warn("Could not decode received Waku message. Payload might be malformed or not a ChatMessage.");
|
||||
}
|
||||
@ -226,6 +241,10 @@ async function initializeApp() {
|
||||
});
|
||||
}
|
||||
|
||||
initCharts();
|
||||
(window as any).onDiscoveryUpdate = onDiscoveryUpdate;
|
||||
(window as any).onConnectionsUpdate = onConnectionsUpdate;
|
||||
wireUiToggles();
|
||||
await subscribeToMessages();
|
||||
|
||||
console.log("Application setup complete. Click 'Send New Message Batch' to send messages.");
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { ChatMessage } from "./message-service";
|
||||
import { getNodeCreationTime } from "./waku-service";
|
||||
import Chart from "chart.js/auto";
|
||||
// @ts-ignore - plugin has no types in our env
|
||||
import zoomPlugin from "chartjs-plugin-zoom";
|
||||
Chart.register(zoomPlugin as any);
|
||||
|
||||
const sentByMeCountEl = document.getElementById("sentByMeCount") as HTMLSpanElement;
|
||||
const receivedMineCountEl = document.getElementById("receivedMineCount") as HTMLSpanElement;
|
||||
@ -13,9 +18,24 @@ let receivedMine = 0;
|
||||
let receivedOthers = 0;
|
||||
let failedToSend = 0;
|
||||
|
||||
let currentMessages: ChatMessage[] = [];
|
||||
const currentMessages: ChatMessage[] = [];
|
||||
let currentPeerId: string | undefined;
|
||||
|
||||
const discoveryChartEl = document.getElementById("discoveryChart") as HTMLCanvasElement | null;
|
||||
const connectionsChartEl = document.getElementById("connectionsChart") as HTMLCanvasElement | null;
|
||||
const latencyChartEl = document.getElementById("latencyChart") as HTMLCanvasElement | null;
|
||||
|
||||
let discoveryChart: Chart | null = null;
|
||||
let connectionsChart: Chart | null = null;
|
||||
let latencyChart: Chart | null = null;
|
||||
|
||||
const discoveryTableBody = document.querySelector("#discoveryTable tbody") as HTMLTableSectionElement | null;
|
||||
const connectionsTableBody = document.querySelector("#connectionsTable tbody") as HTMLTableSectionElement | null;
|
||||
const latencyTableBody = document.querySelector("#latencyTable tbody") as HTMLTableSectionElement | null;
|
||||
const latencySummaryTopEl = document.getElementById("latencySummaryTop") as HTMLDivElement | null;
|
||||
const ttfcSummaryEl = document.getElementById("ttfcSummary") as HTMLDivElement | null;
|
||||
const discoverySummaryTopEl = document.getElementById("discoverySummaryTop") as HTMLDivElement | null;
|
||||
|
||||
export function updatePeerIdDisplay(peerId: string) {
|
||||
currentPeerId = peerId;
|
||||
if (peerIdDisplayEl) {
|
||||
@ -126,3 +146,179 @@ export function renderMessages(filterText?: string) {
|
||||
export function getSearchTerm(): string {
|
||||
return searchInputEl ? searchInputEl.value : "";
|
||||
}
|
||||
|
||||
function toRelTime(ms: number) {
|
||||
const start = getNodeCreationTime();
|
||||
return (ms - start) / 1000;
|
||||
}
|
||||
|
||||
export function setupCollapsibles() {
|
||||
const pairs: Array<[string, string]> = [
|
||||
["toggleLog", "messageLogContainer"],
|
||||
["toggleDiscoveryTable", "discoveryTableContainer"],
|
||||
["toggleConnectionsTable", "connectionsTableContainer"],
|
||||
["toggleLatencyTable", "latencyTableContainer"],
|
||||
];
|
||||
pairs.forEach(([headerId, containerId]) => {
|
||||
const header = document.getElementById(headerId);
|
||||
const container = document.getElementById(containerId);
|
||||
const btn = header?.querySelector("button");
|
||||
if (!header || !container || !btn) return;
|
||||
btn.addEventListener("click", () => {
|
||||
const hidden = container.classList.toggle("hidden");
|
||||
btn.textContent = hidden ? "Show" : "Hide";
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function initCharts() {
|
||||
if (discoveryChartEl) {
|
||||
discoveryChart = new Chart(discoveryChartEl, {
|
||||
type: "line",
|
||||
data: {
|
||||
datasets: [
|
||||
{ label: "bootstrap", data: [], borderColor: "#1abc9c", tension: 0.2, pointRadius: 2, borderWidth: 2 },
|
||||
{ label: "peer-exchange", data: [], borderColor: "#e67e22", tension: 0.2, pointRadius: 2, borderWidth: 2 },
|
||||
{ label: "peer-cache", data: [], borderColor: "#9b59b6", tension: 0.2, pointRadius: 2, borderWidth: 2 },
|
||||
],
|
||||
},
|
||||
options: { scales: { x: { type: 'linear', title: { display: true, text: "time (s)" } }, y: { title: { display: true, text: "peers" } } } },
|
||||
});
|
||||
}
|
||||
if (connectionsChartEl) {
|
||||
connectionsChart = new Chart(connectionsChartEl, {
|
||||
type: "line",
|
||||
data: { datasets: [{ label: "connections", data: [], borderColor: "#2980b9", tension: 0.25, pointRadius: 2, borderWidth: 2 }] },
|
||||
options: {
|
||||
scales: { x: { type: 'linear', title: { display: true, text: "time (s)" } }, y: { title: { display: true, text: "peers" } } },
|
||||
plugins: {
|
||||
zoom: {
|
||||
zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'x' },
|
||||
pan: { enabled: true, mode: 'x' }
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
if (latencyChartEl) {
|
||||
latencyChart = new Chart(latencyChartEl, {
|
||||
type: "bar",
|
||||
data: { labels: [], datasets: [{ label: "latency (ms)", data: [], backgroundColor: "#34495e" }] },
|
||||
options: { scales: { x: { type: 'category', title: { display: true, text: "message" } }, y: { title: { display: true, text: "ms" } } } },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function onDiscoveryUpdate(timeline: Array<{ time: number; type: string; total: number }>) {
|
||||
if (!discoveryChart || !discoveryTableBody) return;
|
||||
const grouped: Record<string, Array<{ x: number; y: number }>> = {};
|
||||
discoveryChart.data.datasets?.forEach(ds => { grouped[ds.label as string] = []; });
|
||||
timeline.forEach(ev => {
|
||||
if (!grouped[ev.type]) grouped[ev.type] = [];
|
||||
grouped[ev.type].push({ x: toRelTime(ev.time), y: ev.total });
|
||||
});
|
||||
discoveryChart.data.datasets?.forEach(ds => {
|
||||
const label = ds.label as string;
|
||||
// @ts-ignore
|
||||
ds.data = grouped[label] || [];
|
||||
});
|
||||
discoveryChart.update();
|
||||
|
||||
if (discoverySummaryTopEl) {
|
||||
const firsts: Record<string, number | undefined> = {
|
||||
"bootstrap": undefined,
|
||||
"peer-exchange": undefined,
|
||||
"peer-cache": undefined,
|
||||
};
|
||||
for (const ev of timeline) {
|
||||
if (firsts[ev.type] === undefined) firsts[ev.type] = ev.time;
|
||||
}
|
||||
const fmt = (t?: number) => t !== undefined ? `${toRelTime(t).toFixed(2)}s` : "-";
|
||||
discoverySummaryTopEl.textContent = `first bootstrap: ${fmt(firsts["bootstrap"])}, peer-exchange: ${fmt(firsts["peer-exchange"])}, peer-cache: ${fmt(firsts["peer-cache"])}`;
|
||||
}
|
||||
|
||||
discoveryTableBody.innerHTML = "";
|
||||
timeline.slice(-100).forEach(ev => {
|
||||
const tr = document.createElement("tr");
|
||||
const tdTime = document.createElement("td");
|
||||
tdTime.textContent = new Date(ev.time).toLocaleTimeString();
|
||||
const tdType = document.createElement("td");
|
||||
tdType.textContent = ev.type;
|
||||
const tdTotal = document.createElement("td");
|
||||
tdTotal.textContent = String(ev.total);
|
||||
tr.appendChild(tdTime); tr.appendChild(tdType); tr.appendChild(tdTotal);
|
||||
discoveryTableBody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
export function onConnectionsUpdate(timeline: Array<{ time: number; total: number }>) {
|
||||
if (!connectionsChart || !connectionsTableBody) return;
|
||||
const data = timeline.map(ev => ({ x: toRelTime(ev.time), y: ev.total }));
|
||||
// @ts-ignore
|
||||
connectionsChart.data.datasets[0].data = data;
|
||||
connectionsChart.update();
|
||||
|
||||
if (ttfcSummaryEl && timeline.length > 0) {
|
||||
const first = timeline[0];
|
||||
const seconds = toRelTime(first.time);
|
||||
ttfcSummaryEl.textContent = `time to first connection: ${seconds.toFixed(2)}s`;
|
||||
}
|
||||
|
||||
connectionsTableBody.innerHTML = "";
|
||||
timeline.slice(-100).forEach(ev => {
|
||||
const tr = document.createElement("tr");
|
||||
const tdTime = document.createElement("td");
|
||||
tdTime.textContent = new Date(ev.time).toLocaleTimeString();
|
||||
const tdTotal = document.createElement("td");
|
||||
tdTotal.textContent = String(ev.total);
|
||||
tr.appendChild(tdTime); tr.appendChild(tdTotal);
|
||||
connectionsTableBody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
const sentTimestamps = new Map<string, number>();
|
||||
|
||||
export function trackMessageSent(id: string, when: number) {
|
||||
sentTimestamps.set(id, when);
|
||||
}
|
||||
|
||||
export function trackMessageReceived(id: string, when: number) {
|
||||
const sent = sentTimestamps.get(id);
|
||||
if (sent === undefined) return;
|
||||
const latency = Math.max(0, when - sent);
|
||||
if (latencyChart && latencyTableBody) {
|
||||
// push label + numeric value for bar chart
|
||||
const label = id.slice(-6);
|
||||
latencyChart.data.labels?.push(label);
|
||||
// @ts-ignore
|
||||
latencyChart.data.datasets[0].data.push(latency);
|
||||
latencyChart.update();
|
||||
|
||||
const tr = document.createElement("tr");
|
||||
const tdId = document.createElement("td"); tdId.textContent = id;
|
||||
const tdSent = document.createElement("td"); tdSent.textContent = new Date(sent).toLocaleTimeString();
|
||||
const tdRecv = document.createElement("td"); tdRecv.textContent = new Date(when).toLocaleTimeString();
|
||||
const tdLat = document.createElement("td"); tdLat.textContent = String(latency);
|
||||
tr.appendChild(tdId); tr.appendChild(tdSent); tr.appendChild(tdRecv); tr.appendChild(tdLat);
|
||||
latencyTableBody.appendChild(tr);
|
||||
|
||||
const values: number[] = (latencyChart.data.datasets[0].data as any[]).map((v: any) => Number(v));
|
||||
if (latencySummaryTopEl) {
|
||||
const avg = values.reduce((a, b) => a + b, 0) / values.length;
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const p = (q: number) => sorted[Math.floor((q / 100) * (sorted.length - 1))] ?? 0;
|
||||
latencySummaryTopEl.textContent = `avg=${avg.toFixed(1)}ms p90=${p(90)}ms p95=${p(95)}ms p99=${p(99)}ms`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In case a message is received before we recorded its send time (tab reload, etc.)
|
||||
export function recordLatency(id: string, sent: number, received?: number) {
|
||||
const when = received ?? Date.now();
|
||||
sentTimestamps.set(id, sent);
|
||||
trackMessageReceived(id, when);
|
||||
}
|
||||
|
||||
export function wireUiToggles() {
|
||||
setupCollapsibles();
|
||||
}
|
||||
|
||||
@ -6,6 +6,35 @@ import { sha256, generateRandomNumber } from "./utils";
|
||||
export const DEFAULT_CONTENT_TOPIC = "/js-waku-examples/1/message-ratio/utf8";
|
||||
|
||||
let wakuNodeInstance: LightNode | null = null;
|
||||
let nodeCreationTime = Date.now();
|
||||
|
||||
export type DiscoveryType = "bootstrap" | "peer-exchange" | "peer-cache";
|
||||
|
||||
export interface DiscoveryEvent {
|
||||
time: number;
|
||||
type: DiscoveryType;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface ConnectionEvent {
|
||||
time: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
const discoveryCounts: Record<DiscoveryType, number> = {
|
||||
"bootstrap": 0,
|
||||
"peer-exchange": 0,
|
||||
"peer-cache": 0,
|
||||
};
|
||||
|
||||
const discoveredAt = new Map<string, number>();
|
||||
const countedByType: Record<DiscoveryType, Set<string>> = {
|
||||
"bootstrap": new Set<string>(),
|
||||
"peer-exchange": new Set<string>(),
|
||||
"peer-cache": new Set<string>(),
|
||||
};
|
||||
const connectionTimeline: ConnectionEvent[] = [];
|
||||
const discoveryTimeline: DiscoveryEvent[] = [];
|
||||
|
||||
export async function getWakuNode(): Promise<LightNode> {
|
||||
if (wakuNodeInstance) {
|
||||
@ -38,6 +67,68 @@ export async function getWakuNode(): Promise<LightNode> {
|
||||
await node.start();
|
||||
await node.waitForPeers();
|
||||
|
||||
nodeCreationTime = Date.now();
|
||||
|
||||
node.libp2p.addEventListener("peer:discovery", (evt: any) => {
|
||||
try {
|
||||
const info = evt.detail; // PeerInfo
|
||||
const peerId = info?.id?.toString?.() || info?.id || "";
|
||||
if (!peerId) return;
|
||||
if (!discoveredAt.has(peerId)) {
|
||||
discoveredAt.set(peerId, Date.now());
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
node.libp2p.addEventListener("peer:update", (evt: any) => {
|
||||
try {
|
||||
const update = evt.detail; // PeerUpdate
|
||||
const peer = update?.peer;
|
||||
const peerId = peer?.id?.toString?.() || peer?.id || "";
|
||||
if (!peerId) return;
|
||||
const peerTags = peer?.tags ?? {};
|
||||
let hasBootstrap = false;
|
||||
let hasPeerExchange = false;
|
||||
let hasPeerCache = false;
|
||||
if (peerTags instanceof Map) {
|
||||
hasBootstrap = peerTags.has("bootstrap");
|
||||
hasPeerExchange = peerTags.has("peer-exchange");
|
||||
hasPeerCache = peerTags.has("peer-cache");
|
||||
} else {
|
||||
hasBootstrap = Object.prototype.hasOwnProperty.call(peerTags, "bootstrap");
|
||||
hasPeerExchange = Object.prototype.hasOwnProperty.call(peerTags, "peer-exchange");
|
||||
hasPeerCache = Object.prototype.hasOwnProperty.call(peerTags, "peer-cache");
|
||||
}
|
||||
|
||||
const time = discoveredAt.get(peerId) ?? Date.now();
|
||||
|
||||
if (hasBootstrap && !countedByType["bootstrap"].has(peerId)) {
|
||||
countedByType["bootstrap"].add(peerId);
|
||||
discoveryCounts["bootstrap"]++;
|
||||
discoveryTimeline.push({ time, type: "bootstrap", total: discoveryCounts["bootstrap"] });
|
||||
}
|
||||
if (hasPeerExchange && !countedByType["peer-exchange"].has(peerId)) {
|
||||
countedByType["peer-exchange"].add(peerId);
|
||||
discoveryCounts["peer-exchange"]++;
|
||||
discoveryTimeline.push({ time, type: "peer-exchange", total: discoveryCounts["peer-exchange"] });
|
||||
}
|
||||
if (hasPeerCache && !countedByType["peer-cache"].has(peerId)) {
|
||||
countedByType["peer-cache"].add(peerId);
|
||||
discoveryCounts["peer-cache"]++;
|
||||
discoveryTimeline.push({ time, type: "peer-cache", total: discoveryCounts["peer-cache"] });
|
||||
}
|
||||
|
||||
(window as any).onDiscoveryUpdate?.([...discoveryTimeline]);
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
node.libp2p.addEventListener("peer:connect", () => {
|
||||
const now = Date.now();
|
||||
const total = node.libp2p.getConnections().length;
|
||||
connectionTimeline.push({ time: now, total });
|
||||
(window as any).onConnectionsUpdate?.([...connectionTimeline]);
|
||||
});
|
||||
|
||||
wakuNodeInstance = node;
|
||||
return node;
|
||||
}
|
||||
@ -57,3 +148,15 @@ export function createWakuDecoder() {
|
||||
contentTopic: DEFAULT_CONTENT_TOPIC,
|
||||
});
|
||||
}
|
||||
|
||||
export function getDiscoveryTimeline() {
|
||||
return discoveryTimeline;
|
||||
}
|
||||
|
||||
export function getConnectionTimeline() {
|
||||
return connectionTimeline;
|
||||
}
|
||||
|
||||
export function getNodeCreationTime() {
|
||||
return nodeCreationTime;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user