vitess.io/vitess@v0.16.2/web/vtadmin/src/components/charts/WorkflowStreamsLagChart.tsx (about)

     1  /**
     2   * Copyright 2021 The Vitess Authors.
     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  import { useMemo } from 'react';
    18  
    19  import { useManyExperimentalTabletDebugVars, useWorkflow } from '../../hooks/api';
    20  import { vtadmin } from '../../proto/vtadmin';
    21  import { getStreamVReplicationLagTimeseries, QPS_REFETCH_INTERVAL } from '../../util/tabletDebugVars';
    22  import { formatStreamKey, getStreams, getStreamTablets } from '../../util/workflows';
    23  import { Timeseries } from './Timeseries';
    24  
    25  interface Props {
    26      clusterID: string;
    27      keyspace: string;
    28      workflowName: string;
    29  }
    30  
    31  // Default min/max values (in seconds) for the y-axis when there is no data to show.
    32  const DEFAULT_Y_MAX = 5;
    33  const DEFAULT_Y_MIN = 0;
    34  
    35  export const WorkflowStreamsLagChart = ({ clusterID, keyspace, workflowName }: Props) => {
    36      const { data: workflow, ...wq } = useWorkflow({ clusterID, keyspace, name: workflowName });
    37  
    38      const queryParams = useMemo(() => {
    39          const aliases = getStreamTablets(workflow);
    40          return aliases.map((alias) => ({ alias, clusterID }));
    41      }, [clusterID, workflow]);
    42  
    43      const tabletQueries = useManyExperimentalTabletDebugVars(queryParams, {
    44          enabled: !!workflow,
    45          refetchInterval: QPS_REFETCH_INTERVAL,
    46          refetchIntervalInBackground: true,
    47      });
    48  
    49      const anyLoading = wq.isLoading || tabletQueries.some((q) => q.isLoading);
    50  
    51      const chartOptions: Highcharts.Options = useMemo(() => {
    52          const series = formatSeries(workflow, tabletQueries);
    53          const allSeriesEmpty = series.every((s) => !s.data?.length);
    54  
    55          return {
    56              series,
    57              yAxis: {
    58                  labels: {
    59                      format: '{text} s',
    60                  },
    61                  // The desired behaviour is to show axes + grid lines
    62                  // even when there is no data to show. Unfortunately, setting
    63                  // softMin/softMax (which is more flexible) doesn't work with showEmpty.
    64                  // Instead, we must set explicit min/max, but only when all series are empty.
    65                  // If at least one series has data, allow min/max to be automatically calculated.
    66                  max: allSeriesEmpty ? DEFAULT_Y_MAX : null,
    67                  min: allSeriesEmpty ? DEFAULT_Y_MIN : null,
    68              },
    69          };
    70      }, [tabletQueries, workflow]);
    71  
    72      return <Timeseries isLoading={anyLoading} options={chartOptions} />;
    73  };
    74  
    75  // Internal function, exported only for testing.
    76  export const formatSeries = (
    77      workflow: vtadmin.Workflow | null | undefined,
    78      tabletQueries: ReturnType<typeof useManyExperimentalTabletDebugVars>
    79  ): Highcharts.SeriesLineOptions[] => {
    80      if (!workflow) {
    81          return [];
    82      }
    83  
    84      // Get streamKeys for streams in this workflow.
    85      const streamKeys = getStreams(workflow).map((s) => formatStreamKey(s));
    86  
    87      // Initialize the timeseries from the workflow, so that every stream in the workflow
    88      // is shown in the legend, even if the /debug/vars data isn't (yet) available.
    89      const seriesByStreamKey: { [streamKey: string]: Highcharts.SeriesLineOptions } = {};
    90  
    91      streamKeys.forEach((streamKey) => {
    92          if (streamKey) {
    93              seriesByStreamKey[streamKey] = { data: [], name: streamKey, type: 'line' };
    94          }
    95      });
    96  
    97      tabletQueries.forEach((tq) => {
    98          if (!tq.data) {
    99              return;
   100          }
   101  
   102          const tabletAlias = tq.data.params.alias;
   103  
   104          const lagData = getStreamVReplicationLagTimeseries(tq.data.data, tq.dataUpdatedAt);
   105          Object.entries(lagData).forEach(([streamID, streamLagData]) => {
   106              // Don't graph aggregate vreplication lag for the tablet, since that
   107              // can include vreplication lag data for streams running on the tablet
   108              // that are not in the current workflow.
   109              if (streamID === 'All') {
   110                  return;
   111              }
   112  
   113              const streamKey = `${tabletAlias}/${streamID}`;
   114  
   115              // Don't graph series for streams that aren't in this workflow.
   116              if (!(streamKey in seriesByStreamKey)) {
   117                  return;
   118              }
   119  
   120              seriesByStreamKey[streamKey].data = streamLagData;
   121          });
   122      });
   123  
   124      return Object.values(seriesByStreamKey);
   125  };