Skip to content

Instantly share code, notes, and snippets.

@d4vidi
Last active December 26, 2018 15:34
Show Gist options
  • Save d4vidi/cef4d84b3d86518ed0861aeb1b3d3ad1 to your computer and use it in GitHub Desktop.
Save d4vidi/cef4d84b3d86518ed0861aeb1b3d3ad1 to your computer and use it in GitHub Desktop.
This is an improvement of Parashuram's `PerfLogger` described in http://blog.nparashuram.com/2018/11/react-native-performance-playbook-part-i.html, which allows for custom sections / events reporting alongside RN core's markers, and also enables the alignment of the report's 0-time with `Appication.onCreate()`
package com.parashuram;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.uimanager.util.ReactFindViewUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import javax.annotation.Nullable;
import java.util.LinkedList;
import java.util.List;
/**
* A class to record the Perf metrics that are emitted by {@link ReactMarker.MarkerListener} It
* records the metrics and sends them over to the server
*/
public class PerfLogger {
/** Class holds the individual records from the performance metrics */
private class PerfLoggerRecord {
private final long mTime;
private final String mName;
private final String mTag;
private final int mInstanceKey;
private final int mTid;
private final int mPid;
PerfLoggerRecord(String name, String tag, int instanceKey) {
mTime = System.currentTimeMillis();
mName = name;
mTag = tag;
mInstanceKey = instanceKey;
mPid = Process.myPid();
mTid = Process.myTid();
}
PerfLoggerRecord(String name, String tag, int instanceKey, long time) {
mTime = time;
mName = name;
mTag = tag;
mInstanceKey = instanceKey;
mPid = Process.myPid();
mTid = Process.myTid();
}
public JSONObject toJSON() {
JSONObject result = new JSONObject();
try {
result.put("time", mTime);
result.put("name", mName);
result.put("tag", mTag);
result.put("instanceKey", mInstanceKey);
result.put("pid", mPid);
result.put("tid", mTid);
return result;
} catch (JSONException e) {
return new JSONObject();
}
}
public String toString() {
return TextUtils.join(
",",
new String[] {
Long.toString(mTime),
mName,
mTag,
Integer.toString(mInstanceKey),
Integer.toString(mTid),
Integer.toString(mPid)
});
}
}
// TODO (axe) This also used as a global JS variable. Need a better way to expose this
private static final String TAG = "AXE_PERFLOGGER";
private final long mStartTime;
private final List<PerfLoggerRecord> mPerfLoggerRecords = new LinkedList<>();
private ReactNativeHost mReactNativeHost;
private static PerfLogger sInstance;
public static void createInstance(Long startTime) {
sInstance = new PerfLogger(null, startTime);
sInstance.initialize();
sInstance.logCustomEventMarker("BEGINNING_OF_TIME", null, startTime);
}
public static PerfLogger getInstance() {
return sInstance;
}
private PerfLogger(ReactNativeHost reactNativeHost, Long startTime) {
mStartTime = startTime;
mReactNativeHost = reactNativeHost;
}
private void initialize() {
addReactMarkerListener();
addTTIEndListener();
// setVariableForJS(null);
}
public void setReactNativeHost(ReactNativeHost host) {
mReactNativeHost = host;
}
public void logCustomStartMarker(String name, String tag) {
logCustomStartMarker(name, tag, System.currentTimeMillis());
}
public void logCustomStartMarker(String name, String tag, Long time) {
logCustomMarker("@" + name + "_START", tag, time);
}
public void logCustomEndMarker(String name, String tag) {
logCustomEndMarker(name, tag, System.currentTimeMillis());
}
public void logCustomEndMarker(String name, String tag, Long time) {
logCustomMarker("@" + name + "_END", tag, time);
}
public void logCustomEventMarker(String name, String tag) {
logCustomEventMarker(name, tag, System.currentTimeMillis());
}
public void logCustomEventMarker(String name, String tag, Long time) {
logCustomStartMarker(name, tag, time);
logCustomEndMarker(name, tag, time + 1);
}
private void logCustomMarker(String name, String tag, Long time) {
Log.e("ASDASD", name + ": " + tag);
mPerfLoggerRecords.add(new PerfLoggerRecord(name, tag, -1, time));
}
/**
* This is the main functionality of this file. It basically listens to all the events and stores
* them
*/
private void addReactMarkerListener() {
ReactMarker.addListener(
new ReactMarker.MarkerListener() {
@Override
public void logMarker(ReactMarkerConstants name, @Nullable String tag, int instanceKey) {
Log.e("ZXCZXC", name.toString() + ": " + tag);
mPerfLoggerRecords.add(new PerfLoggerRecord(name.toString(), tag, instanceKey));
}
});
}
/**
* Currently, we set a global JS variable to send the data from {@link ReactMarker.MarkerListener}
* to JS This should ideally be a native module. The global variable is {@link PerfLogger#TAG}
*
* <p>Currently, we call it on start and when the initial loading is complete, but it can
* technically be called at the end of any scenario that needs to be profiled
*
* @param records
*/
private void setVariableForJS(List<PerfLoggerRecord> records) {
final ReactInstanceManager reactInstanceManager = mReactNativeHost.getReactInstanceManager();
ReactContext context = reactInstanceManager.getCurrentReactContext();
if (context != null) {
// Called when React Native is ready. In this file, its when TTI is complete
context.getCatalystInstance().setGlobalVariable(TAG, getPerfRecordsJSON(mStartTime, records));
} else {
// Called when React Native is not readt, in this file during {@link PerfLogger#initialize}
// In this case, we wait for React Native, and then set the global JS
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext context) {
reactInstanceManager.removeReactInstanceEventListener(this);
context
.getCatalystInstance()
// TODO (axe) Use a native module instead of setting a global JS
.setGlobalVariable(TAG, getPerfRecordsJSON(mStartTime, null));
}
});
}
}
private static String getPerfRecordsJSON(
long startTime, @Nullable List<PerfLoggerRecord> records) {
JSONObject result = new JSONObject();
try {
result.put("startTime", startTime);
if (records != null) {
JSONArray jsonRecords = new JSONArray();
for (PerfLoggerRecord record : records) {
// Log.d(TAG, record.toString());
jsonRecords.put(record.toJSON());
}
result.put("data", jsonRecords);
}
return result.toString();
} catch (JSONException e) {
Log.w(TAG, "Could not convert perf records to JSON", e);
return "{}";
}
}
/**
* Waits for Loading to complete, also called a Time-To-Interaction (TTI) event. To indicate TTI
* completion, add a prop nativeID="tti_complete" to the component whose appearance indicates that
* the initial TTI or loading is complete
*/
private void addTTIEndListener() {
ReactFindViewUtil.addViewListener(
new ReactFindViewUtil.OnViewFoundListener() {
@Override
public String getNativeId() {
// This is the value of the nativeID property
return "tti_complete";
}
@Override
public void onViewFound(final View view) {
// Once we find the view, we also need to wait for it to be drawn
view.getViewTreeObserver()
// TODO (axe) Should be OnDrawListener instead of this
.addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
mPerfLoggerRecords.add(new PerfLoggerRecord("TTI_COMPLETE", null, 0));
setVariableForJS(mPerfLoggerRecords);
return true;
}
});
}
});
}
}
@d4vidi
Copy link
Author

d4vidi commented Dec 26, 2018

Usage in app (conceptual code):

public class AndroidApp extends Application implements ReactApplication {

    // ...

	@Override
	public void onCreate() {
		PerfLogger.createInstance(System.currentTimeMillis());
		PerfLogger.getInstance().logCustomStartMarker("App.onCreate", null);
		super.onCreate();

        // Give the host to the logger, now that we have it
		PerfLogger.getInstance().setReactNativeHost(getReactNativeHost());

		initInternalStuff();
		PerfLogger.getInstance().logCustomEndMarker("App.onCreate", null);
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment