Yarn workspaces

Summary:
1) moved "sonar/desktop/src" to "sonar/desktop/app/src", so "app" is now a separate package containing the core Flipper app code
2) Configured yarn workspaces with the root in "sonar/desktop": app, static, pkg, doctor, headless-tests. Plugins are not included for now, I plan to do this later.

Reviewed By: jknoxville

Differential Revision: D20535782

fbshipit-source-id: 600b2301960f37c7d72166e0d04eba462bec9fc1
This commit is contained in:
Anton Nikolaev
2020-03-20 13:31:37 -07:00
committed by Facebook GitHub Bot
parent 676d7bbd24
commit 863f89351e
340 changed files with 1635 additions and 294 deletions

View File

@@ -0,0 +1,283 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {PersistedStateReducer, FlipperDevicePlugin} from '../plugin';
import {State, MiddlewareAPI} from '../reducers/index';
import {setPluginState} from '../reducers/pluginStates';
import {flipperRecorderAddEvent} from './pluginStateRecorder';
import {
clearMessageQueue,
queueMessage,
Message,
} from '../reducers/pluginMessageQueue';
import {Idler, BaseIdler} from './Idler';
import {pluginIsStarred, getSelectedPluginKey} from '../reducers/connections';
import {deconstructPluginKey} from './clientUtils';
const MAX_BACKGROUND_TASK_TIME = 25;
type StatEntry = {
cpuTimeTotal: number; // Total time spend in persisted Reducer
cpuTimeDelta: number; // Time spend since previous tracking tick
messageCountTotal: number; // amount of message received for this plugin
messageCountDelta: number; // amout of messages received since previous tracking tick
maxTime: number; // maximum time spend in a single reducer call
};
const pluginBackgroundStats = new Map<string, StatEntry>();
export function resetPluginBackgroundStatsDelta() {
pluginBackgroundStats.forEach(stat => {
stat.cpuTimeDelta = 0;
stat.messageCountDelta = 0;
});
}
export function getPluginBackgroundStats(): {
cpuTime: number; // amount of ms cpu used since the last stats (typically every minute)
byPlugin: {[plugin: string]: StatEntry};
} {
let cpuTime: number = 0;
const byPlugin = Array.from(pluginBackgroundStats.entries()).reduce(
(aggregated, [pluginName, data]) => {
cpuTime += data.cpuTimeDelta;
aggregated[pluginName] = data;
return aggregated;
},
{} as {[plugin: string]: StatEntry},
);
return {
cpuTime,
byPlugin,
};
}
if (window) {
// @ts-ignore
window.flipperPrintPluginBackgroundStats = () => {
console.table(
Array.from(pluginBackgroundStats.entries()).map(
([
plugin,
{
cpuTimeDelta,
cpuTimeTotal,
messageCountDelta,
messageCountTotal,
maxTime,
},
]) => ({
plugin,
cpuTimeTotal,
messageCountTotal,
cpuTimeDelta,
messageCountDelta,
maxTime,
}),
),
);
};
}
function addBackgroundStat(plugin: string, cpuTime: number) {
if (!pluginBackgroundStats.has(plugin)) {
pluginBackgroundStats.set(plugin, {
cpuTimeDelta: 0,
cpuTimeTotal: 0,
messageCountDelta: 0,
messageCountTotal: 0,
maxTime: 0,
});
}
const stat = pluginBackgroundStats.get(plugin)!;
stat.cpuTimeDelta += cpuTime;
stat.cpuTimeTotal += cpuTime;
stat.messageCountDelta += 1;
stat.messageCountTotal += 1;
stat.maxTime = Math.max(stat.maxTime, cpuTime);
if (cpuTime > MAX_BACKGROUND_TASK_TIME) {
console.warn(
`Plugin ${plugin} took too much time while doing background: ${cpuTime}ms. Handling background messages should take less than ${MAX_BACKGROUND_TASK_TIME}ms.`,
);
}
}
function processMessage(
state: State,
pluginKey: string,
plugin: {
id: string;
persistedStateReducer: PersistedStateReducer | null;
},
message: {method: string; params?: any},
): State {
const statName = `${plugin.id}.${message.method}`;
const reducerStartTime = Date.now();
flipperRecorderAddEvent(pluginKey, message.method, message.params);
try {
const newPluginState = plugin.persistedStateReducer!(
state,
message.method,
message.params,
);
addBackgroundStat(statName, Date.now() - reducerStartTime);
return newPluginState;
} catch (e) {
console.error(`Failed to process event for plugin ${plugin.id}`, e);
return state;
}
}
export function processMessageImmediately(
store: MiddlewareAPI,
pluginKey: string,
plugin: {
defaultPersistedState: any;
id: string;
persistedStateReducer: PersistedStateReducer | null;
},
message: {method: string; params?: any},
) {
const persistedState = getCurrentPluginState(store, plugin, pluginKey);
const newPluginState = processMessage(
persistedState,
pluginKey,
plugin,
message,
);
if (persistedState !== newPluginState) {
store.dispatch(
setPluginState({
pluginKey,
state: newPluginState,
}),
);
}
}
export function processMessageLater(
store: MiddlewareAPI,
pluginKey: string,
plugin: {
defaultPersistedState: any;
id: string;
persistedStateReducer: PersistedStateReducer | null;
maxQueueSize?: number;
},
message: {method: string; params?: any},
) {
const isSelected =
pluginKey === getSelectedPluginKey(store.getState().connections);
switch (true) {
case plugin.id === 'Navigation': // Navigation events are always processed, to make sure the navbar stays up to date
case isSelected && getPendingMessages(store, pluginKey).length === 0:
processMessageImmediately(store, pluginKey, plugin, message);
break;
case isSelected:
case plugin instanceof FlipperDevicePlugin:
case pluginIsStarred(
store.getState().connections.userStarredPlugins,
deconstructPluginKey(pluginKey).client,
plugin.id,
):
store.dispatch(
queueMessage(
pluginKey,
message.method,
message.params,
plugin.maxQueueSize,
),
);
break;
// In all other cases, messages will be dropped...
}
}
export async function processMessageQueue(
plugin: {
defaultPersistedState: any;
id: string;
persistedStateReducer: PersistedStateReducer | null;
},
pluginKey: string,
store: MiddlewareAPI,
progressCallback?: (progress: {current: number; total: number}) => void,
idler: BaseIdler = new Idler(),
): Promise<boolean> {
if (!plugin.persistedStateReducer) {
return true;
}
const total = getPendingMessages(store, pluginKey).length;
let progress = 0;
do {
const messages = getPendingMessages(store, pluginKey);
if (!messages.length) {
break;
}
// there are messages to process! lets do so until we have to idle
const persistedState = getCurrentPluginState(store, plugin, pluginKey);
let offset = 0;
let newPluginState = persistedState;
do {
newPluginState = processMessage(
newPluginState,
pluginKey,
plugin,
messages[offset],
);
offset++;
progress++;
progressCallback?.({
total: Math.max(total, progress),
current: progress,
});
} while (offset < messages.length && !idler.shouldIdle());
// save progress
// by writing progress away first and then idling, we make sure this logic is
// resistent to kicking off this process twice; grabbing, processing messages, saving state is done synchronosly
// until the idler has to break
store.dispatch(clearMessageQueue(pluginKey, offset));
if (newPluginState !== persistedState) {
store.dispatch(
setPluginState({
pluginKey,
state: newPluginState,
}),
);
}
if (idler.isCancelled()) {
return false;
}
await idler.idle();
// new messages might have arrived, so keep looping
} while (getPendingMessages(store, pluginKey).length);
return true;
}
function getPendingMessages(
store: MiddlewareAPI,
pluginKey: string,
): Message[] {
return store.getState().pluginMessageQueue[pluginKey] || [];
}
function getCurrentPluginState(
store: MiddlewareAPI,
plugin: {defaultPersistedState: any},
pluginKey: string,
) {
// possible optimization: don't spread default state here by put proper default state when initializing clients
return {
...plugin.defaultPersistedState,
...store.getState().pluginStates[pluginKey],
};
}