Change the export logic for the Inspector

Summary:
This enables the feature which showed the theme information in the layout plugin. It was disabled due to the OOM which occurred while exporting flipper trace. The OOM happened when we tried to serialise the whole layout hierarchy and the amount of info added per node by the theme info was quite heavy. Thus removing it solved the OOM problem at that point, but its not the correct solution.

The problem is that each node has too much information and sending it at one stretch is very heavy and causes OOM. So instead of sending it at one stretch, I have broken it into multiple calls at each level of the tree. This no longer causes OOM and we will be able to show theme information too.

Also for iOS we don't have AXNodes call or AXRoot feature implemented, so anyway I had to put a check to not make those calls, so instead I have kept the feature of fetching all nodes on the iOS instead, as there has been no problem on the iOS side with regards to the OOM. But I am indifferent, the same logic will work for iOS too, it might increase the time to export.

issue discussed [here](https://fb.workplace.com/groups/flippersupport/permalink/854729375007722/)

Reviewed By: jknoxville

Differential Revision: D21136057

fbshipit-source-id: becd237a6d53c50af082597f2e8ed790c25cb966
This commit is contained in:
Pritesh Nandgaonkar
2020-04-23 04:29:24 -07:00
committed by Facebook GitHub Bot
parent 9d0d900b05
commit 17ccdeaf6f
2 changed files with 88 additions and 23 deletions

View File

@@ -51,26 +51,23 @@ public final class ContextDescriptorUtils {
|| sThemeImplThemeKeyField == null || sThemeImplThemeKeyField == null
|| sThemeKeyResIdField == null || sThemeKeyResIdField == null
|| sThemeImplAssetManagerField == null) { || sThemeImplAssetManagerField == null) {
sThemeImplField = theme.getClass().getDeclaredField("mThemeImpl");
sThemeImplField.setAccessible(true);
// Early exiting as this bit of code causes too much of the metadata to be created and themeImpl = sThemeImplField.get(theme);
// ultimately leads to out of memory exception. Reference D20441839 sThemeImplThemeKeyField = themeImpl.getClass().getDeclaredField("mKey");
// sThemeImplField = theme.getClass().getDeclaredField("mThemeImpl"); sThemeImplThemeKeyField.setAccessible(true);
// sThemeImplField.setAccessible(true);
// themeImpl = sThemeImplField.get(theme); sThemeImplAssetManagerField = themeImpl.getClass().getDeclaredField("mAssets");
// sThemeImplThemeKeyField = themeImpl.getClass().getDeclaredField("mKey"); sThemeImplAssetManagerField.setAccessible(true);
// sThemeImplThemeKeyField.setAccessible(true);
// sThemeImplAssetManagerField = themeImpl.getClass().getDeclaredField("mAssets"); sAssetManagerGetStyleAttributesMethod =
// sThemeImplAssetManagerField.setAccessible(true); assetManager.getClass().getDeclaredMethod("getStyleAttributes", int.class);
sAssetManagerGetStyleAttributesMethod.setAccessible(true);
// sAssetManagerGetStyleAttributesMethod = themeKey = sThemeImplThemeKeyField.get(themeImpl);
// assetManager.getClass().getDeclaredMethod("getStyleAttributes", int.class); sThemeKeyResIdField = themeKey.getClass().getDeclaredField("mResId");
// sAssetManagerGetStyleAttributesMethod.setAccessible(true); sThemeKeyResIdField.setAccessible(true);
// themeKey = sThemeImplThemeKeyField.get(themeImpl);
// sThemeKeyResIdField = themeKey.getClass().getDeclaredField("mResId");
// sThemeKeyResIdField.setAccessible(true);
return builderMap; return builderMap;

View File

@@ -77,6 +77,9 @@ const FlipperADButton = styled(Button)({
margin: 10, margin: 10,
}); });
type ClientGetNodesCalls = 'getNodes' | 'getAXNodes';
type ClientMethodCalls = 'getRoot' | 'getAXRoot' | ClientGetNodesCalls;
export default class Layout extends FlipperPlugin<State, any, PersistedState> { export default class Layout extends FlipperPlugin<State, any, PersistedState> {
FlipperADBar() { FlipperADBar() {
return ( return (
@@ -98,19 +101,84 @@ export default class Layout extends FlipperPlugin<State, any, PersistedState> {
} }
static exportPersistedState = async ( static exportPersistedState = async (
callClient: ( callClient: (method: ClientMethodCalls, params?: any) => Promise<any>,
method: 'getAllNodes',
) => Promise<{
allNodes: PersistedState;
}>,
persistedState: PersistedState | undefined, persistedState: PersistedState | undefined,
store: ReduxState | undefined, store: ReduxState | undefined,
_idler?: Idler | undefined,
statusUpdate?: (msg: string) => void,
supportsMethod?: (method: ClientMethodCalls) => Promise<boolean>,
): Promise<PersistedState | undefined> => { ): Promise<PersistedState | undefined> => {
if (!store) { if (!store) {
return persistedState; return persistedState;
} }
const {allNodes} = await callClient('getAllNodes'); statusUpdate && statusUpdate('Fetching Root Node...');
return allNodes; // We need not check the if the client supports `getRoot` as if it should and if it doesn't we will get a suppressed notification in Flipper and things will still export, but we will get an error surfaced.
const rootElement: Element | null = await callClient('getRoot');
const rootAXElement: Element | null =
supportsMethod && (await supportsMethod('getAXRoot')) // getAXRoot only relevant for Android
? await callClient('getAXRoot')
: null;
const elements: ElementMap = {};
if (rootElement) {
statusUpdate && statusUpdate('Fetching Child Nodes...');
await Layout.getAllNodes(
rootElement,
elements,
callClient,
'getNodes',
supportsMethod,
);
}
const AXelements: ElementMap = {};
if (rootAXElement) {
statusUpdate && statusUpdate('Fetching Child AX Nodes...');
await Layout.getAllNodes(
rootAXElement,
AXelements,
callClient,
'getAXNodes',
supportsMethod,
);
}
statusUpdate && statusUpdate('Finished Fetching Child Nodes...');
return {
rootElement: rootElement != undefined ? rootElement.id : null,
rootAXElement: rootAXElement != undefined ? rootAXElement.id : null,
elements,
AXelements,
};
};
static getAllNodes = async (
root: Element,
nodeMap: ElementMap,
callClient: (method: ClientGetNodesCalls, params?: any) => Promise<any>,
method: ClientGetNodesCalls,
supportsMethod?: (method: ClientGetNodesCalls) => Promise<boolean>,
): Promise<void> => {
nodeMap[root.id] = root;
if (
root.children.length > 0 &&
supportsMethod &&
(await supportsMethod(method))
) {
await callClient(method, {ids: root.children}).then(
async ({elements}: {elements: Array<Element>}) => {
await Promise.all(
elements.map(async (elem) => {
await Layout.getAllNodes(
elem,
nodeMap,
callClient,
method,
supportsMethod,
);
}),
);
},
);
}
}; };
static serializePersistedState: ( static serializePersistedState: (