Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lens] Add lens editor performance metrics #163089

Merged
merged 14 commits into from
Aug 7, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
const onRenderChange = useCallback(
(isRendered: boolean = true) => {
if (isRendered) {
renderComplete();
// this requestAnimationFrame call is a temporary fix for https://github.com/elastic/elastic-charts/issues/2124
window.requestAnimationFrame(() => {
renderComplete();
});
}
},
[renderComplete]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,10 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
const onRenderChange = useCallback(
(isRendered: boolean = true) => {
if (isRendered) {
renderComplete();
// this requestAnimationFrame call is a temporary fix for https://github.com/elastic/elastic-charts/issues/2124
window.requestAnimationFrame(() => {
renderComplete();
});
}
},
[renderComplete]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ export const MetricVis = ({
const onRenderChange = useCallback<RenderChangeListener>(
(isRendered) => {
if (isRendered) {
renderComplete();
// this requestAnimationFrame call is a temporary fix for https://github.com/elastic/elastic-charts/issues/2124
window.requestAnimationFrame(() => {
renderComplete();
});
}
},
[renderComplete]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,11 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
const onRenderChange = useCallback(
(isRendered: boolean = true) => {
if (isRendered) {
props.renderComplete();
setChartIsLoaded(true);
// this requestAnimationFrame call is a temporary fix for https://github.com/elastic/elastic-charts/issues/2124
window.requestAnimationFrame(() => {
props.renderComplete();
setChartIsLoaded(true);
});
}
},
[props]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ export const TagCloudChart = ({
const onRenderChange = useCallback<RenderChangeListener>(
(isRendered) => {
if (isRendered) {
renderComplete();
// this requestAnimationFrame call is a temporary fix for https://github.com/elastic/elastic-charts/issues/2124
window.requestAnimationFrame(() => {
renderComplete();
});
}
},
[renderComplete]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,10 @@ export function XYChart({
const onRenderChange = useCallback(
(isRendered: boolean = true) => {
if (isRendered) {
renderComplete();
// this requestAnimationFrame call is a temporary fix for https://github.com/elastic/elastic-charts/issues/2124
window.requestAnimationFrame(() => {
renderComplete();
});
}
},
[renderComplete]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@
flex-direction: column;
align-items: center;
justify-content: center;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ import {
selectFrameDatasourceAPI,
} from '../../state_management';
import { filterAndSortUserMessages } from '../../app_plugin/get_application_user_messages';

const MAX_SUGGESTIONS_DISPLAYED = 5;
const LOCAL_STORAGE_SUGGESTIONS_PANEL = 'LENS_SUGGESTIONS_PANEL_HIDDEN';

Expand Down Expand Up @@ -108,11 +107,13 @@ const PreviewRenderer = ({
ExpressionRendererComponent,
expression,
hasError,
onRender,
}: {
withLabel: boolean;
expression: string | null | undefined;
ExpressionRendererComponent: ReactExpressionRendererType;
hasError: boolean;
onRender: () => void;
}) => {
const onErrorMessage = (
<div className="lnsSuggestionPanel__suggestionIcon">
Expand Down Expand Up @@ -143,6 +144,7 @@ const PreviewRenderer = ({
padding="s"
renderMode="preview"
expression={expression}
onRender$={onRender}
debounce={2000}
renderError={() => {
return onErrorMessage;
Expand All @@ -159,6 +161,7 @@ const SuggestionPreview = ({
selected,
onSelect,
showTitleAsLabel,
onRender,
}: {
onSelect: () => void;
preview: {
Expand All @@ -170,6 +173,7 @@ const SuggestionPreview = ({
ExpressionRenderer: ReactExpressionRendererType;
selected: boolean;
showTitleAsLabel?: boolean;
onRender: () => void;
}) => {
return (
<EuiToolTip content={preview.title}>
Expand All @@ -194,6 +198,7 @@ const SuggestionPreview = ({
expression={preview.expression && toExpression(preview.expression)}
withLabel={Boolean(showTitleAsLabel)}
hasError={Boolean(preview.error)}
onRender={onRender}
/>
) : (
<span className="lnsSuggestionPanel__suggestionIcon">
Expand Down Expand Up @@ -358,20 +363,36 @@ export function SuggestionPanel({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [existsStagedPreview]);

if (!activeDatasourceId) {
return null;
}

if (suggestions.length === 0) {
return null;
}
const startTime = useRef<number>(0);
const initialRenderComplete = useRef<boolean>(false);
const suggestionsRendered = useRef<boolean[]>([]);
const totalSuggestions = suggestions.length + 1;

const onSuggestionRender = useCallback((suggestionIndex: number) => {
suggestionsRendered.current[suggestionIndex] = true;
if (initialRenderComplete.current === false && suggestionsRendered.current.every(Boolean)) {
initialRenderComplete.current = true;
// console.log(
// 'time to fetch data and perform initial render for all suggestions',
// performance.now() - startTime.current
// );
Comment on lines +375 to +378
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uncomment for review

}
}, []);

function rollbackToCurrentVisualization() {
const rollbackToCurrentVisualization = useCallback(() => {
if (lastSelectedSuggestion !== -1) {
setLastSelectedSuggestion(-1);
dispatchLens(rollbackSuggestion());
dispatchLens(applyChanges());
}
}, [dispatchLens, lastSelectedSuggestion]);

if (!activeDatasourceId) {
return null;
}

if (suggestions.length === 0) {
return null;
}

const renderApplyChangesPrompt = () => (
Expand Down Expand Up @@ -400,52 +421,58 @@ export function SuggestionPanel({
</EuiPanel>
);

const renderSuggestionsUI = () => (
<>
{currentVisualization.activeId && !hideSuggestions && (
<SuggestionPreview
preview={{
error: currentStateError,
expression: currentStateExpression,
icon:
visualizationMap[currentVisualization.activeId].getDescription(
currentVisualization.state
).icon || 'empty',
title: i18n.translate('xpack.lens.suggestions.currentVisLabel', {
defaultMessage: 'Current visualization',
}),
}}
ExpressionRenderer={AutoRefreshExpressionRenderer}
onSelect={rollbackToCurrentVisualization}
selected={lastSelectedSuggestion === -1}
showTitleAsLabel
/>
)}
{!hideSuggestions &&
suggestions.map((suggestion, index) => {
return (
<SuggestionPreview
preview={{
expression: suggestion.previewExpression,
icon: suggestion.previewIcon,
title: suggestion.title,
}}
ExpressionRenderer={AutoRefreshExpressionRenderer}
key={index}
onSelect={() => {
if (lastSelectedSuggestion === index) {
rollbackToCurrentVisualization();
} else {
setLastSelectedSuggestion(index);
switchToSuggestion(dispatchLens, suggestion, { applyImmediately: true });
}
}}
selected={index === lastSelectedSuggestion}
/>
);
})}
</>
);
const renderSuggestionsUI = () => {
suggestionsRendered.current = new Array(totalSuggestions).fill(false);
startTime.current = performance.now();
return (
<>
{currentVisualization.activeId && !hideSuggestions && (
<SuggestionPreview
preview={{
error: currentStateError,
expression: currentStateExpression,
icon:
visualizationMap[currentVisualization.activeId].getDescription(
currentVisualization.state
).icon || 'empty',
title: i18n.translate('xpack.lens.suggestions.currentVisLabel', {
defaultMessage: 'Current visualization',
}),
}}
ExpressionRenderer={AutoRefreshExpressionRenderer}
onSelect={rollbackToCurrentVisualization}
selected={lastSelectedSuggestion === -1}
showTitleAsLabel
onRender={() => onSuggestionRender(0)}
/>
)}
{!hideSuggestions &&
suggestions.map((suggestion, index) => {
return (
<SuggestionPreview
preview={{
expression: suggestion.previewExpression,
icon: suggestion.previewIcon,
title: suggestion.title,
}}
ExpressionRenderer={AutoRefreshExpressionRenderer}
key={index}
onSelect={() => {
if (lastSelectedSuggestion === index) {
rollbackToCurrentVisualization();
} else {
setLastSelectedSuggestion(index);
switchToSuggestion(dispatchLens, suggestion, { applyImmediately: true });
}
}}
selected={index === lastSelectedSuggestion}
onRender={() => onSuggestionRender(index + 1)}
/>
);
})}
</>
);
};

return (
<div className="lnsSuggestionPanel">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ export const WorkspacePanel = React.memo(function WorkspacePanel(props: Workspac
);
});

const log = (...messages: Array<string | number>) => {
// console.log(...messages);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uncomment for review

};

// Exported for testing purposes only.
export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
framePublicAPI,
Expand Down Expand Up @@ -170,7 +174,10 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
errors: [],
});

const initialRenderComplete = useRef<boolean>();
const initialVisualizationRenderComplete = useRef<boolean>(false);

// NOTE: This does not reflect the actual visualization render
const initialWorkspaceRenderComplete = useRef<boolean>();

const renderDeps = useRef<{
datasourceMap: DatasourceMap;
Expand All @@ -192,8 +199,20 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
dataViews,
};

// NOTE: initialRenderTime is only set once when the component mounts
const initialRenderTime = useRef<number>(performance.now());
const dataReceivedTime = useRef<number>(0);

const onRender$ = useCallback(() => {
if (renderDeps.current) {
if (!initialVisualizationRenderComplete.current) {
initialVisualizationRenderComplete.current = true;
// NOTE: this metric is only repored for an initial editor load of a pre-existing visualization
log(
'initial visualization took to render after data received',
performance.now() - dataReceivedTime.current
);
}
const datasourceEvents = Object.values(renderDeps.current.datasourceMap).reduce<string[]>(
(acc, datasource) => {
if (!renderDeps.current!.datasourceStates[datasource.id]) return [];
Expand Down Expand Up @@ -232,6 +251,12 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
const onData$ = useCallback(
(_data: unknown, adapters?: Partial<DefaultInspectorAdapters>) => {
if (renderDeps.current) {
dataReceivedTime.current = performance.now();
if (!initialVisualizationRenderComplete.current) {
// NOTE: this metric is only repored for an initial editor load of a pre-existing visualization
log('initial data took to arrive', dataReceivedTime.current - initialRenderTime.current);
}

const [defaultLayerId] = Object.keys(renderDeps.current.datasourceLayers);
const datasource = Object.values(renderDeps.current.datasourceMap)[0];
const datasourceState = Object.values(renderDeps.current.datasourceStates)[0].state;
Expand Down Expand Up @@ -276,7 +301,8 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
[addUserMessages, dispatchLens, plugins.data.search]
);

const shouldApplyExpression = autoApplyEnabled || !initialRenderComplete.current || triggerApply;
const shouldApplyExpression =
autoApplyEnabled || !initialWorkspaceRenderComplete.current || triggerApply;
const activeVisualization = visualization.activeId
? visualizationMap[visualization.activeId]
: null;
Expand Down Expand Up @@ -389,9 +415,9 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
// null signals an empty workspace which should count as an initial render
if (
(expressionExists || localState.expressionToRender === null) &&
!initialRenderComplete.current
!initialWorkspaceRenderComplete.current
) {
initialRenderComplete.current = true;
initialWorkspaceRenderComplete.current = true;
}
}, [expressionExists, localState.expressionToRender]);

Expand Down Expand Up @@ -687,6 +713,12 @@ export const VisualizationWrapper = ({
// Used for reporting
const { isRenderComplete, hasDynamicError, setIsRenderComplete, setDynamicError, nodeRef } =
useReportingState(errors);

const onRenderHandler = useCallback(() => {
setIsRenderComplete(true);
onRender$();
}, [setIsRenderComplete, onRender$]);
Comment on lines +724 to +727
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important to memoize—multiple subscriptions in useExpressionRenderer hook were being created which led to multiple calls to onRender$.


const searchContext: ExecutionContextSearch = useMemo(
() => ({
query: context.query,
Expand Down Expand Up @@ -782,10 +814,7 @@ export const VisualizationWrapper = ({
onEvent={onEvent}
hasCompatibleActions={hasCompatibleActions}
onData$={onData$}
onRender$={() => {
setIsRenderComplete(true);
onRender$();
}}
onRender$={onRenderHandler}
inspectorAdapters={lensInspector.adapters}
executionContext={executionContext}
renderMode="edit"
Expand Down