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}&nbsp;</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  }