Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Backtrace serializer/deserializer - GSON replacement #162

Open
wants to merge 77 commits into
base: master
Choose a base branch
from

Conversation

BartoszLitwiniuk
Copy link
Collaborator

@BartoszLitwiniuk BartoszLitwiniuk commented Feb 5, 2025

Important note: First we need to merge PR #144

Summary
This PR contains all changes needed to replace GSON library which was used for serialization/deserialization data. These modifications are part of the ongoing efforts to optimize the backtrace-android library by reducing external dependencies and enhancing performance.

Key Changes

  • Introduces new manual serialization and deserialization logic tailored for Backtrace data models.
  • Eliminates the need for GSON, reducing runtime overhead and potential compatibility issues.
  • Add unit tests to check the correctness of serialization and deserialization of the main data models

Performance comparison GSON vs custom solution based on included unit-tests

BacktraceData serialization (SerializerBacktraceDataGsonComparisonTest.java)

[GSON] Average execution time: 2.04 milliseconds
[Org.json] Average execution time: 0.942 milliseconds

Different objects serialization (SerializerGsonComparisonTest.java)

Total serialization time of object type BacktraceApiResult [GSON]: 247 milliseconds, [Org.json]: 57 milliseconds
Total serialization time of object type BacktraceResult [GSON]: 11 milliseconds, [Org.json]: 3 milliseconds
Total serialization time of object type BacktraceReport [GSON]: 5 milliseconds, [Org.json]: 8 milliseconds
Total serialization time of object type BacktraceReport [GSON]: 7 milliseconds, [Org.json]: 11 milliseconds
Total serialization time of object type BacktraceStackFrame [GSON]: 1 milliseconds, [Org.json]: 1 milliseconds
Total serialization time of object type SourceCode [GSON]: 2 milliseconds, [Org.json]: 1 milliseconds
Total serialization time of object type ThreadInformation [GSON]: 2 milliseconds, [Org.json]: 2 milliseconds

Different object deserialization (DeserializerGsonComparisonTest.java)

Total deserialization time [GSON] of object BacktraceApiResult: 120 milliseconds, [Org.json]: 42 milliseconds
Total deserialization time [GSON] of object BacktraceApiResult: 1 milliseconds, [Org.json]: 2 milliseconds
Total deserialization time [GSON] of object BacktraceData: 22 milliseconds, [Org.json]: 22 milliseconds
Total deserialization time [GSON] of object BacktraceReport: 3 milliseconds, [Org.json]: 5 milliseconds
Total deserialization time [GSON] of object BacktraceReport: 6 milliseconds, [Org.json]: 4 milliseconds
Total deserialization time [GSON] of object BacktraceResult: 6 milliseconds, [Org.json]: 6 milliseconds
Total deserialization time [GSON] of object BacktraceStackFrame: 2 milliseconds, [Org.json]: 1 milliseconds
Total deserialization time [GSON] of object SourceCode: 1 milliseconds, [Org.json]: 1 milliseconds
Total deserialization time [GSON] of object ThreadInformation: 2 milliseconds, [Org.json]: 2 milliseconds

Potential breaking changes

  • If you are a client of this library and you were using GSON annotation import com.google.gson.annotations.SerializedName; you need to replace it with import backtraceio.library.common.json.serialization.SerializedName; and verify if your objects are serialized/deserialized correctly before going to production.

Architecture of new serialization/deserialization flow
image|300
image|300

Comment on lines +49 to +60
// GSON
long startTime = System.currentTimeMillis();
BacktraceSerializeHelper.toJson(data);
long endTime = System.currentTimeMillis();
timeGson += endTime - startTime;

// ORG JSON
long startTimeOrg = System.currentTimeMillis();
BacktraceDataSerializer serializer = new BacktraceDataSerializer(new NamingPolicy());
serializer.toJson(data);
long endTimeOrg = System.currentTimeMillis();
timeOrgJson += endTimeOrg - startTimeOrg;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be nice to test one-time serialization also?
I'm thinking about the FieldNameCache initialization, which may take some more time on the first go.

if (obj == null) {
return null;
}
SourceCodeDeserializer deserializer = new SourceCodeDeserializer();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small nit: maybe keep this (and every other serializer) as class fields, so you won't have to create them each time?

Comment on lines +78 to +85
try {
if (obj.get(key) instanceof JSONObject) {
result.put(key, deserializer.deserialize((JSONObject) obj.get(key)));
}
} catch (JSONException e) {
BacktraceLogger.e(LOG_TAG, String.format("Exception on deserialization of source code, " +
"key %s, object %s", key, obj), e);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we try/catch the whole object serialization? Returning a "valid" deserialized object with it's data missing is a code smell.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This applies for every try/catch in this class and other deserializers.

final ThreadInformationDeserializer deserializer = new ThreadInformationDeserializer();
Iterator<String> keys = obj.keys();

while (keys.hasNext()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't you guys have foreach? 😢

Comment on lines +113 to +115
if (jsonArray == null) {
return null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this codebase expect ever BacktraceData.classifiers to be null? Maybe return an empty array instead?


JSONArray jsonArray = new JSONArray();
for (Object item : array) {
jsonArray.put(serialize(namingPolicy, item, serializationDepth));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
jsonArray.put(serialize(namingPolicy, item, serializationDepth));
jsonArray.put(serialize(namingPolicy, item, serializationDepth + 1));


JSONArray jsonArray = new JSONArray();
for (Object item : collection) {
jsonArray.put(serialize(namingPolicy, item, serializationDepth));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
jsonArray.put(serialize(namingPolicy, item, serializationDepth));
jsonArray.put(serialize(namingPolicy, item, serializationDepth + 1));

}

public static boolean isPrimitiveType(Object source) {
return WRAPPER_TYPE_MAP.containsKey(source.getClass()) || source instanceof String || source instanceof Number;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't you also check for non-wrapped types, such as int.class?


private static Object serializeException(NamingPolicy namingPolicy, Exception exception) {
try {
return getAllFields(namingPolicy, exception.getClass(), exception, 2);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why 2?

for (Map.Entry<?, ?> entry : map.entrySet()) {
String key = String.valueOf(entry.getKey());
Object value = entry.getValue();
jsonObject.put(key, serialize(namingPolicy, value, serializationDepth));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
jsonObject.put(key, serialize(namingPolicy, value, serializationDepth));
jsonObject.put(key, serialize(namingPolicy, value, serializationDepth + 1));

Base automatically changed from backtrace-data-models-refactor to master February 12, 2025 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants