Skip to content

Commit

Permalink
Adding ability to add custom metrics to custom traces.
Browse files Browse the repository at this point in the history
  • Loading branch information
ArtursKadikis committed Apr 30, 2020
1 parent 0c323b8 commit a7b5f7e
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 27 deletions.
10 changes: 8 additions & 2 deletions app/src/main/java/ly/count/android/demo/ActivityExampleAPM.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import android.os.Bundle;
import android.view.View;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import ly.count.android.sdk.Countly;
Expand Down Expand Up @@ -52,11 +54,15 @@ public void onClickStartTrace_2(View v) {
}

public void onClickEndTrace_1(View v) {
Countly.sharedInstance().apm().endTrace("Some_trace_key_1");
Map<String, Integer> customMetric = new HashMap();
customMetric.put("ABC", 1233);
customMetric.put("C44C", 1337);

Countly.sharedInstance().apm().endTrace("Some_trace_key_1", customMetric);
}

public void onClickEndTrace_2(View v) {
Countly.sharedInstance().apm().endTrace("another key_1");
Countly.sharedInstance().apm().endTrace("another key_1", null);
}

public void onClickStartNetworkTrace_1(View v) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,59 +34,59 @@ void EmptyFunction_3(){


public void onClickCrashReporting01(View v) {
Countly.sharedInstance().addCrashBreadcrumb("Unrecognized selector crash");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Unrecognized selector crash");
}

public void onClickCrashReporting02(View v) {
Countly.sharedInstance().addCrashBreadcrumb("Out of bounds crash");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Out of bounds crash");
//noinspection MismatchedReadAndWriteOfArray
int[] data = new int[]{};
data[0] = 9;
}

public void onClickCrashReporting03(View v) {
Countly.sharedInstance().addCrashBreadcrumb("Null pointer crash");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Null pointer crash");

Object[] o = null;
o[0].getClass();
}

public void onClickCrashReporting04(View v) {
Countly.sharedInstance().addCrashBreadcrumb("Invalid Geometry crash");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Invalid Geometry crash");
}

public void onClickCrashReporting05(View v) {
Countly.sharedInstance().addCrashBreadcrumb("Assert fail crash");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Assert fail crash");
//Assert.assertEquals(1, 0);
}

public void onClickCrashReporting06(View v) {
Countly.sharedInstance().addCrashBreadcrumb("Kill process crash");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Kill process crash");
android.os.Process.killProcess(android.os.Process.myPid());
}

public void onClickCrashReporting07(View v) {
Countly.sharedInstance().addCrashBreadcrumb("Custom crash log crash");
Countly.sharedInstance().addCrashBreadcrumb("Adding some custom crash log");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Custom crash log crash");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Adding some custom crash log");

//noinspection UnusedAssignment,divzero
@SuppressWarnings("NumericOverflow") int test = 10/0;
}

public void onClickCrashReporting08(View v) {
Countly.sharedInstance().addCrashBreadcrumb("Recording handled exception 1");
Countly.sharedInstance().recordHandledException(new Exception("A custom error text"));
Countly.sharedInstance().addCrashBreadcrumb("Recording handled exception 3");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Recording handled exception 1");
Countly.sharedInstance().crashes().recordHandledException(new Exception("A custom error text"));
Countly.sharedInstance().crashes().addCrashBreadcrumb("Recording handled exception 3");
}

public void onClickCrashReporting09(View v) throws Exception {
Countly.sharedInstance().addCrashBreadcrumb("Unhandled exception info");
Countly.sharedInstance().crashes().addCrashBreadcrumb("Unhandled exception info");
throw new Exception("A unhandled exception");
}

public void onClickCrashReporting13(View v){
String largeCrumb = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
Countly.sharedInstance().addCrashBreadcrumb(largeCrumb);
Countly.sharedInstance().crashes().addCrashBreadcrumb(largeCrumb);
}

public void onClickCrashReporting10(View v) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,73 @@ public void tearDown() {
}

