Network viewer fixes (#16877)
* minor fixes * fix hostname
Costa Tsaousis committed
Jan 30, 2024 at 19:14 UTC
9cbf6b7ae9377db33c84930508a99362950d6898
3 files changed
+379
-8
collectors/network-viewer.plugin/viewer.html
new
+371
@@ -0,0 +1,371 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Network Viewer</title>
7
+ <style>
8
+ /* Styles to make the canvas full width and height */
9
+ body, html {
10
+ margin: 0;
11
+ padding: 0;
12
+ height: 100%;
13
+ width: 100%;
14
+ }
15
+ #d3-canvas {
16
+ height: 100%;
17
+ width: 100%;
18
+ }
19
+ </style>
20
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400&display=swap" rel="stylesheet">
21
+ <!-- Include D3.js -->
22
+ <script src="https://d3js.org/d3.v7.min.js"></script>
23
+ <script>
24
+
25
+ // The transformData function
26
+ function transformData(dataPayload, desiredColumnNames, columns) {
27
+ console.log(dataPayload);
28
+
29
+ const transformedData = [];
30
+
31
+ // Create a map to store data per application
32
+ const appMap = new Map();
33
+
34
+ dataPayload.data.forEach(row => {
35
+ const rowData = {};
36
+ desiredColumnNames.forEach(columnName => {
37
+ const columnIndex = columns[columnName].index;
38
+ rowData[columnName] = row[columnIndex];
39
+ });
40
+
41
+ const appName = rowData['Process'];
42
+ if (!appMap.has(appName)) {
43
+ appMap.set(appName, {
44
+ listenCount: 0,
45
+ inboundCount: 0,
46
+ outboundCount: 0,
47
+ localCount: 0,
48
+ privateCount: 0,
49
+ publicCount: 0,
50
+ totalCount: 0
51
+ });
52
+ }
53
+
54
+ const appData = appMap.get(appName);
55
+ appData.totalCount++;
56
+
57
+ if (rowData['Direction'] === 'listen') {
58
+ appData.listenCount++;
59
+ }
60
+ else if (rowData['Direction'] === 'local') {
61
+ appData.localCount++;
62
+ }
63
+ else if (rowData['Direction'] === 'inbound') {
64
+ appData.inboundCount++;
65
+ }
66
+ else if (rowData['Direction'] === 'outbound') {
67
+ appData.outboundCount++;
68
+ }
69
+
70
+ if (rowData['RemoteAddressSpace'] === 'public') {
71
+ appData.publicCount++;
72
+ }
73
+ else if (rowData['RemoteAddressSpace'] === 'private') {
74
+ appData.privateCount++;
75
+ }
76
+ });
77
+
78
+ // Convert the map to an array format
79
+ for (let [appName, appData] of appMap) {
80
+ transformedData.push({
81
+ name: appName,
82
+ ...appData
83
+ });
84
+ }
85
+
86
+ console.log(transformedData);
87
+
88
+ return transformedData;
89
+ }
90
+ </script>
91
+</head>
92
+<body>
93
+<div id="d3-canvas"></div> <!-- Div for D3 rendering -->
94
+
95
+<script>
96
+ // Function to draw the circles and labels for each application with border forces
97
+ function drawApplications(data) {
98
+ console.log(data);
99
+ console.log(data.length)
100
+ const maxTotalCount = d3.max(data, d => d.totalCount);
101
+ const maxXCount = d3.max(data, d => Math.abs(Math.max(d.publicCount, d.privateCount)));
102
+ const maxStrength = 0.005;
103
+ const borderPadding = 40;
104
+ console.log("maxTotalCount", maxTotalCount);
105
+ console.log("maxXCount", maxXCount);
106
+
107
+ const max = {
108
+ totalCount: d3.max(data, d => d.totalCount),
109
+ localCount: d3.max(data, d => d.localCount),
110
+ listenCount: d3.max(data, d => d.listenCount),
111
+ privateCount: d3.max(data, d => d.privateCount),
112
+ publicCount: d3.max(data, d => d.publicCount),
113
+ inboundCount: d3.max(data, d => d.inboundCount),
114
+ outboundCount: d3.max(data, d => d.outboundCount),
115
+ }
116
+
117
+ const w = window.innerWidth;
118
+ const h = window.innerHeight;
119
+ const cw = w / 2;
120
+ const ch = h / 2;
121
+ console.log(w, h, cw, ch);
122
+ console.log(w, h, cw, ch);
123
+
124
+ const minSize = 13;
125
+ const maxSize = Math.max(5, Math.min(w, h) / data.length) + minSize; // Avoid division by zero or too large sizes
126
+
127
+ const publicColor = "#bbaa00";
128
+ const privateColor = "#5555ff";
129
+ const serverColor = "#009900";
130
+ const clientColor = "#990000";
131
+
132
+ function hexToHalfOpacityRGBA(hex) {
133
+ // Ensure the hex color is valid
134
+ if (hex.length !== 7 || hex[0] !== '#') {
135
+ throw new Error('Invalid hex color format');
136
+ }
137
+
138
+ // Extract the red, green, and blue components
139
+ const r = parseInt(hex.slice(1, 3), 16);
140
+ const g = parseInt(hex.slice(3, 5), 16);
141
+ const b = parseInt(hex.slice(5, 7), 16);
142
+
143
+ // Return the color in RGBA format with half opacity
144
+ return `rgba(${r}, ${g}, ${b}, 0.5)`;
145
+ }
146
+
147
+ const pieColors = d3.scaleOrdinal()
148
+ .domain(["publicCount", "privateCount", "listenInboundCount", "outboundCount", "others"])
149
+ .range([publicColor, privateColor, serverColor, clientColor, "#666666"]); // Example colors
150
+
151
+ const pie = d3.pie().value(d => d.value);
152
+ const arc = d3.arc();
153
+
154
+ function getPieData(d) {
155
+ const others = d.totalCount - (d.publicCount + d.privateCount + d.listenCount + d.inboundCount + d.outboundCount);
156
+ return [
157
+ {value: d.publicCount},
158
+ {value: d.privateCount},
159
+ {value: d.listenCount + d.inboundCount},
160
+ {value: d.outboundCount},
161
+ {value: others > 0 ? others : 0}
162
+ ];
163
+ }
164
+
165
+ const forceStrengthScale = d3.scaleLinear()
166
+ .domain([0, maxTotalCount])
167
+ .range([0, maxStrength]);
168
+
169
+ const circleSize = d3.scaleLog()
170
+ .domain([1, maxTotalCount]) // Assuming maxTotalCount is the maximum value in your data
171
+ .range([minSize, maxSize])
172
+ .clamp(true); // Clamps the output so that it stays within the range
173
+
174
+ const logScaleRight = d3.scaleLog().domain([1, max.publicCount + 1]).range([0, cw - borderPadding]);
175
+ const logScaleLeft = d3.scaleLog().domain([1, max.privateCount + 1]).range([0, cw - borderPadding]);
176
+ const logScaleTop = d3.scaleLog().domain([1, max.outboundCount + 1]).range([0, ch - borderPadding]);
177
+ const logScaleBottom = d3.scaleLog().domain([1, (max.listenCount + max.inboundCount) / 2 + 1]).range([0, ch - borderPadding]);
178
+
179
+ data.forEach((d, i) => {
180
+ const forces = {
181
+ total: d.totalCount / max.totalCount,
182
+ local: d.localCount / max.localCount,
183
+ listen: d.listenCount / max.listenCount,
184
+ private: d.privateCount / max.privateCount,
185
+ public: d.publicCount / max.publicCount,
186
+ inbound: d.inboundCount / max.inboundCount,
187
+ outbound: d.outboundCount / max.outboundCount,
188
+ }
189
+
190
+ const pos = {
191
+ right: logScaleRight(d.publicCount + 1),
192
+ left: logScaleLeft(d.privateCount + 1),
193
+ top: logScaleTop(d.outboundCount + 1),
194
+ bottom: logScaleBottom((d.listenCount + d.inboundCount) / 2 + 1),
195
+ };
196
+
197
+ d.targetX = cw + pos.right - pos.left;
198
+ d.targetY = ch + pos.bottom - pos.top;
199
+
200
+ if(d.name === 'deluged')
201
+ console.log("object", d, "forces", forces, "pos", pos);
202
+ });
203
+
204
+
205
+ console.log(data);
206
+
207
+ const svg = d3.select('#d3-canvas').append('svg')
208
+ .attr('width', '100%')
209
+ .attr('height', '100%');
210
+
211
+ // Top area - Clients
212
+ svg.append('rect')
213
+ .attr('x', 0)
214
+ .attr('y', 0)
215
+ .attr('width', '100%')
216
+ .attr('height', borderPadding / 2) // Adjust height as needed
217
+ .style('fill', hexToHalfOpacityRGBA(clientColor));
218
+
219
+ svg.append('text')
220
+ .text('Clients')
221
+ .attr('x', '50%')
222
+ .attr('y', borderPadding / 2 - 4) // Adjust y position based on the rectangle's height
223
+ .attr('text-anchor', 'middle')
224
+ .style('font-family', 'IBM Plex Sans')
225
+ .style('font-size', '14px')
226
+ .style('font-weight', 'bold'); // Make the font bold
227
+
228
+ // Bottom area - Servers
229
+ svg.append('rect')
230
+ .attr('x', 0)
231
+ .attr('y', h - borderPadding / 2)
232
+ .attr('width', '100%')
233
+ .attr('height', borderPadding / 2) // Adjust height as needed
234
+ .style('fill', hexToHalfOpacityRGBA(serverColor));
235
+
236
+ svg.append('text')
237
+ .text('Servers')
238
+ .attr('x', '50%')
239
+ .attr('y', h - borderPadding / 2 + 16) // Adjust y position based on the rectangle's height
240
+ .attr('text-anchor', 'middle')
241
+ .style('font-family', 'IBM Plex Sans')
242
+ .style('font-size', '14px')
243
+ .style('font-weight', 'bold'); // Make the font bold
244
+
245
+ svg.append('rect')
246
+ .attr('x', w - borderPadding / 2) // Position it close to the right edge
247
+ .attr('y', 0)
248
+ .attr('width', borderPadding / 2) // Width of the border area
249
+ .attr('height', '100%')
250
+ .style('fill', hexToHalfOpacityRGBA(publicColor));
251
+
252
+ svg.append('text')
253
+ .text('Public')
254
+ .attr('x', w - (borderPadding / 2)) // Position close to the right edge
255
+ .attr('y', ch - 10) // Vertically centered
256
+ .attr('text-anchor', 'middle')
257
+ .attr('dominant-baseline', 'middle') // Center alignment of the text
258
+ .attr('transform', `rotate(90, ${w - (borderPadding / 2)}, ${ch})`) // Rotate around the text's center
259
+ .style('font-family', 'IBM Plex Sans')
260
+ .style('font-size', '14px')
261
+ .style('font-weight', 'bold'); // Make the font bold
262
+
263
+ svg.append('rect')
264
+ .attr('x', 0) // Positioned at the left edge
265
+ .attr('y', 0)
266
+ .attr('width', borderPadding / 2) // Width of the border area
267
+ .attr('height', '100%')
268
+ .style('fill', hexToHalfOpacityRGBA(privateColor));
269
+
270
+ svg.append('text')
271
+ .text('Private')
272
+ .attr('x', borderPadding / 2) // Position close to the left edge
273
+ .attr('y', ch) // Vertically centered
274
+ .attr('text-anchor', 'middle')
275
+ .attr('dominant-baseline', 'middle') // Center alignment of the text
276
+ .attr('transform', `rotate(-90, ${borderPadding / 2 - 10}, ${ch})`) // Rotate around the text's center
277
+ .style('font-family', 'IBM Plex Sans')
278
+ .style('font-size', '14px')
279
+ .style('font-weight', 'bold'); // Make the font bold
280
+
281
+ function boundaryForce(alpha) {
282
+ return function(d) {
283
+ const nodeRadius = circleSize(d.totalCount) / 2;
284
+ d.x = Math.max(borderPadding + nodeRadius, Math.min(w - borderPadding - nodeRadius, d.x));
285
+ d.y = Math.max(borderPadding + nodeRadius, Math.min(h - borderPadding - nodeRadius, d.y));
286
+ };
287
+ }
288
+
289
+ const simulation = d3.forceSimulation(data)
290
+ .force('center', d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2).strength(1)) // Scale center force strength
291
+ .force("x", d3.forceX(d => d.targetX).strength(0.3))
292
+ .force("y", d3.forceY(d => d.targetY).strength(0.3))
293
+ .force("charge", d3.forceManyBody().strength(-0.5))
294
+ .force("collide", d3.forceCollide(d => circleSize(d.totalCount) * 1.1 + 15).strength(1))
295
+ .force("boundary", boundaryForce(0.5))
296
+ .on('tick', ticked);
297
+
298
+ const app = svg.selectAll('.app')
299
+ .data(data)
300
+ .enter().append('g')
301
+ .attr('class', 'app')
302
+ .call(d3.drag()
303
+ .on('start', dragstarted)
304
+ .on('drag', dragged)
305
+ .on('end', dragended));
306
+
307
+ app.each(function(d) {
308
+ const group = d3.select(this);
309
+ const pieData = pie(getPieData(d));
310
+ const radius = circleSize(d.totalCount);
311
+
312
+ group.selectAll('path')
313
+ .data(pieData)
314
+ .enter().append('path')
315
+ .attr('d', arc.innerRadius(0).outerRadius(radius))
316
+ .attr('fill', (d, i) => pieColors(i));
317
+ });
318
+
319
+ app.append('text')
320
+ .text(d => d.name)
321
+ .attr('text-anchor', 'middle')
322
+ .attr('y', d => circleSize(d.totalCount) + 10)
323
+ .style('font-family', 'IBM Plex Sans') // Set the font family
324
+ .style('font-size', '12px') // Set the font size
325
+ .style('font-weight', 'bold'); // Make the font bold
326
+
327
+ // Initialize app positions at the center
328
+ app.attr('transform', `translate(${window.innerWidth / 2}, ${window.innerHeight / 2})`);
329
+
330
+ function ticked() {
331
+ app.attr('transform', d => `translate(${d.x}, ${d.y})`);
332
+ }
333
+
334
+ function dragstarted(event, d) {
335
+ if (!event.active) simulation.alphaTarget(1).restart();
336
+ d.fx = d.x;
337
+ d.fy = d.y;
338
+ }
339
+
340
+ function dragged(event, d) {
341
+ d.fx = event.x;
342
+ d.fy = event.y;
343
+ }
344
+
345
+ function dragended(event, d) {
346
+ if (!event.active) simulation.alphaTarget(0);
347
+ d.fx = null;
348
+ d.fy = null;
349
+ }
350
+ }
351
+
352
+ // Modify your fetchData function to call drawApplications after data transformation
353
+ function fetchData() {
354
+ fetch('http://localhost:19999/api/v1/function?function=network-viewer')
355
+ .then(response => response.json())
356
+ .then(data => {
357
+ // Your existing code
358
+ const desiredColumns = ["Direction", "Protocol", "Namespace", "Process", "CommandLine", "LocalIP", "LocalPort", "RemoteIP", "RemotePort", "LocalAddressSpace", "RemoteAddressSpace"];
359
+ const transformed = transformData(data, desiredColumns, data.columns);
360
+
361
+ // Now draw the applications with border forces
362
+ drawApplications(transformed);
363
+ })
364
+ .catch(error => console.error('Error fetching data:', error));
365
+ }
366
+
367
+ // Load data on start
368
+ window.onload = fetchData;
369
+</script>
370
+</body>
371
+</html>
collectors/plugins.d/local-sockets.h
+2
-2
@@ -170,7 +170,7 @@ typedef struct local_socket {
170
static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) __attribute__ ((format(__printf__, 2, 3)));
171
static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) {
172
if(++ls->stats.errors_encountered == ls->config.max_errors) {
173
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-LISTENERS: max number of logs reached. Not logging anymore");
173
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-SOCKETS: max number of logs reached. Not logging anymore");
174
return;
175
}
176
@@ -183,7 +183,7 @@ static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) {
183
vsnprintf(buf, sizeof(buf), format, args);
184
va_end(args);
185
186
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-LISTENERS: %s", buf);
186
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-SOCKETS: %s", buf);
187
}
188
189
// --------------------------------------------------------------------------------------------------------------------
health/schema.d/health:alert:prototype.json
+6
-6
@@ -452,9 +452,9 @@
452
"ui:classNames": "dyncfg-grid-col-span-5-2"
453
},
454
"value": {
455
- "ui:classNames": "dyncfg-grid-col-span-1-6",
455
+ "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
456
"database_lookup": {
457
- "ui:classNames": "dyncfg-grid-col-span-1-6",
457
+ "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
458
"after": {
459
"ui:classNames": "dyncfg-grid-col-span-1-1"
460
},
@@ -479,7 +479,7 @@
479
}
480
},
481
"conditions": {
482
- "ui:classNames": "dyncfg-grid-col-span-1-6",
482
+ "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
483
"warning_condition": {
484
"ui:classNames": "dyncfg-grid-col-span-1-2"
485
},
@@ -494,7 +494,7 @@
494
}
495
},
496
"action": {
497
- "ui:classNames": "dyncfg-grid-col-span-1-6",
497
+ "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
498
"execute": {
499
"ui:classNames": "dyncfg-grid-col-span-1-3"
500
},
@@ -507,7 +507,7 @@
507
"delay": {
508
"ui:Collapsible": true,
509
"ui:InitiallyExpanded": false,
510
- "ui:classNames": "dyncfg-grid-col-span-1-6",
510
+ "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
511
"up": {
512
"ui:classNames": "dyncfg-grid-col-span-1-2"
513
},
@@ -524,7 +524,7 @@
524
"repeat": {
525
"ui:Collapsible": true,
526
"ui:InitiallyExpanded": false,
527
- "ui:classNames": "dyncfg-grid-col-span-1-6",
527
+ "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
528
"enabled": {
529
"ui:classNames": "dyncfg-grid-col-span-1-2"
530
},