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 };