From 6203fb26a86475a77bcda52e1502a61778e7004b Mon Sep 17 00:00:00 2001 From: Stephen Pryor Date: Wed, 1 Nov 2017 14:35:45 -0700 Subject: [PATCH] Attempt to resolve telemetry memory leak (#785) * Attempt to resolve telemetry memory leak --- src/components/charts/timeline.js | 53 +++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/components/charts/timeline.js b/src/components/charts/timeline.js index ee347c899..78ab2aea0 100644 --- a/src/components/charts/timeline.js +++ b/src/components/charts/timeline.js @@ -4,39 +4,78 @@ import React, { Component } from 'react'; import C3 from 'c3'; import moment from 'moment'; import _ from 'lodash'; +import Rx from 'rxjs'; import Config from '../../common/config'; import './chart.css'; +const SLIDING_WINDOW_SIZE = 300; + class Timeline extends Component { + constructor(props) { super(props); - this.state = { - timeline: {} - }; + this.chart = {}; + // The chartDataQueue + this.chartDataQueue = []; } componentDidMount() { - this.setState({ timeline: C3.generate({ ...this.props.chartConfig }) }); + // Temp Fix: Pending issues https://github.com/c3js/c3/issues/2057 & https://github.com/c3js/c3/issues/1097 + // Every five minutes, rebuild the chart to avoid a memory leak in the chart library + // TODO: Remove these hacks when the D3 chart is fixed + this.subscription = Rx.Observable.interval(1000*60*5) + .startWith(0) + .subscribe(_ => { + if (this.chart.unload) { + const generateProps = { + ...this.props.chartConfig, + data: { + ...this.props.chartConfig.data, + json: this.chartDataQueue + } + }; + this.chart.unload({ + done: () => this.createChart(generateProps) + }); + } else { + this.createChart(this.props.chartConfig); + } + }); + } + + componentWillUnmount() { + this.subscription.unsubscribe(); + this.chart.destroy(); + } + + createChart(generateProps) { + this.chart = C3.generate(generateProps); } destroyChart() { - this.state.timeline.unload(); + this.chart.unload(); } switchChart(props) { - this.state.timeline.load({ ...props, unload: true }); + this.chart.load({ ...props, unload: true }); + this.chartDataQueue = props.json; } updateChart(props) { const startTime = moment() .subtract(Config.INTERVALS.TELEMETRY_SLIDE_WINDOW_MIN, 'minutes') .toISOString(); - this.state.timeline.flow({ + this.chart.flow({ ...props, duration: Config.INTERVALS.TELEMETRY_FLOW_DURATION_MS, to: startTime }); + // Update the chart data queue with new data and prune it if it gets too large + this.chartDataQueue = [ ...this.chartDataQueue, ...props.json ]; + while(this.chartDataQueue.length > SLIDING_WINDOW_SIZE) { + this.chartDataQueue.shift(); + } } componentWillReceiveProps(nextProps) {