Include information about selected device, app and plugin into analytics events and error reports
Summary: This diff generalises computation of the currently selected plugin, app, device etc. and adds this information to all the analytics events and error reports. Slicing of events by os, device, app or selected plugin can be very useful. This is especially true for errors which often affects only certain types of devices, e.g. android only or physical devices only. Having such information can help to narrow down such issues. Reviewed By: passy Differential Revision: D28511441 fbshipit-source-id: ed9dc57927c70ed8cc6fe093e21604eae54c2f60
This commit is contained in:
committed by
Facebook GitHub Bot
parent
b378d8b946
commit
25ae4a0535
@@ -8,16 +8,13 @@
|
||||
*/
|
||||
|
||||
import {computeUsageSummary} from '../tracking';
|
||||
import {SelectedPluginData, State} from '../../reducers/usageTracking';
|
||||
import BaseDevice from '../../devices/BaseDevice';
|
||||
import {getPluginKey} from '../../utils/pluginUtils';
|
||||
import type {State} from '../../reducers/usageTracking';
|
||||
import type {SelectionInfo} from '../../utils/info';
|
||||
|
||||
const device = new BaseDevice('serial', 'emulator', 'test device', 'iOS');
|
||||
const layoutPluginKey = getPluginKey('Facebook', device, 'Layout');
|
||||
const networkPluginKey = getPluginKey('Facebook', device, 'Network');
|
||||
const databasesPluginKey = getPluginKey('Facebook', device, 'Databases');
|
||||
const pluginData: SelectedPluginData = {
|
||||
const layoutSelection: SelectionInfo = {
|
||||
plugin: 'Layout',
|
||||
pluginName: 'flipper-plugin-layout',
|
||||
pluginVersion: '0.0.0',
|
||||
app: 'Facebook',
|
||||
device: 'test device',
|
||||
deviceName: 'test device',
|
||||
@@ -26,8 +23,12 @@ const pluginData: SelectedPluginData = {
|
||||
os: 'iOS',
|
||||
archived: false,
|
||||
};
|
||||
const pluginData2 = {...pluginData, plugin: 'Network'};
|
||||
const pluginData3 = {...pluginData, plugin: 'Databases'};
|
||||
const networkSelection = {...layoutSelection, plugin: 'Network'};
|
||||
const databasesSelection = {...layoutSelection, plugin: 'Databases'};
|
||||
|
||||
const layoutPluginKey = JSON.stringify(layoutSelection);
|
||||
const networkPluginKey = JSON.stringify(networkSelection);
|
||||
const databasesPluginKey = JSON.stringify(databasesSelection);
|
||||
|
||||
test('Never focused', () => {
|
||||
const state: State = {
|
||||
@@ -96,10 +97,10 @@ test('Always focused plugin change', () => {
|
||||
timeline: [
|
||||
{type: 'TIMELINE_START', time: 100, isFocused: true},
|
||||
{
|
||||
type: 'PLUGIN_SELECTED',
|
||||
type: 'SELECTION_CHANGED',
|
||||
time: 150,
|
||||
pluginKey: layoutPluginKey,
|
||||
pluginData,
|
||||
selectionKey: layoutPluginKey,
|
||||
selection: layoutSelection,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -113,10 +114,10 @@ test('Focused then plugin change then unfocusd', () => {
|
||||
timeline: [
|
||||
{type: 'TIMELINE_START', time: 100, isFocused: true},
|
||||
{
|
||||
type: 'PLUGIN_SELECTED',
|
||||
type: 'SELECTION_CHANGED',
|
||||
time: 150,
|
||||
pluginKey: layoutPluginKey,
|
||||
pluginData,
|
||||
selectionKey: layoutPluginKey,
|
||||
selection: layoutSelection,
|
||||
},
|
||||
{type: 'WINDOW_FOCUS_CHANGE', time: 350, isFocused: false},
|
||||
],
|
||||
@@ -131,28 +132,28 @@ test('Multiple plugin changes', () => {
|
||||
timeline: [
|
||||
{type: 'TIMELINE_START', time: 100, isFocused: true},
|
||||
{
|
||||
type: 'PLUGIN_SELECTED',
|
||||
type: 'SELECTION_CHANGED',
|
||||
time: 150,
|
||||
pluginKey: layoutPluginKey,
|
||||
pluginData,
|
||||
selectionKey: layoutPluginKey,
|
||||
selection: layoutSelection,
|
||||
},
|
||||
{
|
||||
type: 'PLUGIN_SELECTED',
|
||||
type: 'SELECTION_CHANGED',
|
||||
time: 350,
|
||||
pluginKey: networkPluginKey,
|
||||
pluginData: pluginData2,
|
||||
selectionKey: networkPluginKey,
|
||||
selection: networkSelection,
|
||||
},
|
||||
{
|
||||
type: 'PLUGIN_SELECTED',
|
||||
type: 'SELECTION_CHANGED',
|
||||
time: 650,
|
||||
pluginKey: layoutPluginKey,
|
||||
pluginData,
|
||||
selectionKey: layoutPluginKey,
|
||||
selection: layoutSelection,
|
||||
},
|
||||
{
|
||||
type: 'PLUGIN_SELECTED',
|
||||
type: 'SELECTION_CHANGED',
|
||||
time: 1050,
|
||||
pluginKey: databasesPluginKey,
|
||||
pluginData: pluginData3,
|
||||
selectionKey: databasesPluginKey,
|
||||
selection: databasesSelection,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ import pluginManager from './pluginManager';
|
||||
import reactNative from './reactNative';
|
||||
import pluginMarketplace from './fb-stubs/pluginMarketplace';
|
||||
import pluginDownloads from './pluginDownloads';
|
||||
import info from '../utils/info';
|
||||
|
||||
import {Logger} from '../fb-interfaces/Logger';
|
||||
import {Store} from '../reducers/index';
|
||||
@@ -49,6 +50,7 @@ export default function (store: Store, logger: Logger): () => Promise<void> {
|
||||
reactNative,
|
||||
pluginMarketplace,
|
||||
pluginDownloads,
|
||||
info,
|
||||
].filter(notNull);
|
||||
const globalCleanup = dispatchers
|
||||
.map((dispatcher) => dispatcher(store, logger))
|
||||
|
||||
@@ -22,19 +22,21 @@ import {
|
||||
clearTimeline,
|
||||
TrackingEvent,
|
||||
State as UsageTrackingState,
|
||||
SelectedPluginData,
|
||||
selectionChanged,
|
||||
} from '../reducers/usageTracking';
|
||||
import produce from 'immer';
|
||||
import BaseDevice from '../devices/BaseDevice';
|
||||
import {deconstructClientId, deconstructPluginKey} from '../utils/clientUtils';
|
||||
import {deconstructClientId} from '../utils/clientUtils';
|
||||
import {getCPUUsage} from 'process';
|
||||
import {getPluginKey} from '../utils/pluginUtils';
|
||||
import {sideEffect} from '../utils/sideEffect';
|
||||
import {getSelectionInfo} from '../utils/info';
|
||||
import type {SelectionInfo} from '../utils/info';
|
||||
|
||||
const TIME_SPENT_EVENT = 'time-spent';
|
||||
|
||||
type UsageInterval = {
|
||||
pluginKey: string | null;
|
||||
pluginData: SelectedPluginData | null;
|
||||
selectionKey: string | null;
|
||||
selection: SelectionInfo | null;
|
||||
length: number;
|
||||
focused: boolean;
|
||||
};
|
||||
@@ -45,7 +47,7 @@ export type UsageSummary = {
|
||||
[pluginKey: string]: {
|
||||
focusedTime: number;
|
||||
unfocusedTime: number;
|
||||
} & SelectedPluginData;
|
||||
} & SelectionInfo;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -74,6 +76,28 @@ export function emitBytesReceived(plugin: string, bytes: number) {
|
||||
}
|
||||
|
||||
export default (store: Store, logger: Logger) => {
|
||||
sideEffect(
|
||||
store,
|
||||
{
|
||||
name: 'pluginUsageTracking',
|
||||
throttleMs: 0,
|
||||
noTimeBudgetWarns: true,
|
||||
runSynchronously: true,
|
||||
},
|
||||
(state) => ({
|
||||
connections: state.connections,
|
||||
loadedPlugins: state.plugins.loadedPlugins,
|
||||
}),
|
||||
(state, store) => {
|
||||
const selection = getSelectionInfo(
|
||||
state.connections,
|
||||
state.loadedPlugins,
|
||||
);
|
||||
const time = Date.now();
|
||||
store.dispatch(selectionChanged({selection, time}));
|
||||
},
|
||||
);
|
||||
|
||||
let droppedFrames: number = 0;
|
||||
let largeFrameDrops: number = 0;
|
||||
|
||||
@@ -154,12 +178,11 @@ export default (store: Store, logger: Logger) => {
|
||||
|
||||
logger.track('usage', TIME_SPENT_EVENT, usageSummary.total);
|
||||
for (const key of Object.keys(usageSummary.plugin)) {
|
||||
const keyParts = deconstructPluginKey(key);
|
||||
logger.track(
|
||||
'usage',
|
||||
TIME_SPENT_EVENT,
|
||||
usageSummary.plugin[key],
|
||||
keyParts.pluginName,
|
||||
usageSummary.plugin[key]?.plugin ?? 'none',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -242,8 +265,8 @@ export function computeUsageSummary(
|
||||
const intervals: UsageInterval[] = [];
|
||||
let intervalStart = 0;
|
||||
let isFocused = false;
|
||||
let pluginData: SelectedPluginData | null = null;
|
||||
let pluginKey: string | null;
|
||||
let selection: SelectionInfo | null = null;
|
||||
let selectionKey: string | null;
|
||||
|
||||
function startInterval(event: TrackingEvent) {
|
||||
intervalStart = event.time;
|
||||
@@ -253,21 +276,26 @@ export function computeUsageSummary(
|
||||
) {
|
||||
isFocused = event.isFocused;
|
||||
}
|
||||
if (event.type === 'PLUGIN_SELECTED') {
|
||||
pluginKey = event.pluginKey;
|
||||
pluginData = event.pluginData;
|
||||
if (event.type === 'SELECTION_CHANGED') {
|
||||
selectionKey = event.selectionKey;
|
||||
selection = event.selection;
|
||||
}
|
||||
}
|
||||
function endInterval(time: number) {
|
||||
const length = time - intervalStart;
|
||||
intervals.push({length, focused: isFocused, pluginKey, pluginData});
|
||||
intervals.push({
|
||||
length,
|
||||
focused: isFocused,
|
||||
selectionKey,
|
||||
selection,
|
||||
});
|
||||
}
|
||||
|
||||
for (const event of state.timeline) {
|
||||
if (
|
||||
event.type === 'TIMELINE_START' ||
|
||||
event.type === 'WINDOW_FOCUS_CHANGE' ||
|
||||
event.type === 'PLUGIN_SELECTED'
|
||||
event.type === 'SELECTION_CHANGED'
|
||||
) {
|
||||
if (event.type !== 'TIMELINE_START') {
|
||||
endInterval(event.time);
|
||||
@@ -282,14 +310,14 @@ export function computeUsageSummary(
|
||||
produce(acc, (draft) => {
|
||||
draft.total.focusedTime += x.focused ? x.length : 0;
|
||||
draft.total.unfocusedTime += x.focused ? 0 : x.length;
|
||||
const pluginKey = x.pluginKey ?? getPluginKey(null, null, 'none');
|
||||
draft.plugin[pluginKey] = draft.plugin[pluginKey] ?? {
|
||||
const selectionKey = x.selectionKey ?? 'none';
|
||||
draft.plugin[selectionKey] = draft.plugin[selectionKey] ?? {
|
||||
focusedTime: 0,
|
||||
unfocusedTime: 0,
|
||||
...x.pluginData,
|
||||
...x.selection,
|
||||
};
|
||||
draft.plugin[pluginKey].focusedTime += x.focused ? x.length : 0;
|
||||
draft.plugin[pluginKey].unfocusedTime += x.focused ? 0 : x.length;
|
||||
draft.plugin[selectionKey].focusedTime += x.focused ? x.length : 0;
|
||||
draft.plugin[selectionKey].unfocusedTime += x.focused ? 0 : x.length;
|
||||
}),
|
||||
{
|
||||
total: {focusedTime: 0, unfocusedTime: 0},
|
||||
|
||||
Reference in New Issue
Block a user