forked from mattermost/mattermost-mobile
-
Notifications
You must be signed in to change notification settings - Fork 3
/
perf.js
189 lines (174 loc) · 5.84 KB
/
perf.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import React from "react";
import {DeviceEventEmitter} from 'react-native';
const performanceNow = require("fbjs/lib/performanceNow");
let jsStartTime = performanceNow();
// Construct a simple trace record
const traceRecord = ({
name,
time: ts,
tag = null,
instanceKey = 0,
tid = 0,
pid = 0
}) => ({
cat: "react-native",
ph: "I",
name,
ts,
pid,
tid,
args: {
instanceKey,
tag
}
});
// Gets the logs that Java sends from ReactMarker and converts them into the format that
// chrome://tracing can understand.
// Note that we should not really do this on the device, but send the data to the server
// and the server to change the format
const logsToTrace = (logs, epochStart) => {
const findClosingEventTime = (
{ name, args: { tag, instanceKey } },
index,
records
) => {
if (
name === "CREATE_MODULE_START" ||
name === "CONVERT_CONSTANTS_START" ||
name === "GET_CONSTANTS_START"
) {
// Some events like the above do not have all the information to be associated with the
// end event. In that case, we look for the first matching end event with the same name
// and assume that it would be the end event.
// This will be fixed in React Native soon
for (let i = index; i < records.length; i++) {
if (records[i].name === name.replace(/_START$/, "_END")) {
return records[i].time;
}
}
} else {
// For most events, we just look for the name, tag and instance to match, to
// find the closing event
const endEvents = records.filter(
e =>
e.name.endsWith("_END") &&
e.name.replace(/_END$/, "_START") === name &&
// Either the tag, or the instance, or both will match for the end tag
(e.tag ? e.tag === tag : e.instanceKey === instanceKey)
);
if (endEvents.length === 1) {
return endEvents[0].time;
}
}
if (__DEV__) {
console.log(
"Could not find the ending event for ",
name,
tag,
instanceKey
);
}
};
const traceEvents = [];
// Iterate over each element find its closing event, and add that to the list of traceEvents
logs.forEach((record, index) => {
//console.log(JSON.stringify(record));
let event = traceRecord({ ...record, time: (record.time - epochStart) * 1000 });
if (record.name.endsWith("_START")) {
const endTime = findClosingEventTime(event, index, logs);
if (typeof endTime !== "undefined") {
event.ph = "X";
event.dur = (endTime - record.time) * 1000;
}
event.name = record.name.replace(/_START$/, "");
traceEvents.push(event);
} else if (event.name.endsWith("_END")) {
// Nothing to do for end event, we have already processed it
} else {
// This is an instant event - an event without a close. We just log this
traceEvents.push(event);
}
});
return traceEvents;
};
// Function to convert raw logs to a format that chrome://tracing can consume.
// Ideally this should be done at the server, not on the device
const getTrace = (nativeMetrics, jsTimeSpans) => {
const trace = { traceEvents: [] };
// Iterate over logs from Java and convert them
if (typeof nativeMetrics !== "undefined") {
if (typeof nativeMetrics.startTime !== "undefined") {
jsStartTime = nativeMetrics.startTime;
}
if (typeof nativeMetrics.data !== "undefined") {
trace.traceEvents = logsToTrace(
nativeMetrics.data,
jsStartTime
);
}
}
// Iterate over the JS components logs, and convert them.
for (var name in jsTimeSpans) {
let { start, end } = jsTimeSpans[name];
const event = traceRecord({
name,
time: start - jsStartTime,
tag: "JS_EVENT"
});
event.ph = "X";
event.dur = end - start;
trace.traceEvents.push(event);
}
return trace;
};
DeviceEventEmitter.addListener('perfMetrics', (metrics) => {
const trace = getTrace(metrics, jsTimeSpans);
fetch("http://localhost:3000", {
method: "POST",
body: JSON.stringify(trace)
});
console.log("---- Uploaded Performance Metrics ----");
});
/////////////////////////////////////////////////////////////////////////////////
// A helper to record timespans that JS components send us
const jsTimeSpans = {};
const TimeSpan = {
start(name) {
jsTimeSpans[name] = { start: performanceNow() };
},
stop(name) {
const timespan = jsTimeSpans[name];
if (typeof timespan !== "undefined") {
jsTimeSpans[name] = { ...timespan, end: performanceNow() };
}
}
};
// A simple component to record the time taken to construct and mount JS components
class ComponentLogger extends React.Component {
_hasLoggedUpdate = false;
constructor(props) {
super(props);
const { name, type } = this.props;
TimeSpan[type](name + "_mount");
}
shouldComponentUpdate() {
if (!this._hasLoggedUpdate) {
const { name, type } = this.props;
this._hasLoggedUpdate = true;
TimeSpan[type](name + "_update");
}
return false;
}
render() {
return null;
}
}
export default function(name, component) {
return (
<>
<ComponentLogger type="start" name={name} />
{component}
<ComponentLogger type="stop" name={name} />
</>
);
}