From b6910407e8849a5b243bc21617ee15d6b702d803 Mon Sep 17 00:00:00 2001 From: Osana Babayan <32496536+osana@users.noreply.github.com> Date: Tue, 2 Apr 2019 12:26:36 -0400 Subject: [android] updated PerformanceEvent (#13894) --- .../module/telemetry/PerformanceEvent.java | 94 ++++++++++- .../mapboxsdk/module/telemetry/TelemetryImpl.java | 5 +- .../module/telemetry/PerformanceEventTest.java | 188 +++++++++++++++++++++ .../src/main/AndroidManifest.xml | 11 ++ .../telemetry/PerformanceMeasurementActivity.java | 187 ++++++++++++++++++++ .../src/main/res/values/categories.xml | 1 + .../src/main/res/values/descriptions.xml | 1 + .../src/main/res/values/titles.xml | 1 + platform/android/scripts/exclude-activity-gen.json | 3 +- 9 files changed, 482 insertions(+), 9 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/module/telemetry/PerformanceEventTest.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/telemetry/PerformanceMeasurementActivity.java diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/PerformanceEvent.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/PerformanceEvent.java index b88e1885ca..12d1fe46cf 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/PerformanceEvent.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/PerformanceEvent.java @@ -1,20 +1,55 @@ package com.mapbox.mapboxsdk.module.telemetry; +import com.google.gson.Gson; + +import com.google.gson.JsonObject; + +import com.google.gson.reflect.TypeToken; import com.mapbox.android.telemetry.Event; import android.os.Bundle; import android.os.Parcel; + import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Locale; /** * Generic Performance Event that can be used for performance measurements. * Customer measurements can be added to the bundle. + * + * Bundle is expected to have following properties: + * "attributes", "counters", and "metadata" with String values. + * + * Attributes: a string representing an array of name/string value pair objects. + * Counters: a string representing an array of name/number value pair objects. + * Metadata is a string representation of a JsonObject with string values. + * + * Here is an example of a Performance event bundle data: + * + * "attributes": [{ "name": "style_id", "value": "mapbox://styles/mapbox/streets-v10"}] + * + * "counters": [{"name": "fps_average", "value": 90.7655486547093}, + * {"name": "fps_deviation", "value": 29.301809631465574}] + * “metadata”: { + * “version”: “9”, + * “screenSize”: “1080x1794”, + * “country”: “US”, + * “device”: “Pixel 2”, + * “abi”: “arm64-v8a”, + * “brand”: “google”, + * “ram”: “3834167296”, + * “os”: “android”, + * “gpu”: “Qualcomm, Adreno (TM) 540, OpenGL ES 3.2 V@313.0 (GIT@7bf2852, Ie32bfa6f6f)“, + * “manufacturer”: “Google” + * } */ public class PerformanceEvent extends Event { - private static final String PERFORMANCE_TRACE = "performance.trace"; + + private static final String PERFORMANCE_TRACE = "mobile.performance_trace"; private final String event; @@ -22,23 +57,36 @@ public class PerformanceEvent extends Event { private final String sessionId; - private final Bundle data; + private final List> attributes; + + private final List> counters; + + private final JsonObject metadata; + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); - PerformanceEvent(String sessionId, Bundle data) { + PerformanceEvent(String sessionId, Bundle bundle) { + this.event = PERFORMANCE_TRACE; this.created = DATE_FORMAT.format(new Date()); this.sessionId = sessionId; - this.data = data; + this.attributes = initList(bundle.getString("attributes"), + new TypeToken>>() {}); + this.counters = initList(bundle.getString("counters"), + new TypeToken>>() {}); + this.metadata = initMetaData(bundle.getString("metadata")); } private PerformanceEvent(Parcel in) { this.event = in.readString(); this.created = in.readString(); this.sessionId = in.readString(); - this.data = in.readBundle(); + + this.attributes = initList(in.readString(), new TypeToken>>() {}); + this.counters = initList(in.readString(), new TypeToken>>() {}); + this.metadata = initMetaData(in.readString()); } @Override @@ -51,7 +99,30 @@ public class PerformanceEvent extends Event { parcel.writeString(event); parcel.writeString(created); parcel.writeString(sessionId); - parcel.writeBundle(data); + + Gson gson = new Gson(); + + parcel.writeString(gson.toJson(attributes)); + parcel.writeString(gson.toJson(counters)); + + if (metadata != null) { + parcel.writeString(metadata.toString()); + } + } + + private ArrayList> initList(String fromString, TypeToken typeToken) { + if (fromString == null || fromString.isEmpty()) { + return new ArrayList<>(); + } + return new Gson().fromJson(fromString, typeToken.getType()); + } + + private JsonObject initMetaData(String fromString) { + if (fromString == null) { + return new JsonObject(); + } else { + return new Gson().fromJson(fromString, JsonObject.class); + } } public static final Creator CREATOR = new Creator() { @@ -65,4 +136,15 @@ public class PerformanceEvent extends Event { return new PerformanceEvent[size]; } }; + + + private class Attribute { + private final String name; + private final T value; + + Attribute(String name, T value) { + this.name = name; + this.value = value; + } + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/TelemetryImpl.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/TelemetryImpl.java index 5e021f961e..697a51286f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/TelemetryImpl.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/TelemetryImpl.java @@ -111,8 +111,9 @@ public class TelemetryImpl implements TelemetryDefinition { @Override public void onPerformanceEvent(Bundle data) { - if (data != null && !data.isEmpty()) { - telemetry.push(new PerformanceEvent(UUID.randomUUID().toString(), data)); + if (data == null) { + data = new Bundle(); } + telemetry.push(new PerformanceEvent(UUID.randomUUID().toString(), data)); } } \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/module/telemetry/PerformanceEventTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/module/telemetry/PerformanceEventTest.java new file mode 100644 index 0000000000..6f256b4e56 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/module/telemetry/PerformanceEventTest.java @@ -0,0 +1,188 @@ +package com.mapbox.mapboxsdk.module.telemetry; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.WindowManager; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.mapbox.mapboxsdk.Mapbox; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(AndroidJUnit4.class) +public class PerformanceEventTest { + + @Test + public void checksPerformanceEventWithMetaData() throws Exception { + PerformanceEvent event = obtainPerformanceEvent(); + assertNotNull(event); + + Parcel parcel = Parcel.obtain(); + + event.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + PerformanceEvent newPerfEvent = PerformanceEvent.CREATOR.createFromParcel(parcel); + assertNotNull(newPerfEvent); + + compare(event, newPerfEvent, "attributes", "style_id"); + compare(event, newPerfEvent, "counters", "int_value"); + compare(event, newPerfEvent, "counters", "long_value"); + compare(event, newPerfEvent, "counters", "double_value"); + assertEquals(getMetadata(event), getMetadata(newPerfEvent)); + } + + @Test + public void checksPerformanceEventOnlyRequiredData() throws Exception { + Bundle bundle = new Bundle(); + bundle.putString("property ignored", "value will be ignored"); + PerformanceEvent event = new PerformanceEvent(UUID.randomUUID().toString(), bundle); + assertNotNull(event); + + Parcel parcel = Parcel.obtain(); + event.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + PerformanceEvent newPerfEvent = PerformanceEvent.CREATOR.createFromParcel(parcel); + assertNotNull(newPerfEvent); + + assertEquals(getAttributes(event).size(), 0); + assertEquals(getCounters(event).size(), 0); + assertEquals(getAttributes(newPerfEvent).size(), 0); + assertEquals(getCounters(newPerfEvent).size(), 0); + assertEquals(getMetadata(event), getMetadata(newPerfEvent)); + } + + + private PerformanceEvent obtainPerformanceEvent() { + String styleStr = "mapbox://styles/mapbox/streets-v11"; + boolean testPerfEvent = true; + Double doubleValue = 40.5; + Long longValue = 40L; + Integer intValue = 40; + + List> attributes = new ArrayList<>(); + attributes.add( + new Attribute<>("style_id", styleStr)); + attributes.add( + new Attribute<>("test_perf_event", String.valueOf(testPerfEvent))); + + List> counters = new ArrayList(); + counters.add(new Attribute<>("long_value", longValue)); + counters.add(new Attribute<>("double_value", doubleValue)); + counters.add(new Attribute<>("int_value", intValue)); + + Gson gson = new Gson(); + + Bundle bundle = new Bundle(); + bundle.putString("attributes", gson.toJson(attributes)); + bundle.putString("counters", gson.toJson(counters)); + + JsonObject metaData = new JsonObject(); + metaData.addProperty("os", "android"); + metaData.addProperty("manufacturer", Build.MANUFACTURER); + metaData.addProperty("brand", Build.BRAND); + metaData.addProperty("device", Build.MODEL); + metaData.addProperty("version", Build.VERSION.RELEASE); + metaData.addProperty("abi", Build.CPU_ABI); + metaData.addProperty("country", Locale.getDefault().getISO3Country()); + metaData.addProperty("ram", getRam()); + metaData.addProperty("screenSize", getWindowSize()); + bundle.putString("metadata", metaData.toString()); + + return new PerformanceEvent(UUID.randomUUID().toString(), bundle); + } + + private void compare(PerformanceEvent event1, PerformanceEvent event2, String listFieldName, String name) + throws NoSuchFieldException, IllegalAccessException { + Object value1 = getValue(event1, listFieldName, name); + Object value2 = getValue(event2, listFieldName, name); + + if (value1 instanceof Double && value2 instanceof Double) { + assertEquals((Double)value1, (Double)value2, 0.00006); + } else { + assertEquals(value1, value2); + } + } + + private Object getPrivateFieldValue(Object object, String fieldName) + throws NoSuchFieldException, IllegalAccessException { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(object); + } + + private Object getValue(PerformanceEvent event, String listFieldName, String name) + throws NoSuchFieldException, IllegalAccessException { + ArrayList list = (ArrayList)getPrivateFieldValue(event, listFieldName); + for (Object element : list) { + Object elementName = getPrivateFieldValue(element, "name"); + if (elementName != null && elementName.equals((String)name)) { + return getPrivateFieldValue(element, "value"); + } + } + return null; + } + + private JsonObject getMetadata(PerformanceEvent event) + throws NoSuchFieldException, IllegalAccessException { + return (JsonObject)getPrivateFieldValue(event, "metadata"); + } + + private ArrayList getAttributes(PerformanceEvent event) + throws NoSuchFieldException, IllegalAccessException { + return (ArrayList)getPrivateFieldValue(event, "attributes"); + } + + private ArrayList getCounters(PerformanceEvent event) + throws NoSuchFieldException, IllegalAccessException { + return (ArrayList)getPrivateFieldValue(event, "counters"); + } + + private static String getRam() { + ActivityManager actManager = + (ActivityManager) Mapbox.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); + actManager.getMemoryInfo(memInfo); + return String.valueOf(memInfo.totalMem); + } + + private static String getWindowSize() { + WindowManager windowManager = + (WindowManager) Mapbox.getApplicationContext().getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + int width = metrics.widthPixels; + int height = metrics.heightPixels; + + return "{" + width + "," + height + "}"; + } + + private class Attribute { + private String name; + private T value; + + Attribute(String name, T value) { + this.name = name; + this.value = value; + } + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index a5cc549cc2..017fe3ddca 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -930,6 +930,17 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity" /> + + + + startTimes = new HashMap<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_map_simple); + mapView = findViewById(R.id.mapView); + mapView.onCreate(savedInstanceState); + + + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.eventListener(new EventListener() { + + @Override + public void callStart(Call call) { + String url = call.request().url().toString(); + startTimes.put(url, System.nanoTime()); + super.callStart(call); + Timber.e("callStart: %s", url); + } + + @Override + public void callEnd(Call call) { + String url = call.request().url().toString(); + Timber.e("callEnd: %s", url); + Long start = startTimes.get(url); + if (start != null) { + long elapsed = System.nanoTime() - start; + triggerPerformanceEvent(url.substring(0, url.indexOf('?')), elapsed); + startTimes.remove(start); + Timber.e("callEnd: %s took %d", url, elapsed); + } + super.callEnd(call); + } + }); + HttpRequestUtil.setOkHttpClient(builder.build()); + + mapView.getMapAsync(mapboxMap -> mapboxMap.setStyle( + new Style.Builder().fromUrl(Style.MAPBOX_STREETS))); + } + + + @Override + protected void onStart() { + super.onStart(); + mapView.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + mapView.onStop(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + protected void onDestroy() { + + startTimes.clear(); + + super.onDestroy(); + mapView.onDestroy(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } + + private void triggerPerformanceEvent(String style, long elapsed) { + + List> attributes = new ArrayList<>(); + attributes.add( + new Attribute<>("style_id", style)); + attributes.add( + new Attribute<>("test_perf_event", "true")); + + List> counters = new ArrayList(); + counters.add(new Attribute<>("elapsed", elapsed)); + + + JsonObject metaData = new JsonObject(); + metaData.addProperty("os", "android"); + metaData.addProperty("manufacturer", Build.MANUFACTURER); + metaData.addProperty("brand", Build.BRAND); + metaData.addProperty("device", Build.MODEL); + metaData.addProperty("version", Build.VERSION.RELEASE); + metaData.addProperty("abi", Build.CPU_ABI); + metaData.addProperty("country", Locale.getDefault().getISO3Country()); + metaData.addProperty("ram", getRam()); + metaData.addProperty("screenSize", getWindowSize()); + + Gson gson = new Gson(); + + Bundle bundle = new Bundle(); + bundle.putString("attributes", gson.toJson(attributes)); + bundle.putString("counters", gson.toJson(counters)); + bundle.putString("metadata", metaData.toString()); + + Mapbox.getTelemetry().onPerformanceEvent(bundle); + } + + private static String getRam() { + ActivityManager actManager = + (ActivityManager) Mapbox.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); + actManager.getMemoryInfo(memInfo); + return String.valueOf(memInfo.totalMem); + } + + private static String getWindowSize() { + WindowManager windowManager = + (WindowManager) Mapbox.getApplicationContext().getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + int width = metrics.widthPixels; + int height = metrics.heightPixels; + + return "{" + width + "," + height + "}"; + } + + private class Attribute { + private String name; + private T value; + + Attribute(String name, T value) { + this.name = name; + this.value = value; + } + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/categories.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/categories.xml index 918fd4cc3f..2c34a59327 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/categories.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/categories.xml @@ -16,4 +16,5 @@ Texture View Location _Integration + Telemetry \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index cd4130c44a..778805b3b3 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -81,4 +81,5 @@ Show a TextureView MapView as a recyclerView item Show a GLSurfaceView MapView as a recyclerView item Show a MapView inside a viewpager inside a recyclerView + Show the use PerformanceEvent for performance measurements diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index 07ba2b7afd..12c82bf21a 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -81,4 +81,5 @@ RecyclerView TextureView RecyclerView GLSurfaceView Nested ViewPager + Performance Measurement \ No newline at end of file diff --git a/platform/android/scripts/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json index 47c437bd23..a6070edccf 100644 --- a/platform/android/scripts/exclude-activity-gen.json +++ b/platform/android/scripts/exclude-activity-gen.json @@ -49,5 +49,6 @@ "ChangeResourcesCachePathActivity", "EspressoTestActivity", "FragmentBackStackActivity", - "ChildFragmentMapInDialogActivity" + "ChildFragmentMapInDialogActivity", + "PerformanceMeasurementActivity" ] -- cgit v1.2.1