mirror of
https://github.com/netdata/netdata.git
synced 2025-04-06 14:35:32 +00:00
parent
5d960b419e
commit
9cbf6b7ae9
3 changed files with 379 additions and 8 deletions
collectors
health/schema.d
371
collectors/network-viewer.plugin/viewer.html
Normal file
371
collectors/network-viewer.plugin/viewer.html
Normal file
|
@ -0,0 +1,371 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Network Viewer</title>
|
||||
<style>
|
||||
/* Styles to make the canvas full width and height */
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
#d3-canvas {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400&display=swap" rel="stylesheet">
|
||||
<!-- Include D3.js -->
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<script>
|
||||
|
||||
// The transformData function
|
||||
function transformData(dataPayload, desiredColumnNames, columns) {
|
||||
console.log(dataPayload);
|
||||
|
||||
const transformedData = [];
|
||||
|
||||
// Create a map to store data per application
|
||||
const appMap = new Map();
|
||||
|
||||
dataPayload.data.forEach(row => {
|
||||
const rowData = {};
|
||||
desiredColumnNames.forEach(columnName => {
|
||||
const columnIndex = columns[columnName].index;
|
||||
rowData[columnName] = row[columnIndex];
|
||||
});
|
||||
|
||||
const appName = rowData['Process'];
|
||||
if (!appMap.has(appName)) {
|
||||
appMap.set(appName, {
|
||||
listenCount: 0,
|
||||
inboundCount: 0,
|
||||
outboundCount: 0,
|
||||
localCount: 0,
|
||||
privateCount: 0,
|
||||
publicCount: 0,
|
||||
totalCount: 0
|
||||
});
|
||||
}
|
||||
|
||||
const appData = appMap.get(appName);
|
||||
appData.totalCount++;
|
||||
|
||||
if (rowData['Direction'] === 'listen') {
|
||||
appData.listenCount++;
|
||||
}
|
||||
else if (rowData['Direction'] === 'local') {
|
||||
appData.localCount++;
|
||||
}
|
||||
else if (rowData['Direction'] === 'inbound') {
|
||||
appData.inboundCount++;
|
||||
}
|
||||
else if (rowData['Direction'] === 'outbound') {
|
||||
appData.outboundCount++;
|
||||
}
|
||||
|
||||
if (rowData['RemoteAddressSpace'] === 'public') {
|
||||
appData.publicCount++;
|
||||
}
|
||||
else if (rowData['RemoteAddressSpace'] === 'private') {
|
||||
appData.privateCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// Convert the map to an array format
|
||||
for (let [appName, appData] of appMap) {
|
||||
transformedData.push({
|
||||
name: appName,
|
||||
...appData
|
||||
});
|
||||
}
|
||||
|
||||
console.log(transformedData);
|
||||
|
||||
return transformedData;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="d3-canvas"></div> <!-- Div for D3 rendering -->
|
||||
|
||||
<script>
|
||||
// Function to draw the circles and labels for each application with border forces
|
||||
function drawApplications(data) {
|
||||
console.log(data);
|
||||
console.log(data.length)
|
||||
const maxTotalCount = d3.max(data, d => d.totalCount);
|
||||
const maxXCount = d3.max(data, d => Math.abs(Math.max(d.publicCount, d.privateCount)));
|
||||
const maxStrength = 0.005;
|
||||
const borderPadding = 40;
|
||||
console.log("maxTotalCount", maxTotalCount);
|
||||
console.log("maxXCount", maxXCount);
|
||||
|
||||
const max = {
|
||||
totalCount: d3.max(data, d => d.totalCount),
|
||||
localCount: d3.max(data, d => d.localCount),
|
||||
listenCount: d3.max(data, d => d.listenCount),
|
||||
privateCount: d3.max(data, d => d.privateCount),
|
||||
publicCount: d3.max(data, d => d.publicCount),
|
||||
inboundCount: d3.max(data, d => d.inboundCount),
|
||||
outboundCount: d3.max(data, d => d.outboundCount),
|
||||
}
|
||||
|
||||
const w = window.innerWidth;
|
||||
const h = window.innerHeight;
|
||||
const cw = w / 2;
|
||||
const ch = h / 2;
|
||||
console.log(w, h, cw, ch);
|
||||
console.log(w, h, cw, ch);
|
||||
|
||||
const minSize = 13;
|
||||
const maxSize = Math.max(5, Math.min(w, h) / data.length) + minSize; // Avoid division by zero or too large sizes
|
||||
|
||||
const publicColor = "#bbaa00";
|
||||
const privateColor = "#5555ff";
|
||||
const serverColor = "#009900";
|
||||
const clientColor = "#990000";
|
||||
|
||||
function hexToHalfOpacityRGBA(hex) {
|
||||
// Ensure the hex color is valid
|
||||
if (hex.length !== 7 || hex[0] !== '#') {
|
||||
throw new Error('Invalid hex color format');
|
||||
}
|
||||
|
||||
// Extract the red, green, and blue components
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
|
||||
// Return the color in RGBA format with half opacity
|
||||
return `rgba(${r}, ${g}, ${b}, 0.5)`;
|
||||
}
|
||||
|
||||
const pieColors = d3.scaleOrdinal()
|
||||
.domain(["publicCount", "privateCount", "listenInboundCount", "outboundCount", "others"])
|
||||
.range([publicColor, privateColor, serverColor, clientColor, "#666666"]); // Example colors
|
||||
|
||||
const pie = d3.pie().value(d => d.value);
|
||||
const arc = d3.arc();
|
||||
|
||||
function getPieData(d) {
|
||||
const others = d.totalCount - (d.publicCount + d.privateCount + d.listenCount + d.inboundCount + d.outboundCount);
|
||||
return [
|
||||
{value: d.publicCount},
|
||||
{value: d.privateCount},
|
||||
{value: d.listenCount + d.inboundCount},
|
||||
{value: d.outboundCount},
|
||||
{value: others > 0 ? others : 0}
|
||||
];
|
||||
}
|
||||
|
||||
const forceStrengthScale = d3.scaleLinear()
|
||||
.domain([0, maxTotalCount])
|
||||
.range([0, maxStrength]);
|
||||
|
||||
const circleSize = d3.scaleLog()
|
||||
.domain([1, maxTotalCount]) // Assuming maxTotalCount is the maximum value in your data
|
||||
.range([minSize, maxSize])
|
||||
.clamp(true); // Clamps the output so that it stays within the range
|
||||
|
||||
const logScaleRight = d3.scaleLog().domain([1, max.publicCount + 1]).range([0, cw - borderPadding]);
|
||||
const logScaleLeft = d3.scaleLog().domain([1, max.privateCount + 1]).range([0, cw - borderPadding]);
|
||||
const logScaleTop = d3.scaleLog().domain([1, max.outboundCount + 1]).range([0, ch - borderPadding]);
|
||||
const logScaleBottom = d3.scaleLog().domain([1, (max.listenCount + max.inboundCount) / 2 + 1]).range([0, ch - borderPadding]);
|
||||
|
||||
data.forEach((d, i) => {
|
||||
const forces = {
|
||||
total: d.totalCount / max.totalCount,
|
||||
local: d.localCount / max.localCount,
|
||||
listen: d.listenCount / max.listenCount,
|
||||
private: d.privateCount / max.privateCount,
|
||||
public: d.publicCount / max.publicCount,
|
||||
inbound: d.inboundCount / max.inboundCount,
|
||||
outbound: d.outboundCount / max.outboundCount,
|
||||
}
|
||||
|
||||
const pos = {
|
||||
right: logScaleRight(d.publicCount + 1),
|
||||
left: logScaleLeft(d.privateCount + 1),
|
||||
top: logScaleTop(d.outboundCount + 1),
|
||||
bottom: logScaleBottom((d.listenCount + d.inboundCount) / 2 + 1),
|
||||
};
|
||||
|
||||
d.targetX = cw + pos.right - pos.left;
|
||||
d.targetY = ch + pos.bottom - pos.top;
|
||||
|
||||
if(d.name === 'deluged')
|
||||
console.log("object", d, "forces", forces, "pos", pos);
|
||||
});
|
||||
|
||||
|
||||
console.log(data);
|
||||
|
||||
const svg = d3.select('#d3-canvas').append('svg')
|
||||
.attr('width', '100%')
|
||||
.attr('height', '100%');
|
||||
|
||||
// Top area - Clients
|
||||
svg.append('rect')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('width', '100%')
|
||||
.attr('height', borderPadding / 2) // Adjust height as needed
|
||||
.style('fill', hexToHalfOpacityRGBA(clientColor));
|
||||
|
||||
svg.append('text')
|
||||
.text('Clients')
|
||||
.attr('x', '50%')
|
||||
.attr('y', borderPadding / 2 - 4) // Adjust y position based on the rectangle's height
|
||||
.attr('text-anchor', 'middle')
|
||||
.style('font-family', 'IBM Plex Sans')
|
||||
.style('font-size', '14px')
|
||||
.style('font-weight', 'bold'); // Make the font bold
|
||||
|
||||
// Bottom area - Servers
|
||||
svg.append('rect')
|
||||
.attr('x', 0)
|
||||
.attr('y', h - borderPadding / 2)
|
||||
.attr('width', '100%')
|
||||
.attr('height', borderPadding / 2) // Adjust height as needed
|
||||
.style('fill', hexToHalfOpacityRGBA(serverColor));
|
||||
|
||||
svg.append('text')
|
||||
.text('Servers')
|
||||
.attr('x', '50%')
|
||||
.attr('y', h - borderPadding / 2 + 16) // Adjust y position based on the rectangle's height
|
||||
.attr('text-anchor', 'middle')
|
||||
.style('font-family', 'IBM Plex Sans')
|
||||
.style('font-size', '14px')
|
||||
.style('font-weight', 'bold'); // Make the font bold
|
||||
|
||||
svg.append('rect')
|
||||
.attr('x', w - borderPadding / 2) // Position it close to the right edge
|
||||
.attr('y', 0)
|
||||
.attr('width', borderPadding / 2) // Width of the border area
|
||||
.attr('height', '100%')
|
||||
.style('fill', hexToHalfOpacityRGBA(publicColor));
|
||||
|
||||
svg.append('text')
|
||||
.text('Public')
|
||||
.attr('x', w - (borderPadding / 2)) // Position close to the right edge
|
||||
.attr('y', ch - 10) // Vertically centered
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle') // Center alignment of the text
|
||||
.attr('transform', `rotate(90, ${w - (borderPadding / 2)}, ${ch})`) // Rotate around the text's center
|
||||
.style('font-family', 'IBM Plex Sans')
|
||||
.style('font-size', '14px')
|
||||
.style('font-weight', 'bold'); // Make the font bold
|
||||
|
||||
svg.append('rect')
|
||||
.attr('x', 0) // Positioned at the left edge
|
||||
.attr('y', 0)
|
||||
.attr('width', borderPadding / 2) // Width of the border area
|
||||
.attr('height', '100%')
|
||||
.style('fill', hexToHalfOpacityRGBA(privateColor));
|
||||
|
||||
svg.append('text')
|
||||
.text('Private')
|
||||
.attr('x', borderPadding / 2) // Position close to the left edge
|
||||
.attr('y', ch) // Vertically centered
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle') // Center alignment of the text
|
||||
.attr('transform', `rotate(-90, ${borderPadding / 2 - 10}, ${ch})`) // Rotate around the text's center
|
||||
.style('font-family', 'IBM Plex Sans')
|
||||
.style('font-size', '14px')
|
||||
.style('font-weight', 'bold'); // Make the font bold
|
||||
|
||||
function boundaryForce(alpha) {
|
||||
return function(d) {
|
||||
const nodeRadius = circleSize(d.totalCount) / 2;
|
||||
d.x = Math.max(borderPadding + nodeRadius, Math.min(w - borderPadding - nodeRadius, d.x));
|
||||
d.y = Math.max(borderPadding + nodeRadius, Math.min(h - borderPadding - nodeRadius, d.y));
|
||||
};
|
||||
}
|
||||
|
||||
const simulation = d3.forceSimulation(data)
|
||||
.force('center', d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2).strength(1)) // Scale center force strength
|
||||
.force("x", d3.forceX(d => d.targetX).strength(0.3))
|
||||
.force("y", d3.forceY(d => d.targetY).strength(0.3))
|
||||
.force("charge", d3.forceManyBody().strength(-0.5))
|
||||
.force("collide", d3.forceCollide(d => circleSize(d.totalCount) * 1.1 + 15).strength(1))
|
||||
.force("boundary", boundaryForce(0.5))
|
||||
.on('tick', ticked);
|
||||
|
||||
const app = svg.selectAll('.app')
|
||||
.data(data)
|
||||
.enter().append('g')
|
||||
.attr('class', 'app')
|
||||
.call(d3.drag()
|
||||
.on('start', dragstarted)
|
||||
.on('drag', dragged)
|
||||
.on('end', dragended));
|
||||
|
||||
app.each(function(d) {
|
||||
const group = d3.select(this);
|
||||
const pieData = pie(getPieData(d));
|
||||
const radius = circleSize(d.totalCount);
|
||||
|
||||
group.selectAll('path')
|
||||
.data(pieData)
|
||||
.enter().append('path')
|
||||
.attr('d', arc.innerRadius(0).outerRadius(radius))
|
||||
.attr('fill', (d, i) => pieColors(i));
|
||||
});
|
||||
|
||||
app.append('text')
|
||||
.text(d => d.name)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('y', d => circleSize(d.totalCount) + 10)
|
||||
.style('font-family', 'IBM Plex Sans') // Set the font family
|
||||
.style('font-size', '12px') // Set the font size
|
||||
.style('font-weight', 'bold'); // Make the font bold
|
||||
|
||||
// Initialize app positions at the center
|
||||
app.attr('transform', `translate(${window.innerWidth / 2}, ${window.innerHeight / 2})`);
|
||||
|
||||
function ticked() {
|
||||
app.attr('transform', d => `translate(${d.x}, ${d.y})`);
|
||||
}
|
||||
|
||||
function dragstarted(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(1).restart();
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
}
|
||||
|
||||
function dragged(event, d) {
|
||||
d.fx = event.x;
|
||||
d.fy = event.y;
|
||||
}
|
||||
|
||||
function dragended(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(0);
|
||||
d.fx = null;
|
||||
d.fy = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Modify your fetchData function to call drawApplications after data transformation
|
||||
function fetchData() {
|
||||
fetch('http://localhost:19999/api/v1/function?function=network-viewer')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Your existing code
|
||||
const desiredColumns = ["Direction", "Protocol", "Namespace", "Process", "CommandLine", "LocalIP", "LocalPort", "RemoteIP", "RemotePort", "LocalAddressSpace", "RemoteAddressSpace"];
|
||||
const transformed = transformData(data, desiredColumns, data.columns);
|
||||
|
||||
// Now draw the applications with border forces
|
||||
drawApplications(transformed);
|
||||
})
|
||||
.catch(error => console.error('Error fetching data:', error));
|
||||
}
|
||||
|
||||
// Load data on start
|
||||
window.onload = fetchData;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -170,7 +170,7 @@ typedef struct local_socket {
|
|||
static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) __attribute__ ((format(__printf__, 2, 3)));
|
||||
static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) {
|
||||
if(++ls->stats.errors_encountered == ls->config.max_errors) {
|
||||
nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-LISTENERS: max number of logs reached. Not logging anymore");
|
||||
nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-SOCKETS: max number of logs reached. Not logging anymore");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) {
|
|||
vsnprintf(buf, sizeof(buf), format, args);
|
||||
va_end(args);
|
||||
|
||||
nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-LISTENERS: %s", buf);
|
||||
nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-SOCKETS: %s", buf);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
|
|
@ -452,9 +452,9 @@
|
|||
"ui:classNames": "dyncfg-grid-col-span-5-2"
|
||||
},
|
||||
"value": {
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-6",
|
||||
"ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
|
||||
"database_lookup": {
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-6",
|
||||
"ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
|
||||
"after": {
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-1"
|
||||
},
|
||||
|
@ -479,7 +479,7 @@
|
|||
}
|
||||
},
|
||||
"conditions": {
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-6",
|
||||
"ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
|
||||
"warning_condition": {
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-2"
|
||||
},
|
||||
|
@ -494,7 +494,7 @@
|
|||
}
|
||||
},
|
||||
"action": {
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-6",
|
||||
"ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
|
||||
"execute": {
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-3"
|
||||
},
|
||||
|
@ -507,7 +507,7 @@
|
|||
"delay": {
|
||||
"ui:Collapsible": true,
|
||||
"ui:InitiallyExpanded": false,
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-6",
|
||||
"ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
|
||||
"up": {
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-2"
|
||||
},
|
||||
|
@ -524,7 +524,7 @@
|
|||
"repeat": {
|
||||
"ui:Collapsible": true,
|
||||
"ui:InitiallyExpanded": false,
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-6",
|
||||
"ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
|
||||
"enabled": {
|
||||
"ui:classNames": "dyncfg-grid-col-span-1-2"
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue