diff --git a/crates/fdev/src/main.rs b/crates/fdev/src/main.rs index 202a8e150..915e46acb 100644 --- a/crates/fdev/src/main.rs +++ b/crates/fdev/src/main.rs @@ -60,8 +60,7 @@ fn main() -> Result<(), anyhow::Error> { }, SubCommand::Test(test_config) => testing::test_framework(test_config).await, SubCommand::NetworkMetricsServer(server_config) => { - let (server, _) = - crate::network_metrics_server::start_server(&server_config).await; + let (server, _) = crate::network_metrics_server::start_server(&server_config).await; tokio::select! { _ = tokio::signal::ctrl_c() => {} _ = server => {} diff --git a/network-monitor/dist/index.html b/network-monitor/dist/index.html index 3ac30651c..1d060da73 100644 --- a/network-monitor/dist/index.html +++ b/network-monitor/dist/index.html @@ -19,7 +19,7 @@

Freenet Network Monitor

-
+
Connections history class="table is-striped block is-bordered" >
-
+
@@ -54,6 +54,7 @@

Connections history

+
diff --git a/network-monitor/src/topology.ts b/network-monitor/src/topology.ts index b9640ffec..2961ee5bf 100644 --- a/network-monitor/src/topology.ts +++ b/network-monitor/src/topology.ts @@ -1,5 +1,6 @@ import * as fbTopology from "./generated/topology"; import * as d3 from "d3"; +import { BaseType } from "d3-selection"; export let peers: PeerList = {}; @@ -73,6 +74,7 @@ export function handleChange(peerChange: fbTopology.PeerChange) { unpacked.currentState.forEach((connection) => { handleAddedConnection(connection, false); }); + ringHistogram(Object.values(peers)); } catch (e) { console.error(e); } finally { @@ -312,8 +314,8 @@ function updateTable() { closeModal.classList.add("modal-close", "is-large"); modal.appendChild(closeModal); - const containerDiv = document.querySelector(".container.main")!; - containerDiv.appendChild(modal); + const mainDiv = document.querySelector(".main")!; + mainDiv.appendChild(modal); modal.addEventListener("click", () => { modal.style.display = "none"; const graphContainer = d3.select("#peer-conns-graph"); @@ -392,6 +394,7 @@ function updateTable() { const sortDirections: number[] = []; document.addEventListener("DOMContentLoaded", () => { + ringHistogram(Object.values(peers)); document .querySelector("#peers-table-h")! .querySelectorAll("th")! @@ -510,19 +513,15 @@ function ringVisualization( e: MouseEvent, d: { value: number; legend: string } ) { - const circleRect = this.getBoundingClientRect(); - const mouseX = e.clientX - circleRect.left; // Get the mouse's x position within the circle - const mouseY = e.clientY - circleRect.top; // Get the mouse's y position within the circle tooltip .style("visibility", "visible") .style("position", "absolute") .style("stroke", "black") - .style("z-index", "2") .html(`${d.legend}: ${d.value}`); const tooltipRect = tooltip.node()!.getBoundingClientRect(); tooltip - .style("left", circleRect.left + mouseX - tooltipRect.width / 1.5 + "px") - .style("top", circleRect.top + mouseY - tooltipRect.height / 1.5 + "px"); + .style("left", e.clientX - tooltipRect.width / 2 + "px") + .style("top", e.clientY - tooltipRect.height / 2 + "px"); d3.select(this).style("stroke", "black"); } @@ -558,9 +557,6 @@ function ringVisualization( d: { value: number; legend: string } ) { const distance = getDistance(referencePoint.value, d.value).toFixed(5); - const lineRect = this.getBoundingClientRect(); - const mouseX = e.clientX - lineRect.left; // Get the mouse's x position within the line - const mouseY = e.clientY - lineRect.top; // Get the mouse's y position within the line tooltip .style("visibility", "visible") .style("position", "absolute") @@ -568,14 +564,8 @@ function ringVisualization( .html(`Distance: ${distance}`); const tooltipRect = tooltip.node()!.getBoundingClientRect(); tooltip - .style( - "left", - lineRect.left + window.scrollX + mouseX - tooltipRect.width + "px" - ) - .style( - "top", - lineRect.top + window.scrollY + mouseY - tooltipRect.height / 1.5 + "px" - ); + .style("left", e.clientX - tooltipRect.width / 2 + "px") + .style("top", e.clientY - tooltipRect.height / 2 + "px"); d3.select(this).style("stroke", "black"); } @@ -706,3 +696,124 @@ function displayHistory(peer: Peer) { // Append the new table to the peerConnections element peerDetails.appendChild(table); } + +function ringHistogram(peerLocations: Peer[]) { + const width = 500; + const height = 300; + const margin = { top: 10, right: 30, bottom: 50, left: 60 }; + const innerWidth = width - margin.left - margin.right; + + const container = d3.select("#peers-histogram"); + container.selectAll("*").remove(); + + const svg = container + .append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", `translate(${margin.left}, ${margin.top})`); + + const bucketSize = 0.05; + + const binsData: number[] = Array(Math.ceil(1 / bucketSize)).fill(0); + peerLocations.forEach((peer) => { + const binIndex = Math.floor(peer.currentLocation / bucketSize); + binsData[binIndex]++; + }); + + const histogram = binsData.map((count, i) => ({ + x0: i * bucketSize, + x1: (i + 1) * bucketSize, + count, + })); + + const xScale = d3.scaleLinear().domain([0, 1]).range([0, innerWidth]); + const legendSpace = 50; + const adjustedHeight = height - legendSpace; + const padding = 10; + const yScale = d3 + .scaleLinear() + .domain([0, d3.max(histogram, (d) => d.count)! + padding]) + .range([adjustedHeight, 0]); + + const colorScale = d3 + .scaleQuantile() + .domain(binsData) + .range(d3.schemeYlGn[9]); + + const bins = svg + .selectAll("rect") + .data(histogram) + .enter() + .append("rect") + .attr("x", (d) => xScale(d.x0!)) + .attr("y", (d) => yScale(d.count)) + .attr("width", innerWidth / histogram.length) + .attr("height", (d) => adjustedHeight - yScale(d.count)) + .attr("fill", (d) => colorScale(d.count)) + .attr("stroke", "black") + .attr("stroke-width", 1); + + const tooltip = container + .append("div") + .attr("class", "bin-tooltip") + .style("background-color", "white") + .style("border", "solid") + .style("border-width", "2px") + .style("border-radius", "5px") + .style("padding", "5px") + .style("opacity", 0) + .style("position", "absolute"); + + bins + .on("mouseover", (event: MouseEvent, d) => { + tooltip.transition().duration(200).style("opacity", 0.9); + tooltip + .html( + `Number of peers: ${d.count}, Subrange: [${d.x0.toFixed( + 2 + )}, ${d.x1.toFixed(2)})` + ) + .style("left", event.clientX + window.scrollX + "px") + .style("top", event.clientY + window.scrollY - 150 + "px"); + }) + .on("mousemove", (event, _) => { + tooltip + .style("left", event.clientX + window.scrollX + "px") + .style("top", event.clientY + window.scrollY - 150 + "px"); + }) + .on("mouseout", (_) => { + tooltip.transition().duration(500).style("opacity", 0); + }); + + const xAxisTicks = Math.floor(innerWidth / 50); // 50 is the desired space between ticks + const xAxis = d3.axisBottom(xScale).ticks(xAxisTicks); + svg + .append("g") + .attr("transform", `translate(0, ${adjustedHeight})`) + .call(xAxis); + + const yAxisTicks = Math.floor(adjustedHeight / 50); // 50 is the desired space between ticks + const yAxis = d3.axisLeft(yScale).ticks(yAxisTicks); + svg.append("g").call(yAxis); + + // Position the legend within the SVG area + svg + .append("text") + .attr("fill", "#000") + .attr("x", innerWidth / 2) + .attr("y", height - margin.bottom / 4) + .attr("text-anchor", "middle") + .attr("font-size", "14px") + .text("Peer Locations"); + + svg + .append("text") + .attr("fill", "#000") + .attr("y", margin.left - 100) + .attr("x", -adjustedHeight / 2) + .attr("transform", "rotate(-90)") + .attr("text-anchor", "middle") + .attr("font-size", "14px") + .text("Amount of Peers"); +}