Fixed the selectors performance leaks.

This commit is contained in:
Filip Barl
2017-02-28 19:19:31 +01:00
parent 11ce5638c5
commit 19202a2e44
11 changed files with 77 additions and 59 deletions

View File

@@ -5,14 +5,23 @@ import { saveGraph } from '../utils/file-utils';
import { modulo } from '../utils/math-utils';
import { updateRoute } from '../utils/router-utils';
import { parseQuery } from '../utils/search-utils';
import { bufferDeltaUpdate, resumeUpdate,
resetUpdateBuffer } from '../utils/update-buffer-utils';
import { doControlRequest, getAllNodes, getNodesDelta, getNodeDetails,
getTopologies, deletePipe } from '../utils/web-api-utils';
import { getActiveTopologyOptions,
getCurrentTopologyUrl } from '../utils/topology-utils';
import {
bufferDeltaUpdate,
resumeUpdate,
resetUpdateBuffer,
} from '../utils/update-buffer-utils';
import {
doControlRequest,
getAllNodes,
getNodesDelta,
getNodeDetails,
getTopologies,
deletePipe,
} from '../utils/web-api-utils';
import { getCurrentTopologyUrl } from '../utils/topology-utils';
import { storageSet } from '../utils/storage-utils';
import { loadTheme } from '../utils/contrast-utils';
import { activeTopologyOptionsSelector } from '../selectors/topology';
const log = debug('scope:app-actions');
@@ -165,16 +174,16 @@ export function changeTopologyOption(option, value, topologyId) {
// update all request workers with new options
resetUpdateBuffer();
const state = getState();
getTopologies(getActiveTopologyOptions(state), dispatch);
getTopologies(activeTopologyOptionsSelector(state), dispatch);
getNodesDelta(
getCurrentTopologyUrl(state),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
dispatch
);
getNodeDetails(
state.get('topologyUrlsById'),
state.get('currentTopologyId'),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
state.get('nodeDetails'),
dispatch
);
@@ -269,7 +278,7 @@ export function clickNode(nodeId, label, origin) {
getNodeDetails(
state.get('topologyUrlsById'),
state.get('currentTopologyId'),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
state.get('nodeDetails'),
dispatch
);
@@ -296,7 +305,7 @@ export function clickRelative(nodeId, topologyId, label, origin) {
getNodeDetails(
state.get('topologyUrlsById'),
state.get('currentTopologyId'),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
state.get('nodeDetails'),
dispatch
);
@@ -325,7 +334,7 @@ export function clickShowTopologyForNode(topologyId, nodeId) {
const state = getState();
getNodesDelta(
getCurrentTopologyUrl(state),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
dispatch
);
};
@@ -343,7 +352,7 @@ export function clickTopology(topologyId) {
const state = getState();
getNodesDelta(
getCurrentTopologyUrl(state),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
dispatch
);
};
@@ -562,13 +571,13 @@ export function receiveTopologies(topologies) {
const state = getState();
getNodesDelta(
getCurrentTopologyUrl(state),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
dispatch
);
getNodeDetails(
state.get('topologyUrlsById'),
state.get('currentTopologyId'),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
state.get('nodeDetails'),
dispatch
);
@@ -678,16 +687,16 @@ export function route(urlState) {
});
// update all request workers with new options
const state = getState();
getTopologies(getActiveTopologyOptions(state), dispatch);
getTopologies(activeTopologyOptionsSelector(state), dispatch);
getNodesDelta(
getCurrentTopologyUrl(state),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
dispatch
);
getNodeDetails(
state.get('topologyUrlsById'),
state.get('currentTopologyId'),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
state.get('nodeDetails'),
dispatch
);
@@ -720,7 +729,7 @@ export function changeInstance() {
const state = getState();
getNodesDelta(
getCurrentTopologyUrl(state),
getActiveTopologyOptions(state),
activeTopologyOptionsSelector(state),
dispatch,
true // forces websocket teardown and reconnect to new instance
);

View File

@@ -10,7 +10,7 @@ import Logo from '../components/logo';
import NodesChartElements from './nodes-chart-elements';
import { clickBackground, cacheZoomState } from '../actions/app-actions';
import { activeLayoutZoomSelector } from '../selectors/nodes-chart-zoom';
import { activeTopologyZoomCacheKeyPath } from '../utils/topology-utils';
import { activeTopologyZoomCacheKeyPathSelector } from '../selectors/topology';
import { ZOOM_CACHE_DEBOUNCE_INTERVAL } from '../constants/timer';
@@ -146,7 +146,7 @@ class NodesChart extends React.Component {
function mapStateToProps(state) {
return {
layoutZoom: activeLayoutZoomSelector(state),
layoutId: JSON.stringify(activeTopologyZoomCacheKeyPath(state)),
layoutId: JSON.stringify(activeTopologyZoomCacheKeyPathSelector(state)),
selectedNodeId: state.get('selectedNodeId'),
forceRelayout: state.get('forceRelayout'),
};

View File

@@ -21,7 +21,7 @@ import MetricSelector from './metric-selector';
import NetworkSelector from './networks-selector';
import DebugToolbar, { showingDebugToolbar, toggleDebugToolbar } from './debug-toolbar';
import { getRouter, getUrlState } from '../utils/router-utils';
import { getActiveTopologyOptions } from '../utils/topology-utils';
import { activeTopologyOptionsSelector } from '../selectors/topology';
import { availableNetworksSelector } from '../selectors/node-networks';
const BACKSPACE_KEY_CODE = 8;
@@ -143,7 +143,7 @@ class App extends React.Component {
function mapStateToProps(state) {
return {
activeTopologyOptions: getActiveTopologyOptions(state),
activeTopologyOptions: activeTopologyOptionsSelector(state),
gridMode: state.get('gridMode'),
routeSet: state.get('routeSet'),
searchFocused: state.get('searchFocused'),

View File

@@ -1,7 +1,8 @@
import React from 'react';
import { connect } from 'react-redux';
import { getActiveTopologyOptions, getCurrentTopologyOptions } from '../utils/topology-utils';
import { getCurrentTopologyOptions } from '../utils/topology-utils';
import { activeTopologyOptionsSelector } from '../selectors/topology';
import TopologyOptionAction from './topology-option-action';
class TopologyOptions extends React.Component {
@@ -37,7 +38,7 @@ function mapStateToProps(state) {
return {
options: getCurrentTopologyOptions(state),
topologyId: state.get('currentTopologyId'),
activeOptions: getActiveTopologyOptions(state)
activeOptions: activeTopologyOptionsSelector(state)
};
}

View File

@@ -6,8 +6,9 @@ describe('RootReducer', () => {
const reducer = require('../root').default;
const initialState = require('../root').initialState;
const topologyUtils = require('../../utils/topology-utils');
const topologySelectors = require('../../selectors/topology');
// TODO maybe extract those to topology-utils tests?
const getActiveTopologyOptions = topologyUtils.getActiveTopologyOptions;
const activeTopologyOptionsSelector = topologySelectors.activeTopologyOptionsSelector;
const getAdjacentNodes = topologyUtils.getAdjacentNodes;
const isTopologyEmpty = topologyUtils.isTopologyEmpty;
const getUrlState = require('../../utils/router-utils').getUrlState;
@@ -241,28 +242,28 @@ describe('RootReducer', () => {
nextState = reducer(nextState, ClickTopologyAction);
// default options
expect(getActiveTopologyOptions(nextState).has('option1')).toBeTruthy();
expect(getActiveTopologyOptions(nextState).get('option1')).toBe('off');
expect(activeTopologyOptionsSelector(nextState).has('option1')).toBeTruthy();
expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('off');
expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
// turn on
nextState = reducer(nextState, ChangeTopologyOptionAction);
expect(getActiveTopologyOptions(nextState).get('option1')).toBe('on');
expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('on');
expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('on');
// turn off
nextState = reducer(nextState, ChangeTopologyOptionAction2);
expect(getActiveTopologyOptions(nextState).get('option1')).toBe('off');
expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('off');
expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
// sub-topology should retain main topo options
nextState = reducer(nextState, ClickSubTopologyAction);
expect(getActiveTopologyOptions(nextState).get('option1')).toBe('off');
expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('off');
expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
// other topology w/o options dont return options, but keep in app state
nextState = reducer(nextState, ClickTopology2Action);
expect(getActiveTopologyOptions(nextState)).toBeUndefined();
expect(activeTopologyOptionsSelector(nextState)).toBeUndefined();
expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
});
@@ -274,13 +275,13 @@ describe('RootReducer', () => {
let nextState = initialState;
nextState = reducer(nextState, RouteAction);
expect(getActiveTopologyOptions(nextState).get('option1')).toBe('on');
expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('on');
expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('on');
// stay same after topos have been received
nextState = reducer(nextState, ReceiveTopologiesAction);
nextState = reducer(nextState, ClickTopologyAction);
expect(getActiveTopologyOptions(nextState).get('option1')).toBe('on');
expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('on');
expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('on');
});
@@ -293,7 +294,7 @@ describe('RootReducer', () => {
nextState = reducer(nextState, RouteAction);
nextState = reducer(nextState, ReceiveTopologiesAction);
nextState = reducer(nextState, ClickTopologyAction);
expect(getActiveTopologyOptions(nextState).get('option1')).toBe('off');
expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('off');
expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
});

View File

@@ -6,7 +6,10 @@ import { fromJS, is as isDeepEqual, List as makeList, Map as makeMap,
import ActionTypes from '../constants/action-types';
import { EDGE_ID_SEPARATOR } from '../constants/naming';
import { graphExceedsComplexityThreshSelector } from '../selectors/topology';
import {
graphExceedsComplexityThreshSelector,
activeTopologyZoomCacheKeyPathSelector,
} from '../selectors/topology';
import { applyPinnedSearches } from '../utils/search-utils';
import { getNetworkNodes } from '../utils/network-view-utils';
import {
@@ -17,7 +20,6 @@ import {
filterHiddenTopologies,
addTopologyFullname,
getDefaultTopology,
activeTopologyZoomCacheKeyPath,
} from '../utils/topology-utils';
const log = debug('scope:app-store');
@@ -210,7 +212,7 @@ export function rootReducer(state = initialState, action) {
}
case ActionTypes.CACHE_ZOOM_STATE: {
return state.setIn(activeTopologyZoomCacheKeyPath(state), action.zoomState);
return state.setIn(activeTopologyZoomCacheKeyPathSelector(state), action.zoomState);
}
case ActionTypes.CLEAR_CONTROL_ERROR: {
@@ -239,7 +241,7 @@ export function rootReducer(state = initialState, action) {
case ActionTypes.CLICK_FORCE_RELAYOUT: {
if (action.forceRelayout) {
// Reset the zoom cache when forcing relayout.
state = state.deleteIn(activeTopologyZoomCacheKeyPath(state));
state = state.deleteIn(activeTopologyZoomCacheKeyPathSelector(state));
}
return state.set('forceRelayout', action.forceRelayout);
}

View File

@@ -3,9 +3,9 @@ import { createSelector, createStructuredSelector } from 'reselect';
import { Map as makeMap } from 'immutable';
import timely from 'timely';
import { getActiveTopologyOptions } from '../utils/topology-utils';
import { initEdgesFromNodes, collapseMultiEdges } from '../utils/layouter-utils';
import { viewportWidthSelector, viewportHeightSelector } from './canvas-viewport';
import { activeTopologyOptionsSelector } from './topology';
import { shownNodesSelector } from './node-filters';
import { doLayout } from '../charts/nodes-layout';
@@ -15,7 +15,7 @@ const log = debug('scope:nodes-chart');
const layoutOptionsSelector = createStructuredSelector({
forceRelayout: state => state.get('forceRelayout'),
topologyId: state => state.get('currentTopologyId'),
topologyOptions: getActiveTopologyOptions,
topologyOptions: activeTopologyOptionsSelector,
height: viewportHeightSelector,
width: viewportWidthSelector,
});

View File

@@ -52,7 +52,7 @@ const focusedNodesIdsSelector = createSelector(
],
(selectedNodeId, nodes) => {
if (!selectedNodeId || nodes.isEmpty()) {
return makeList();
return [];
}
// The selected node always goes in focus.

View File

@@ -2,7 +2,7 @@ import { createSelector } from 'reselect';
import { Map as makeMap } from 'immutable';
import { CANVAS_MARGINS, NODE_BASE_SIZE } from '../constants/styles';
import { activeTopologyZoomCacheKeyPath } from '../utils/topology-utils';
import { activeTopologyZoomCacheKeyPathSelector } from './topology';
import { viewportWidthSelector, viewportHeightSelector } from './canvas-viewport';
import { graphNodesSelector } from './nodes-chart-graph';
@@ -48,7 +48,7 @@ const defaultZoomSelector = createSelector(
const activeLayoutCachedZoomSelector = createSelector(
[
state => state.get('zoomCache'),
activeTopologyZoomCacheKeyPath,
activeTopologyZoomCacheKeyPathSelector,
],
(zoomCache, keyPath) => zoomCache.getIn(keyPath.slice(1))
);

View File

@@ -11,3 +11,23 @@ export const graphExceedsComplexityThreshSelector = createSelector(
],
(nodeCount, edgeCount) => (nodeCount + (2 * edgeCount)) > 1000
);
// Options for current topology, sub-topologies share options with parent
export const activeTopologyOptionsSelector = createSelector(
[
state => state.getIn(['currentTopology', 'parentId']),
state => state.get('currentTopologyId'),
state => state.get('topologyOptions'),
],
(parentTopologyId, currentTopologyId, topologyOptions) => (
topologyOptions.get(parentTopologyId || currentTopologyId)
)
);
export const activeTopologyZoomCacheKeyPathSelector = createSelector(
[
state => state.get('currentTopologyId'),
activeTopologyOptionsSelector,
],
(topologyId, topologyOptions) => ['zoomCache', topologyId, JSON.stringify(topologyOptions)]
);

View File

@@ -127,15 +127,6 @@ export function filterHiddenTopologies(topologies) {
t.stats.filtered_nodes > 0));
}
export function getActiveTopologyOptions(state) {
// options for current topology, sub-topologies share options with parent
const parentId = state.getIn(['currentTopology', 'parentId']);
if (parentId) {
return state.getIn(['topologyOptions', parentId]);
}
return state.getIn(['topologyOptions', state.get('currentTopologyId')]);
}
export function getCurrentTopologyOptions(state) {
return state.getIn(['currentTopology', 'options']);
}
@@ -173,9 +164,3 @@ export function hasSelectedNode(state) {
export function getCurrentTopologyUrl(state) {
return state.getIn(['currentTopology', 'url']);
}
export function activeTopologyZoomCacheKeyPath(state) {
const topologyId = state.get('currentTopologyId');
const topologyOptions = JSON.stringify(getActiveTopologyOptions(state));
return ['zoomCache', topologyId, topologyOptions];
}