@Test
public void boop() {
public void customMetricFilter_invlidFields() {
Map<String, Integer> customMetrics = new HashMap<>();

ModuleAPM.removeReservedInvalidKeys(customMetrics);
Assert.assertEquals(0, customMetrics.size());

customMetrics.put("a11", 2);
customMetrics.put(null, 1);
customMetrics.put("2", null);
customMetrics.put("", 44);
customMetrics.put(null, null);

ModuleAPM.removeReservedInvalidKeys(customMetrics);
Assert.assertEquals(1, customMetrics.size());
}

@Test
public void customMetricFilter_reservedKeys() {
Map<String, Integer> customMetrics = new HashMap<>();

ModuleAPM.removeReservedInvalidKeys(customMetrics);
Assert.assertEquals(0, customMetrics.size());

customMetrics.put("a11", 2);

for(String key:ModuleAPM.reservedKeys) {
customMetrics.put(key, 4);
}

ModuleAPM.removeReservedInvalidKeys(customMetrics);
Assert.assertEquals(1, customMetrics.size());
}

@Test
public void customMetricFilter_validKeyName() {
Map<String, Integer> customMetrics = new HashMap<>();

ModuleAPM.removeReservedInvalidKeys(customMetrics);
Assert.assertEquals(0, customMetrics.size());

customMetrics.put("a11", 2);
customMetrics.put("a11111111111111111111111111111111111", 2);
customMetrics.put("_a11", 2);
customMetrics.put(" a11", 2);
customMetrics.put("a11 ", 2);


ModuleAPM.removeReservedInvalidKeys(customMetrics);
Assert.assertEquals(1, customMetrics.size());
}

@Test
public void customMetricToString() {
Map<String, Integer> customMetrics = new HashMap<>();
customMetrics.put("a11", 2);
customMetrics.put("aaa", 23);
customMetrics.put("a351", 22);
customMetrics.put("a114", 21);
customMetrics.put("a1__f1", 24);


ModuleAPM.removeReservedInvalidKeys(customMetrics);

Assert.assertEquals(5, customMetrics.size());

String metricString = ModuleAPM.customMetricsToString(customMetrics);

Assert.assertEquals(",\"a11\":2,\"aaa\":23,\"a351\":22,\"a1__f1\":24,\"a114\":21", metricString);
}
}
3 changes: 0 additions & 3 deletions sdk/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,4 @@
<uses-library android:name="android.test.runner"/>
</application>

<!--<instrumentation android:name="ly.count.android.sdk.test.InstrumentationTestRunner"-->
<!--android:targetPackage="ly.count.android.sdk"/>-->

</manifest>
4 changes: 2 additions & 2 deletions sdk/src/main/java/ly/count/android/sdk/ConnectionQueue.java
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ void sendConsentChanges(String formattedConsentChanges) {
tick();
}

