From fb1b4603224aa87a2ba0bfeaa5ce9f789eeeac02 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Mon, 8 Oct 2018 09:55:12 -0700 Subject: [PATCH] Use memoize one to cache transformation output --- ui/src/flux/components/TimeMachineTables.tsx | 11 ++-- ui/src/shared/components/GaugeChart.tsx | 26 +++++++-- ui/src/shared/components/LineGraph.tsx | 23 ++++++-- ui/src/shared/components/SingleStat.tsx | 23 ++++++-- ui/src/shared/components/TableGraphFormat.tsx | 57 +++++++++++++------ .../shared/components/TableGraphTransform.tsx | 8 ++- ui/src/shared/graphs/helpers.ts | 34 ++++++++--- ui/src/worker/JobManager.ts | 32 +++++------ 8 files changed, 157 insertions(+), 57 deletions(-) diff --git a/ui/src/flux/components/TimeMachineTables.tsx b/ui/src/flux/components/TimeMachineTables.tsx index 5b1254e2b..500a48984 100644 --- a/ui/src/flux/components/TimeMachineTables.tsx +++ b/ui/src/flux/components/TimeMachineTables.tsx @@ -56,9 +56,9 @@ const filterTables = (tables: FluxTable[]): FluxTable[] => { }) } -const filteredTablesMemoized = memoizeOne(filterTables) - class TimeMachineTables extends PureComponent { + private filteredTablesMemoized = memoizeOne(filterTables) + constructor(props) { super(props) @@ -183,9 +183,12 @@ class TimeMachineTables extends PureComponent { } private get selectedResult(): FluxTable { - const filteredTables = filteredTablesMemoized(this.props.data) + const filteredTables = this.filteredTablesMemoized(this.props.data) + const table = filteredTables.find( + d => d.name === this.state.selectedResultID + ) - return filteredTables.find(d => d.name === this.state.selectedResultID) + return table } } diff --git a/ui/src/shared/components/GaugeChart.tsx b/ui/src/shared/components/GaugeChart.tsx index a6eacd02c..78229ee9a 100644 --- a/ui/src/shared/components/GaugeChart.tsx +++ b/ui/src/shared/components/GaugeChart.tsx @@ -1,6 +1,7 @@ // Libraries import React, {PureComponent} from 'react' import _ from 'lodash' +import memoizeOne from 'memoize-one' // Components import Gauge from 'src/shared/components/Gauge' @@ -8,7 +9,12 @@ import InvalidData from 'src/shared/components/InvalidData' // Utils import {manager} from 'src/worker/JobManager' -import {getDataUUID, hasDataChanged} from 'src/shared/graphs/helpers' +import { + getDataUUID, + hasDataPropsChanged, + isFluxDataEqual, + isInluxQLDataEqual, +} from 'src/shared/graphs/helpers' import getLastValues from 'src/shared/parsing/lastValues' import {ErrorHandling} from 'src/shared/decorators/errors' @@ -52,6 +58,14 @@ class GaugeChart extends PureComponent { private isComponentMounted: boolean private lastUUID: string + private memoizedTimeSeriesToSingleStat = memoizeOne( + getLastValues, + isInluxQLDataEqual + ) + private memoizedFluxTablesToSingleStat = memoizeOne( + manager.fluxTablesToSingleStat, + isFluxDataEqual + ) constructor(props: Props) { super(props) @@ -67,7 +81,7 @@ class GaugeChart extends PureComponent { } public async componentDidUpdate(prevProps: Props) { - const isDataChanged = hasDataChanged(prevProps, this.props) + const isDataChanged = hasDataPropsChanged(prevProps, this.props) this.lastUUID = getDataUUID(this.props.data, this.props.dataType) @@ -139,9 +153,13 @@ class GaugeChart extends PureComponent { let isValidData = true try { if (dataType === DataType.flux) { - lastValues = await manager.fluxTablesToSingleStat(data as FluxTable[]) + lastValues = await this.memoizedFluxTablesToSingleStat( + data as FluxTable[] + ) } else if (dataType === DataType.influxQL) { - lastValues = getLastValues(data as TimeSeriesServerResponse[]) + lastValues = this.memoizedTimeSeriesToSingleStat( + data as TimeSeriesServerResponse[] + ) } if ( diff --git a/ui/src/shared/components/LineGraph.tsx b/ui/src/shared/components/LineGraph.tsx index fa753a50e..74a1e0b1c 100644 --- a/ui/src/shared/components/LineGraph.tsx +++ b/ui/src/shared/components/LineGraph.tsx @@ -3,6 +3,7 @@ import React, {PureComponent, CSSProperties} from 'react' import _ from 'lodash' import Dygraph from 'src/shared/components/Dygraph' import {withRouter, RouteComponentProps} from 'react-router' +import memoizeOne from 'memoize-one' // Components import SingleStat from 'src/shared/components/SingleStat' @@ -16,7 +17,12 @@ import { } from 'src/utils/timeSeriesTransformers' import {manager} from 'src/worker/JobManager' import {fluxTablesToDygraph} from 'src/shared/parsing/flux/dygraph' -import {getDataUUID, hasDataChanged} from 'src/shared/graphs/helpers' +import { + getDataUUID, + hasDataPropsChanged, + isFluxDataEqual, + isInluxQLDataEqual, +} from 'src/shared/graphs/helpers' // Types import {ColorString} from 'src/types/colors' @@ -66,6 +72,15 @@ class LineGraph extends PureComponent { private isComponentMounted: boolean = false private isValidData: boolean = true + private memoizedTimeSeriesToDygraph = memoizeOne( + timeSeriesToDygraph, + isInluxQLDataEqual + ) + private memoizedFluxTablesToDygraph = memoizeOne( + fluxTablesToDygraph, + isFluxDataEqual + ) + constructor(props: LineGraphProps) { super(props) @@ -108,7 +123,7 @@ class LineGraph extends PureComponent { } public componentDidUpdate(prevProps: LineGraphProps) { - const isDataChanged = hasDataChanged(prevProps, this.props) + const isDataChanged = hasDataPropsChanged(prevProps, this.props) if (this.props.loading === RemoteDataState.Done && isDataChanged) { this.parseTimeSeries(this.props.data, this.props.dataType) @@ -245,14 +260,14 @@ class LineGraph extends PureComponent { let result: TimeSeriesToDyGraphReturnType if (dataType === DataType.influxQL) { - result = await timeSeriesToDygraph( + result = await this.memoizedTimeSeriesToDygraph( data as TimeSeriesServerResponse[], location.pathname ) } if (dataType === DataType.flux) { - result = await fluxTablesToDygraph(data as FluxTable[]) + result = await this.memoizedFluxTablesToDygraph(data as FluxTable[]) } return {result, uuid: getDataUUID(data, dataType)} diff --git a/ui/src/shared/components/SingleStat.tsx b/ui/src/shared/components/SingleStat.tsx index cc1d6753d..1d7b749a6 100644 --- a/ui/src/shared/components/SingleStat.tsx +++ b/ui/src/shared/components/SingleStat.tsx @@ -2,6 +2,7 @@ import React, {PureComponent, CSSProperties} from 'react' import classnames from 'classnames' import _ from 'lodash' +import memoizeOne from 'memoize-one' // Components import InvalidData from 'src/shared/components/InvalidData' @@ -11,7 +12,9 @@ import {manager} from 'src/worker/JobManager' import { SMALL_CELL_HEIGHT, getDataUUID, - hasDataChanged, + hasDataPropsChanged, + isFluxDataEqual, + isInluxQLDataEqual, } from 'src/shared/graphs/helpers' import getLastValues from 'src/shared/parsing/lastValues' import {ErrorHandling} from 'src/shared/decorators/errors' @@ -60,6 +63,14 @@ class SingleStat extends PureComponent { private isComponentMounted: boolean private lastUUID: string + private memoizedTimeSeriesToSingleStat = memoizeOne( + getLastValues, + isInluxQLDataEqual + ) + private memoizedFluxTablesToSingleStat = memoizeOne( + manager.fluxTablesToSingleStat, + isFluxDataEqual + ) constructor(props: Props) { super(props) @@ -76,7 +87,7 @@ class SingleStat extends PureComponent { public async componentDidUpdate(prevProps: Props) { this.lastUUID = getDataUUID(this.props.data, this.props.dataType) - const isDataChanged = hasDataChanged(prevProps, this.props) + const isDataChanged = hasDataPropsChanged(prevProps, this.props) if (isDataChanged) { await this.dataToLastValues() @@ -246,9 +257,13 @@ class SingleStat extends PureComponent { let isValidData = true try { if (dataType === DataType.flux) { - lastValues = await manager.fluxTablesToSingleStat(data as FluxTable[]) + lastValues = await this.memoizedFluxTablesToSingleStat( + data as FluxTable[] + ) } else if (dataType === DataType.influxQL) { - lastValues = getLastValues(data as TimeSeriesServerResponse[]) + lastValues = this.memoizedTimeSeriesToSingleStat( + data as TimeSeriesServerResponse[] + ) } if ( diff --git a/ui/src/shared/components/TableGraphFormat.tsx b/ui/src/shared/components/TableGraphFormat.tsx index 227483439..792448173 100644 --- a/ui/src/shared/components/TableGraphFormat.tsx +++ b/ui/src/shared/components/TableGraphFormat.tsx @@ -1,6 +1,7 @@ // Libraries import React, {PureComponent} from 'react' import _ from 'lodash' +import memoizeOne from 'memoize-one' // Utils import {manager} from 'src/worker/JobManager' @@ -64,8 +65,39 @@ interface State { invalidDataError: ErrorTypes } +interface FormatProperties { + sort?: Sort + fieldOptions: FieldOption[] + tableOptions: TableOptions + timeFormat: string + decimalPlaces: DecimalPlaces + uuid: string +} +const areFormatPropertiesEqual = ( + prevProperties: FormatProperties, + newProperties: FormatProperties +) => { + const formatProps = [ + 'uuid', + 'tableOptions', + 'fieldOptions', + 'timeFormat', + 'sort', + ] + + const areEqual = formatProps.every(k => + _.isEqual(prevProperties[k], newProperties[k]) + ) + + return areEqual +} + class TableGraphFormat extends PureComponent { private isComponentMounted: boolean + private memoizedTableTransform = memoizeOne( + manager.tableTransform, + areFormatPropertiesEqual + ) constructor(props: Props) { super(props) @@ -116,16 +148,7 @@ class TableGraphFormat extends PureComponent { } public componentDidUpdate(prevProps: Props) { - const updatedProps = _.keys(_.omit(prevProps, 'data')).filter( - k => !_.isEqual(this.props[k], prevProps[k]) - ) - - if ( - this.props.uuid !== prevProps.uuid || - _.includes(updatedProps, 'tableOptions') || - _.includes(updatedProps, 'fieldOptions') || - _.includes(updatedProps, 'timeFormat') - ) { + if (!areFormatPropertiesEqual(prevProps, this.props)) { this.formatData() } } @@ -140,7 +163,7 @@ class TableGraphFormat extends PureComponent { decimalPlaces, } = this.props - const {sort} = this.state + const sort = {...this.state.sort} if (sortField === sort.field) { sort.direction = sort.direction === ASCENDING ? DESCENDING : ASCENDING @@ -159,14 +182,15 @@ class TableGraphFormat extends PureComponent { ) try { - const formattedData = await manager.tableTransform( - this.props.data.data, + const formattedData = await this.memoizedTableTransform({ + data: this.props.data.data, sort, - computedFieldOptions, + fieldOptions: computedFieldOptions, tableOptions, timeFormat, - decimalPlaces - ) + decimalPlaces, + uuid: latestUUID, + }) if (!this.isComponentMounted) { return @@ -184,6 +208,7 @@ class TableGraphFormat extends PureComponent { if (!this.isComponentMounted) { return } + console.error(err) this.setState({invalidDataError: ErrorTypes.GeneralError}) } diff --git a/ui/src/shared/components/TableGraphTransform.tsx b/ui/src/shared/components/TableGraphTransform.tsx index 2bf44b589..17bcae053 100644 --- a/ui/src/shared/components/TableGraphTransform.tsx +++ b/ui/src/shared/components/TableGraphTransform.tsx @@ -2,6 +2,7 @@ import React, {PureComponent} from 'react' import _ from 'lodash' import uuid from 'uuid' +import memoizeOne from 'memoize-one' // Components import InvalidData from 'src/shared/components/InvalidData' @@ -12,6 +13,7 @@ import { ErrorTypes, getInvalidDataMessage, } from 'src/dashboards/utils/tableGraph' +import {isInluxQLDataEqual} from 'src/shared/graphs/helpers' // Types import { @@ -40,6 +42,10 @@ interface State { class TableGraphTransform extends PureComponent { private isComponentMounted: boolean + private memoizedTimeSeriesToTableGraph = memoizeOne( + timeSeriesToTableGraph, + isInluxQLDataEqual + ) constructor(props: Props) { super(props) @@ -83,7 +89,7 @@ class TableGraphTransform extends PureComponent { if (dataType === DataType.influxQL) { try { - const influxQLData = await timeSeriesToTableGraph( + const influxQLData = await this.memoizedTimeSeriesToTableGraph( data as TimeSeriesServerResponse[] ) diff --git a/ui/src/shared/graphs/helpers.ts b/ui/src/shared/graphs/helpers.ts index cb32e907b..c16e176e7 100644 --- a/ui/src/shared/graphs/helpers.ts +++ b/ui/src/shared/graphs/helpers.ts @@ -187,27 +187,47 @@ export const getDataUUID = ( dataType: DataType ): string => { if (dataType === DataType.influxQL) { - return getDeep(data, '0.response.uuid', '') + return getInfluxQLDataUUID(data as TimeSeriesServerResponse[]) } else { - return getDeep(data, '0.id', '') + return getFluxDataUUID(data as FluxTable[]) } } +const getInfluxQLDataUUID = (data: TimeSeriesServerResponse[]): string => { + return getDeep(data, '0.response.uuid', '') +} + +const getFluxDataUUID = (data: FluxTable[]): string => { + return getDeep(data, '0.id', '') +} + +export const isFluxDataEqual = ( + prevData: FluxTable[], + newData: FluxTable[] +): boolean => { + return getFluxDataUUID(prevData) === getFluxDataUUID(newData) +} + +export const isInluxQLDataEqual = ( + prevData: TimeSeriesServerResponse[], + newData: TimeSeriesServerResponse[] +): boolean => { + return getInfluxQLDataUUID(prevData) === getInfluxQLDataUUID(newData) +} + interface DataProps { data: TimeSeriesServerResponse[] | FluxTable[] dataType: DataType timeRange?: TimeRange } -export const hasDataChanged = ( +export const hasDataPropsChanged = ( prevProps: DataProps, newProps: DataProps ): boolean => { const isDataTypeChanged = prevProps.dataType !== newProps.dataType - const isDataIDsChanged = !_.isEqual( - getDataUUID(prevProps.data, prevProps.dataType), + const isDataIDsChanged = + getDataUUID(prevProps.data, prevProps.dataType) !== getDataUUID(newProps.data, newProps.dataType) - ) - const isTimeRangeChanged = !_.isEqual( _.get(prevProps, 'timeRange'), _.get(newProps, 'timeRange') diff --git a/ui/src/worker/JobManager.ts b/ui/src/worker/JobManager.ts index 4ef060af7..34bccfbe2 100644 --- a/ui/src/worker/JobManager.ts +++ b/ui/src/worker/JobManager.ts @@ -7,6 +7,13 @@ import { TimeSeriesToTableGraphReturnType, } from 'src/types/series' import {DygraphValue, FluxTable} from 'src/types' +import { + Sort, + FieldOption, + TableOptions, + DecimalPlaces, + TimeSeriesValue, +} from 'src/types/dashboards' import {getBasepath} from 'src/utils/basepath' import {TimeSeriesToDyGraphReturnType} from 'src/worker/jobs/timeSeriesToDygraph' import {FluxTablesToDygraphResult} from 'src/worker/jobs/fluxTablesToDygraph' @@ -36,23 +43,14 @@ class JobManager { }) } - public async tableTransform( - data, - sort, - fieldOptions, - tableOptions, - timeFormat, - decimalPlaces - ): Promise { - const payload = { - data, - sort, - fieldOptions, - tableOptions, - timeFormat, - decimalPlaces, - } - + public tableTransform = async (payload: { + data: TimeSeriesValue[][] + sort: Sort + fieldOptions: FieldOption[] + tableOptions: TableOptions + timeFormat: string + decimalPlaces: DecimalPlaces + }): Promise => { return this.publishDBJob('TABLETRANSFORM', payload) }