github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/cluster-stats.js (about) 1 /* 2 Copyright 2023. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 'use strict'; 18 19 let EventCountChart; 20 21 $(document).ready(() => { 22 $('#app-content-area').hide(); 23 setupEventHandlers(); 24 $('.theme-btn').on('click', themePickerHandler); 25 $('.theme-btn').on('click', renderChart); 26 $('#empty-response').empty(); 27 $('#empty-response').hide(); 28 29 let stDate = "now-7d"; 30 let endDate = "now"; 31 datePickerHandler(stDate, endDate, stDate); 32 $('.range-item').on('click', iStatsDatePickerHandler); 33 34 // Make api call to get the cluster stats 35 let data = getTimeRange(); 36 renderClusterStatsTables(); 37 renderChart(); 38 if (Cookies.get('theme')) { 39 theme = Cookies.get('theme'); 40 $('body').attr('data-theme', theme); 41 } 42 {{ .Button1Function }} 43 }); 44 45 function iStatsDatePickerHandler(evt) { 46 evt.preventDefault(); 47 renderChart(); 48 $('#daterangepicker').hide(); 49 } 50 51 function getTimeRange() { 52 return { 53 'startEpoch': filterStartDate || "now-7d", 54 'endEpoch': filterEndDate || "now", 55 }; 56 } 57 58 function renderChart() { 59 let endDate = filterEndDate || "now"; 60 let stDate = filterStartDate || "now-7d"; 61 let data = getTimeRange(); 62 63 $.ajax({ 64 method: 'post', 65 url: 'api/clusterIngestStats', 66 headers: { 67 'Content-Type': 'application/json; charset=utf-8', 68 'Accept': '*/*' 69 }, 70 crossDomain: true, 71 dataType: 'json', 72 data: JSON.stringify(data) 73 }) 74 .then((res)=> { 75 $('#app-content-area').show(); 76 drawStatsChart(res,data) 77 }) 78 .catch(showCStatsError); 79 } 80 81 function drawStatsChart(res,data) { 82 let gridLineColor; 83 let tickColor; 84 if ($('body').attr('data-theme') == "light") { 85 gridLineColor = "#DCDBDF"; 86 tickColor = "#160F29"; 87 } 88 else { 89 gridLineColor = "#383148"; 90 tickColor = "#FFFFFF" 91 } 92 var GBCountData = []; 93 var EventCountData = []; 94 _.forEach(res, (mvalue, key) => { 95 if (key === "chartStats") { 96 _.forEach(mvalue, (val, bucketKey) => 97 { 98 var dataPointsPerMin; 99 if(data.startEpoch==='now-24h'){ 100 dataPointsPerMin = (val.MetricsCount/60); 101 } 102 else{ 103 dataPointsPerMin = (val.MetricsCount/(60*24)); 104 } 105 106 GBCountData.push({ 107 x: bucketKey, 108 y: val.GBCount 109 }), 110 EventCountData.push({ 111 x: bucketKey, 112 y: val.EventCount 113 }) 114 }) 115 if (GBCountChart !== undefined) { 116 GBCountChart.destroy(); 117 } 118 if (EventCountChart !== undefined) { 119 EventCountChart.destroy(); 120 } 121 GBCountChart = renderGBCountChart(GBCountData,gridLineColor,tickColor); 122 EventCountChart=renderEventCountChart(EventCountData,gridLineColor,tickColor); 123 } 124 }) 125 } 126 127 function renderGBCountChart(GBCountData,gridLineColor,tickColor) { 128 var GBCountChartCanvas = $("#GBCountChart").get(0).getContext("2d"); 129 130 GBCountChart = new Chart(GBCountChartCanvas, { 131 type: 'line', 132 data: { 133 datasets: [ 134 { 135 label: 'Ingestion Volume', 136 data: GBCountData, 137 borderColor: ['rgb(99,71,217)'], 138 yAxisID: 'y', 139 pointStyle: 'circle', 140 pointRadius: 10, 141 pointBorderColor: ['rgb(99,71,217)'], 142 fill: false, 143 }, 144 145 ] 146 }, 147 options: { 148 responsive: true, 149 interaction: { 150 intersect: false, 151 mode: 'index', 152 }, 153 plugins: { 154 tooltip: { 155 callbacks: { 156 label: function (context) { 157 let label = context.dataset.label || ''; 158 if (context.parsed.y !== null ) { 159 let f = context.parsed.y; 160 if (context.parsed.y >=10){ 161 f = Number((context.parsed.y).toFixed()).toLocaleString("en-us") 162 label += ' ' + (f) + ' GB'; 163 } 164 else{ 165 label += ' ' + (f).toFixed(3) + ' GB'; 166 167 } 168 } 169 return label; 170 } 171 }, 172 }, 173 legend: { 174 display: false 175 }, 176 }, 177 scales: { 178 y: { 179 ticks: { 180 callback: function (value, index, ticks) { 181 return (value).toFixed(3) + ' GB'; 182 }, 183 color: tickColor, 184 }, 185 beginAtZero: true, 186 type: 'linear', 187 display: true, 188 position: 'left', 189 title: { 190 display: true, 191 text: 'Ingestion Volume' 192 }, 193 grid: { 194 color: gridLineColor, 195 }, 196 }, 197 x: { 198 ticks: { 199 callback: function (val, index, ticks) { 200 let value = this.getLabelForValue(val); 201 if (value && value.indexOf('T') > -1) { 202 let parts = value.split('T'); 203 let xVal = "T" + parts[1]; 204 return xVal; 205 } else { 206 if (value) { 207 let parts = value.split('-'); 208 let xVal = parts[1] + "-" + parts[2]; 209 return xVal; 210 } 211 } 212 }, 213 color: tickColor, 214 }, 215 beginAtZero: true, 216 title: { 217 display: true, 218 text: 'Time Period' 219 }, 220 grid: { 221 color: gridLineColor, 222 }, 223 } 224 } 225 } 226 }); 227 return GBCountChart; 228 } 229 230 function renderEventCountChart(EventCountData,gridLineColor,tickColor){ 231 var EventCountCanvas = $("#EventCountChart").get(0).getContext("2d"); 232 233 EventCountChart = new Chart(EventCountCanvas, { 234 type: 'line', 235 data: { 236 datasets: [ 237 { 238 label: 'Event Count', 239 data: EventCountData, 240 borderColor: ['rgb(99,71,217)'], 241 yAxisID: 'y', 242 pointStyle: 'circle', 243 pointRadius: 10, 244 pointBorderColor: ['rgb(99,71,217)'], 245 fill: false, 246 }, 247 248 ] 249 }, 250 options: { 251 responsive: true, 252 interaction: { 253 intersect: false, 254 mode: 'index', 255 }, 256 plugins: { 257 tooltip: { 258 callbacks: { 259 label: function (context) { 260 let label = context.dataset.label || ''; 261 if (context.parsed.y !== null ) { 262 label += ' ' + parseInt(context.parsed.y).toLocaleString(); 263 } 264 return label; 265 } 266 }, 267 }, 268 legend: { 269 display: false 270 }, 271 }, 272 scales: { 273 y: { 274 ticks: { 275 callback: function (value, index, ticks) { 276 return parseInt(value).toLocaleString(); 277 }, 278 color: tickColor, 279 }, 280 beginAtZero: true, 281 type: 'linear', 282 display: true, 283 position: 'left', 284 title: { 285 display: true, 286 text: 'Event Count' 287 }, 288 grid: { 289 color: gridLineColor, 290 }, 291 }, 292 x: { 293 ticks: { 294 callback: function (val, index, ticks) { 295 let value = this.getLabelForValue(val); 296 if (value && value.indexOf('T') > -1) { 297 let parts = value.split('T'); 298 let xVal = "T" + parts[1]; 299 return xVal; 300 } else { 301 if (value) { 302 let parts = value.split('-'); 303 let xVal = parts[1] + "-" + parts[2]; 304 return xVal; 305 } 306 } 307 }, 308 color: tickColor, 309 }, 310 beginAtZero: true, 311 title: { 312 display: true, 313 text: 'Time Period' 314 }, 315 grid: { 316 color: gridLineColor, 317 }, 318 } 319 } 320 } 321 }); 322 return EventCountChart; 323 } 324 325 function drawTotalStatsChart(res) { 326 var totalIncomingVolume, totalIncomingVolumeMetrics; 327 var totalStorageUsed; 328 var totalStorageSaved; 329 var totalStorageUsedMetrics; 330 _.forEach(res, (mvalue, key) => { 331 if (key === "ingestionStats") { 332 333 _.forEach(mvalue, (v, k) => { 334 if (k === 'Log Incoming Volume'){ 335 totalIncomingVolume = v; 336 } 337 else if (k === 'Metrics Incoming Volume'){ 338 totalIncomingVolumeMetrics = v; 339 } 340 else if (k === 'Log Storage Used'){ 341 totalStorageUsed = v; 342 } 343 else if (k === 'Storage Saved'){ 344 totalStorageSaved = v; 345 } 346 else if (k === 'Metrics Storage Used'){ 347 totalStorageUsedMetrics = v; 348 } 349 }); 350 if (TotalVolumeChart !== undefined) { 351 TotalVolumeChart.destroy(); 352 } 353 354 TotalVolumeChart = renderTotalCharts(totalIncomingVolume, totalIncomingVolumeMetrics, totalStorageUsed, totalStorageUsedMetrics) 355 return TotalVolumeChart 356 } 357 }); 358 359 let el = $('.storage-savings-container'); 360 el.append(`<div class="storage-savings-percent">${Math.round(totalStorageSaved * 10) / 10}%`); 361 362 363 } 364 365 function renderTotalCharts(totalIncomingVolume, totalIncomingVolumeMetrics, totalStorageUsed, totalStorageUsedMetrics) { 366 var TotalVolumeChartCanvas = $("#TotalVolumeChart").get(0).getContext("2d"); 367 TotalVolumeChart = new Chart(TotalVolumeChartCanvas, { 368 type: 'bar', 369 data: { 370 labels: ['Incoming Volume','Storage Used'], 371 datasets: [ 372 { 373 374 label: 'Logs' , 375 data: [parseFloat(totalIncomingVolume),parseFloat(totalStorageUsed)], 376 backgroundColor: ['rgba(99, 72, 217)'], 377 borderWidth: 1, 378 categoryPercentage: 0.8, 379 barPercentage: 0.8, 380 381 }, 382 { 383 label:'Metrics' , 384 data: [parseFloat(totalIncomingVolumeMetrics),parseFloat(totalStorageUsedMetrics)], 385 backgroundColor: ['rgb(255,1,255)'], 386 borderWidth: 1, 387 categoryPercentage: 0.8, 388 barPercentage: 0.8, 389 390 }, 391 ] 392 }, 393 options: { 394 responsive: true, 395 maintainAspectRatio: false, 396 plugins: { 397 legend: { 398 position: 'top' 399 }, 400 tooltip: { 401 callbacks: { 402 label: function (context) { 403 let label = context.dataset.label || ''; 404 if (context.parsed.y !== null) { 405 label += ' ' + (context.parsed.y).toFixed(3) + ' GB'; 406 } 407 return label; 408 } 409 }, 410 }, 411 }, 412 scales: { 413 y: { 414 ticks: { 415 callback: function (value, index, ticks) { 416 return (value).toFixed(3) + ' GB'; 417 } 418 }, 419 420 }, 421 x: { 422 ticks: { 423 callback: function (val, index, ticks) { 424 let value = this.getLabelForValue(val); 425 if (value && value.indexOf('T') > -1) { 426 let parts = value.split('T'); 427 let xVal = "T" + parts[1]; 428 return xVal; 429 } else { 430 if (value) { 431 let parts = value.split('-'); 432 if (parts.length > 1) { 433 let xVal = parts[1] + "-" + parts[2]; 434 return xVal; 435 } 436 } 437 } 438 } 439 }, 440 title: { 441 display: true, 442 text: '' 443 }, 444 445 } 446 } 447 } 448 }); 449 return TotalVolumeChart 450 } 451 452 function processClusterStats(res) { 453 {{ .ClusterStatsSetUserRole }} 454 _.forEach(res, (value, key) => { 455 if (key === "ingestionStats") { 456 let table = $('#ingestion-table'); 457 _.forEach(value, (v, k) => { 458 let tr = $('<tr>'); 459 tr.append('<td>' + k + '</td>'); 460 tr.append('<td class="health-stats-value">' + v + '</td>'); 461 table.find('tbody').append(tr); 462 }) 463 } 464 if (key === "metricsStats") { 465 let table = $('#metrics-table'); 466 _.forEach(value, (v, k) => { 467 let tr = $('<tr>'); 468 tr.append('<td>' + k + '</td>'); 469 tr.append('<td class="health-stats-value">' + v + '</td>'); 470 table.find('tbody').append(tr); 471 }) 472 } 473 if (key === "queryStats") { 474 let table = $('#query-table'); 475 _.forEach(value, (v, k) => { 476 let tr = $('<tr>'); 477 tr.append('<td>' + k + '</td>'); 478 if (k === "Average Latency") { 479 const numericPart = parseFloat(v); 480 const avgLatency = Math.round(numericPart); 481 tr.append('<td class="health-stats-value">' + avgLatency + ' ms</td>'); 482 } 483 else 484 tr.append('<td class="health-stats-value">' + v.toLocaleString() + '</td>'); 485 table.find("tbody").append(tr); 486 }); 487 } 488 }) 489 490 let columnOrder = [ 491 'Index Name', 492 'Incoming Volume', 493 'Event Count', 494 ]; 495 496 {{ .ClusterStatsAdminView }} 497 498 let indexdataTableColumns = columnOrder.map((columnName, index) => { 499 let title = `<div class="grid"><div>${columnName} </div><div><i data-index="${index}"></i></div></div>`; 500 return { 501 title: title, 502 name: columnName, 503 visible: true, 504 defaultContent: ``, 505 }; 506 }); 507 508 const commonDataTablesConfig = { 509 bPaginate: true, 510 columns: indexdataTableColumns, 511 autoWidth: false, 512 colReorder: false, 513 scrollX: false, 514 deferRender: true, 515 scrollY: 500, 516 scrollCollapse: true, 517 scroller: true, 518 lengthChange: false, 519 searching: false, 520 order: [], 521 columnDefs: [], 522 data: [] 523 }; 524 525 let indexDataTable = $('#index-data-table').DataTable(commonDataTablesConfig); 526 let metricsDataTable = $('#metrics-data-table').DataTable(commonDataTablesConfig); 527 528 function displayIndexDataRows(res) { 529 let totalIngestVolume = 0; 530 let totalEventCount = 0; 531 let totalValRow = []; 532 totalValRow[0] = `Total`; 533 totalValRow[1] = `${Number(`${totalIngestVolume >= 10 ? totalIngestVolume.toFixed().toLocaleString("en-US") : totalIngestVolume}`)} GB`; 534 totalValRow[2] = `${totalEventCount.toLocaleString()}`; 535 indexDataTable.row.add(totalValRow); 536 if (res.indexStats && res.indexStats.length > 0) { 537 res.indexStats.map((item) => { 538 _.forEach(item, (v, k) => { 539 let currRow = []; 540 currRow[0] = k; 541 let l = parseFloat(v.ingestVolume) 542 currRow[1] = Number(`${l >= 10 ? l.toFixed().toLocaleString("en-US") : l}`) + ' GB'; 543 currRow[2] = `${v.eventCount}`; 544 {{ .ClusterStatsAdminButton }} 545 546 totalIngestVolume += parseFloat(`${v.ingestVolume}`); 547 totalEventCount += parseInt(`${v.eventCount}`.replaceAll(',','')); 548 549 indexDataTable.row.add(currRow); 550 }); 551 }) 552 } 553 if (res.metricsStats) { 554 let currRow = []; 555 currRow[0] = `metrics`; 556 let q = parseFloat(res.metricsStats["Incoming Volume"]) 557 currRow[1] = (Number(q >= 10 ? q.toFixed() : q)).toLocaleString("en-US") + ' GB'; 558 currRow[2] = `${res.metricsStats["Datapoints Count"]}`; 559 metricsDataTable.row.add(currRow); 560 561 } 562 563 totalIngestVolume = Math.round(parseFloat(`${res.ingestionStats["Log Incoming Volume"]}`) * 1000)/1000 564 totalValRow[1] = `${Number(`${totalIngestVolume >= 10 ? totalIngestVolume.toFixed().toLocaleString("en-US") : totalIngestVolume}`)} GB`; 565 totalValRow[2] = `${totalEventCount.toLocaleString()}`; 566 indexDataTable.draw(); 567 metricsDataTable.draw(); 568 } 569 570 {{ if .ClusterStatsCallDisplayRows }} 571 {{ .ClusterStatsCallDisplayRows }} 572 {{ else }} 573 setTimeout(() => { 574 displayIndexDataRows(res); 575 }, 0); 576 {{ end }} 577 578 } 579 580 581 582 function renderClusterStatsTables() { 583 {{ .ClusterStatsSetUserRole }} 584 {{ .ClusterStatsExtraFunctions }} 585 $.ajax({ 586 method: 'get', 587 url: 'api/clusterStats', 588 headers: { 589 'Content-Type': 'application/json; charset=utf-8', 590 'Accept': '*/*' 591 }, 592 crossDomain: true, 593 dataType: 'json', 594 }).then(function (res) { 595 $('#empty-response').empty(); 596 $('#empty-response').hide(); 597 drawTotalStatsChart(res); 598 {{ .ClusterStatsExtraSetup }} 599 processClusterStats(res); 600 $('#app-content-area').show(); 601 }).catch(showCStatsError); 602 } 603 604 function showCStatsError(res) { 605 if(res.status == 400) { 606 $('#empty-response').html('Permission Denied'); 607 $('#empty-response').show(); 608 $('#app-content-area').hide(); 609 } 610 }