void sendAPMCustomTrace(String key, Long durationMs, Long startMs, Long endMs) {
void sendAPMCustomTrace(String key, Long durationMs, Long startMs, Long endMs, String customMetrics) {
checkInternalState();

if (Countly.sharedInstance().isLoggingEnabled()) {
Expand All @@ -482,7 +482,7 @@ void sendAPMCustomTrace(String key, Long durationMs, Long startMs, Long endMs) {
// &apm={"type":"device","name":"forLoopProfiling_1","apm_metrics":{"duration": 10, “memory”: 200}, "stz": 1584698900, "etz": 1584699900}
// &timestamp=1584698900&count=1

String apmData = "{\"type\":\"device\",\"name\":\"" + key + "\", \"apm_metrics\":{\"duration\": " + durationMs + "}, \"stz\": " + startMs + ", \"etz\": " + endMs + "}";
String apmData = "{\"type\":\"device\",\"name\":\"" + key + "\", \"apm_metrics\":{\"duration\": " + durationMs + customMetrics + "}, \"stz\": " + startMs + ", \"etz\": " + endMs + "}";

final String data = prepareCommonRequestData()
+ "&count=1"
Expand Down
113 changes: 109 additions & 4 deletions sdk/src/main/java/ly/count/android/sdk/ModuleAPM.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@
import android.util.Log;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ModuleAPM extends ModuleBase {

final static String[] reservedKeys = new String[] {"response_time", "response_payload_size", "response_code", "request_payload_size", "duration", "slow_rendering_frames", "frozen_frames"};

Apm apmInterface = null;

Map<String, Long> codeTraces;
Expand Down Expand Up @@ -53,7 +59,7 @@ void startTraceInternal(String traceKey) {
codeTraces.put(traceKey, currentTimestamp);
}

void endTraceInternal(String traceKey) {
void endTraceInternal(String traceKey, Map<String, Integer> customMetrics) {
//end time counting as fast as possible
Long currentTimestamp = UtilsTime.currentTimestampMs();

Expand All @@ -78,7 +84,15 @@ void endTraceInternal(String traceKey) {
} else {
Long durationMs = currentTimestamp - startTimestamp;

_cly.connectionQueue_.sendAPMCustomTrace(traceKey, durationMs, startTimestamp, currentTimestamp);
if(customMetrics != null) {
//custom metrics provided
//remove reserved keys
removeReservedInvalidKeys(customMetrics);
}

String metricString = customMetricsToString(customMetrics);

_cly.connectionQueue_.sendAPMCustomTrace(traceKey, durationMs, startTimestamp, currentTimestamp, metricString);
}
} else {
if (_cly.isLoggingEnabled()) {
Expand All @@ -87,6 +101,97 @@ void endTraceInternal(String traceKey) {
}
}

static String customMetricsToString(Map<String, Integer> customMetrics) {
StringBuilder ret = new StringBuilder();

if(customMetrics == null) {
return ret.toString();
}

for(Iterator<Map.Entry<String, Integer>> it = customMetrics.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Integer> entry = it.next();
String key = entry.getKey();
Integer value = entry.getValue();

ret.append(",\"");
ret.append(key);
ret.append("\":");
ret.append(value);
}

return ret.toString();
}

static void removeReservedInvalidKeys(Map<String, Integer> customMetrics) {
if(customMetrics == null) {
return;
}

//remove reserved keys
for(String rKey:ModuleAPM.reservedKeys) {
customMetrics.remove(rKey);
}

Pattern p = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]*$");
Matcher m = p.matcher("aaaaab");

for(Iterator<Map.Entry<String, Integer>> it = customMetrics.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Integer> entry = it.next();
String key = entry.getKey();
Integer value = entry.getValue();

//remove invalid values
if(key == null || key.isEmpty() || value == null) {
it.remove();
if (Countly.sharedInstance().isLoggingEnabled()) {
Log.w(Countly.TAG, "[ModuleAPM] custom metrics can't contain null or empty key/value");
}
continue;
}

//remove invalid keys
//regex for valid keys serverside
// /^[a-zA-Z][a-zA-Z0-9_]*$/
int keyLength = key.length();

if(keyLength > 32) {
//remove key longer than 32 characters
it.remove();
if (Countly.sharedInstance().isLoggingEnabled()) {
Log.w(Countly.TAG, "[ModuleAPM] custom metric key removed, it can't be longer than 32 characters, [" + key + "]");
}
continue;
}

if(key.charAt(0) == '_') {
//remove key that starts with underscore
it.remove();
if (Countly.sharedInstance().isLoggingEnabled()) {
Log.w(Countly.TAG, "[ModuleAPM] custom metric key removed, it can't start with underscore, [" + key + "]");
}
continue;
}

if(key.charAt(0) == ' ' || key.charAt(keyLength - 1) == ' ') {
//remove key that starts with whitespace
it.remove();
if (Countly.sharedInstance().isLoggingEnabled()) {
Log.w(Countly.TAG, "[ModuleAPM] custom metric key removed, it can't start or end with whitespace, [" + key + "]");
}
continue;
}

if(!p.matcher(key).matches()) {
//validate against regex
it.remove();
if (Countly.sharedInstance().isLoggingEnabled()) {
Log.w(Countly.TAG, "[ModuleAPM] custom metric key removed, did not correspond to the allowed regex '/^[a-zA-Z][a-zA-Z0-9_]*$/'");
}
continue;
}
}
}

/**
* Begin the tracking of a network request
* @param networkTraceKey key that identifies the network trace
Expand Down Expand Up @@ -288,12 +393,12 @@ public void startTrace(String traceKey) {
* End a trace of a action you want to track
* @param traceKey key by which this action is identified
*/
public void endTrace(String traceKey) {
public void endTrace(String traceKey, Map<String, Integer> customMetrics) {
if (_cly.isLoggingEnabled()) {
Log.d(Countly.TAG, "[Apm] Calling 'endTrace' with key:[" + traceKey + "]");
}

endTraceInternal(traceKey);
endTraceInternal(traceKey, customMetrics);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/main/java/ly/count/android/sdk/ModuleEvents.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ public synchronized boolean endEvent(final String key) {
* End timed event with a specified key
* @param key name of the custom event, required, must not be the empty string
* @param segmentation segmentation dictionary to associate with the event, can be null
* @param count count to associate with the event, should be more than zero
* @param sum sum to associate with the event
* @param count count to associate with the event, should be more than zero, default value is 1
* @param sum sum to associate with the event, default value is 0
* @throws IllegalStateException if Countly SDK has not been initialized
* @throws IllegalArgumentException if key is null or empty, count is less than 1, or if segmentation contains null or empty keys or values
* @return true if event with this key has been previously started, false otherwise
Expand Down

0 comments on commit a7b5f7e

Please sign in to comment.