diff --git a/app/build.gradle b/app/build.gradle index 77c159e..e596d1c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,4 +78,9 @@ dependencies { } annotationProcessor 'com.elegant.spi:compiler:1.0.3' //编译时库 + +// implementation 'ly.count.android:sdk:24.4.1' +// implementation 'ly.count.android:sdk-native:24.4.1' + implementation project(":libraries:countly:sdk") + implementation project(":libraries:countly:sdk-native") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9b51ecc..1cc3036 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,6 +37,7 @@ android:name=".NSDNettyActivity" android:exported="false" /> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/App.java b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/App.java new file mode 100644 index 0000000..9cc20a6 --- /dev/null +++ b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/App.java @@ -0,0 +1,69 @@ +package ly.count.android.benchmark; + +import android.app.Application; +import ly.count.android.sdk.Countly; +import ly.count.android.sdk.CountlyConfig; +import ly.count.android.sdk.CountlyStore; +import ly.count.android.sdk.ModuleLog; +import ly.count.android.sdk.PerformanceCounterCollector; + +public class App extends Application { + private final static String COUNTLY_SERVER_URL = "https://xxx.count.ly"; + private final static String COUNTLY_APP_KEY = "YOUR_APP_KEY"; + private final static String DEVICE_ID = "YOUR_DEVICE_ID"; + + public static PerformanceCounterCollector appPcc; + + @Override + public void onCreate() { + super.onCreate(); + + CountlyConfig config = new CountlyConfig(this, COUNTLY_APP_KEY, COUNTLY_SERVER_URL) + .setDeviceId(DEVICE_ID) + .setLoggingEnabled(true) + .giveAllConsents() + .setRequestDropAgeHours(10)//to trigger the age blocks + .setEventQueueSizeToSend(100)//for testing the main use case + .setParameterTamperingProtectionSalt("test-benchmark-salt"); + + appPcc = new PerformanceCounterCollector(); + config.pcc = appPcc; + + Countly.sharedInstance().init(config); + + //clear initial state to erase past data + Countly.sharedInstance().requestQueue().flushQueues(); + + Benchmark.countlyStore = new CountlyStore(this, new ModuleLog()); + } + + /* + * Benchmark scenario - 1 + * Generate events and not requests: yes + * wait: yes + * EQ threshold: 10 + * segm values per event: 5 + * Generated request count: value doesn't matter + * Event count: 10000 + * Fill to 1000 requests which equals to 10000 events generated + * + * steps: + * 1) turn off internet + * 2) clear counters + * 3) Fill RQ/EQ + * 4) turn on internet + * 5) send requests + * 6) wait till all sent + * 7) print counters + * + * Scenario 2 + * RQ size 1000 + * Generate a mix of 1200 requests + * EQ threshold 100 + * 4 direct requests : 1 event request + * direct requests - 960 + * event requests - 240 + * events generated - 24000 + * segm values per event: 6 + */ +} diff --git a/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/Benchmark.java b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/Benchmark.java new file mode 100644 index 0000000..c432bd9 --- /dev/null +++ b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/Benchmark.java @@ -0,0 +1,86 @@ +package ly.count.android.benchmark; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import ly.count.android.sdk.Countly; +import ly.count.android.sdk.CountlyStore; +import ly.count.android.sdk.UtilsTime; +import org.json.JSONException; + +public class Benchmark { + private final BenchmarkUtil benchmarkUtil; + protected static CountlyStore countlyStore; + + protected Benchmark() { + benchmarkUtil = new BenchmarkUtil(); + } + + public void fillRequestQueue(int rqSize, int eventSize, int segmentSize) { + //Countly.sharedInstance().requestQueue().flushQueues(); + print("[Benchmark] fillRequestQueue, Filling request queue, rq is flushed"); + for (int i = 0; i < rqSize; i++) { + Map additionalParams = null; + if (eventSize < 1) { + additionalParams = new ConcurrentHashMap<>(); + additionalParams.put("number", "1"); + } + try { + Map request = benchmarkUtil.generateRequest(eventSize, segmentSize, additionalParams); + Countly.sharedInstance().requestQueue().addDirectRequest(request); + } catch (JSONException e) { + print("[Benchmark] fillRequestQueue, Failed to generate request: " + e); + } + } + print("[Benchmark] fillRequestQueue, Request queue size: " + countlyStore.getRequests().length); + } + + public void fillEventQueue(int eventSize, int segmentSize) { + //Countly.sharedInstance().requestQueue().flushQueues(); + + print("[Benchmark] fillEventQueue, Filling event queue, rq is flushed"); + for (int i = 0; i < eventSize; i++) { + long tsStartGen = UtilsTime.getNanoTime(); + Object[] randomEvent = benchmarkUtil.generateRandomEventBase(segmentSize); + App.appPcc.TrackCounterTimeNs("Benchmark_genTime", UtilsTime.getNanoTime() - tsStartGen); + + long tsStartAction = UtilsTime.getNanoTime(); + Countly.sharedInstance().events().recordEvent(randomEvent[0].toString(), (Map) randomEvent[1], (int) randomEvent[2], (double) randomEvent[3], (double) randomEvent[4]); + App.appPcc.TrackCounterTimeNs("Benchmark_recordEventTime", UtilsTime.getNanoTime() - tsStartAction); + } + print("[Benchmark] fillEventQueue, Request queue size: " + countlyStore.getRequests().length); + } + + public void GenerateBenchmarkDataset(int eventSize, int segmentSize) { + Countly.sharedInstance().requestQueue().flushQueues(); + + int eventsPerChunk = 100; + //int eventChunks = eventSize / eventsPerChunk; + + int eventsRecorded = 0; + + print("[Benchmark] fillEventQueue, Filling event queue, rq is flushed"); + while (eventsRecorded < eventSize) { + //larger RQ filler + long tsStartGenDRequests = UtilsTime.getNanoTime(); + fillRequestQueue(4, 2, 2); + App.appPcc.TrackCounterTimeNs("Benchmark_recordDirectRequests", UtilsTime.getNanoTime() - tsStartGenDRequests); + + //filling it up by individual events + for (int i = 0; i < eventsPerChunk; i++) { + long tsStartGen = UtilsTime.getNanoTime(); + Object[] randomEvent = benchmarkUtil.generateRandomEventBase(segmentSize); + App.appPcc.TrackCounterTimeNs("Benchmark_genTime", UtilsTime.getNanoTime() - tsStartGen); + + long tsStartAction = UtilsTime.getNanoTime(); + Countly.sharedInstance().events().recordEvent(randomEvent[0].toString(), (Map) randomEvent[1], (int) randomEvent[2], (double) randomEvent[3], (double) randomEvent[4]); + App.appPcc.TrackCounterTimeNs("Benchmark_recordEventTime", UtilsTime.getNanoTime() - tsStartAction); + eventsRecorded++; + } + } + print("[Benchmark] fillEventQueue, Request queue size: " + countlyStore.getRequests().length); + } + + protected void print(String message) { + System.err.println(message); + } +} \ No newline at end of file diff --git a/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/BenchmarkUtil.java b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/BenchmarkUtil.java new file mode 100644 index 0000000..8d54e78 --- /dev/null +++ b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/BenchmarkUtil.java @@ -0,0 +1,80 @@ +package ly.count.android.benchmark; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import ly.count.android.sdk.UtilsTime; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class BenchmarkUtil { + private final RandomUtil random; + + protected BenchmarkUtil() { + random = new RandomUtil(); + } + + protected Map generateRequest(int eventSize, int segmentSize, Map additionalParams) throws JSONException { + Map request = new ConcurrentHashMap<>(); + List events = new ArrayList<>(); + for (int i = 0; i < eventSize; i++) { + events.add(generateRandomEvent(segmentSize)); + } + + if (!events.isEmpty()) { + request.put("events", new JSONArray(events).toString()); + } + + if (additionalParams != null && !additionalParams.isEmpty()) { + request.putAll(additionalParams); + } + + return request; + } + + protected Map generateSegmentationMap(int segmentSize) { + Map segment = new ConcurrentHashMap<>(); + + for (int i = 0; i < segmentSize; i++) { + String key = random.generateRandomString(8); + + while (segment.containsKey(key)) { + key = random.generateRandomString(8); + } + segment.put(key, random.generateRandomImmutable()); + } + + return segment; + } + + protected Object[] generateRandomEventBase(int segmentSize) { + Object[] values = new Object[5]; + values[0] = random.generateRandomString(8); // key + values[1] = generateSegmentationMap(segmentSize); // segmentation + values[2] = random.generateRandomInt(1000) + 1; // count + values[3] = random.generateRandomDouble() * 1000; // sum + values[4] = random.generateRandomDouble() * 1000; // dur + return values; + } + + protected JSONObject generateRandomEvent(int segmentSize) throws JSONException { + JSONObject event = new JSONObject(); + Object[] values = generateRandomEventBase(segmentSize); + event.put("key", values[0]); + event.put("count", values[2]); + event.put("sum", values[3]); + event.put("dur", values[4]); + UtilsTime.Instant instant = UtilsTime.getCurrentInstant(); + event.put("timestamp", instant.timestampMs); + event.put("hour", instant.hour); + event.put("dow", instant.dow); + + if (segmentSize > 0) { + event.put("segment", values[1]); + } + + return event; + } +} diff --git a/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/MainActivity.java b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/MainActivity.java new file mode 100644 index 0000000..3b7e649 --- /dev/null +++ b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/MainActivity.java @@ -0,0 +1,140 @@ +package ly.count.android.benchmark; + +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; +import android.widget.Switch; +import androidx.appcompat.app.AppCompatActivity; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import ly.count.android.sdk.Countly; + +public class MainActivity extends AppCompatActivity { + Benchmark benchmark; + + int loop = 10; + int segmentSize = 0; + int eventSize = 0; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + benchmark = new Benchmark(); + } + + public void onHardKillPressed(View v) { + int id = android.os.Process.myPid(); + android.os.Process.killProcess(id); + } + + public void onSchedulerPressed(View v) { + readLoopSegmentEventSize(); + futureWrapper(() -> { + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + // Schedule the task to run every 10 seconds + scheduler.scheduleAtFixedRate(() -> { + benchmark.fillRequestQueue(1, eventSize, segmentSize); + BENCHMARK(1, Countly.sharedInstance().requestQueue()::attemptToSendStoredRequests); + }, 0, 10, TimeUnit.SECONDS); + }); + } + + public void onClickFillRequestQueue(View v) { + readLoopSegmentEventSize(); + if (getSwitchValue(R.id.eventQ)) { + futureWrapper(() -> benchmark.fillEventQueue(eventSize, segmentSize)); + } else { + futureWrapper(() -> benchmark.fillRequestQueue(loop, eventSize, segmentSize)); + } + if (getSwitchValue(R.id.wait)) { + futureWrapper(this::standByForOnTimer); + } + } + + private void standByForOnTimer() { + benchmark.print("[MainActivity] standByForOnTimer, wait is true, waiting for onTimer to be called"); + //while (Countly.sharedInstance().standBy.get()) ; + benchmark.print("[MainActivity] standByForOnTimer, standBy is false, sending requests"); + BENCHMARK(loop, null); + } + + public void onClickBenchmark(View v) { + futureWrapper(() -> BENCHMARK(loop, Countly.sharedInstance().requestQueue()::attemptToSendStoredRequests)); + } + + public void onClearCounters(View v) { + benchmark.print("[MainActivity] clear counters"); + App.appPcc.Clear(); + } + + public void onPrintCounters(View v) { + benchmark.print("[MainActivity] print counters"); + String res = App.appPcc.ReturnResults(); + + benchmark.print(res); + } + + public void onClearStorage(View v) { + benchmark.print("[MainActivity] Clear Storage"); + Countly.sharedInstance().requestQueue().flushQueues(); + } + + public void onGenerateBenchmarkData(View v) { + benchmark.print("[MainActivity] Generate benchmark data"); + + readLoopSegmentEventSize(); + + futureWrapper(() -> benchmark.GenerateBenchmarkDataset(eventSize, segmentSize)); + + if (getSwitchValue(R.id.wait)) { + futureWrapper(this::standByForOnTimer); + } + } + + protected void BENCHMARK(int loop, Runnable runnable) { + benchmark.print("------------------------------------------------------------"); + benchmark.print("[MainActivity] BENCHMARK"); + benchmark.print("------------------------------------------------------------"); + benchmark.print("[MainActivity] BENCHMARK, rqSize: " + Benchmark.countlyStore.getRequests().length); + + benchmark.print("[MainActivity] BENCHMARK loop: " + loop + ", events size: " + eventSize + ", segment size: " + segmentSize); + benchmark.print("[MainActivity] BENCHMARK Triggering sending"); + long startTime = System.currentTimeMillis(); + if (runnable != null) { + runnable.run(); + } + while (Benchmark.countlyStore.getRequests().length > 0) ; // wait for RQ to finish sending + long endTime = System.currentTimeMillis(); + benchmark.print("[MainActivity] BENCHMARK, SENDING TOOK: " + (endTime - startTime) + "MS"); + benchmark.print("------------------------------------------------------------"); + + String res = App.appPcc.ReturnResults(); + benchmark.print(res); + } + + private void readLoopSegmentEventSize() { + loop = Integer.parseInt(((EditText) findViewById(R.id.loop)).getText().toString()); + segmentSize = Integer.parseInt(((EditText) findViewById(R.id.segmentSize)).getText().toString()); + eventSize = Integer.parseInt(((EditText) findViewById(R.id.eventSize)).getText().toString()); + } + + private void futureWrapper(Runnable runnable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + CompletableFuture.runAsync(() -> { + try { + runnable.run(); + } catch (Exception e) { + Countly.sharedInstance().L.e("[MainActivity] futureWrapper, Failed to run scheduler", e); + } + }); + } + } + + private boolean getSwitchValue(int id) { + return ((Switch) findViewById(id)).isChecked(); + } +} \ No newline at end of file diff --git a/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/RandomUtil.java b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/RandomUtil.java new file mode 100644 index 0000000..ed5f098 --- /dev/null +++ b/libraries/countly/app-benchmark/src/main/java/ly/count/android/benchmark/RandomUtil.java @@ -0,0 +1,59 @@ +package ly.count.android.benchmark; + +import java.util.Random; + +public class RandomUtil { + private final Random random; + + public RandomUtil() { + random = new Random(); + } + + public String generateRandomString(int size) { + int length = random.nextInt(size) + 1; // Random string length between 1 and 20 + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + char randomChar = (char) (random.nextInt(26) + 'a'); // Random lowercase letter + sb.append(randomChar); + } + return sb.toString(); + } + + public int generateRandomInt(int bound) { + return random.nextInt(bound); + } + + public double generateRandomDouble() { + return random.nextDouble(); + } + + protected Object generateRandomImmutable() { + int randomInt = random.nextInt(6); + Object value; + switch (randomInt) { + case 0: + value = random.nextInt(); + break; + case 1: + value = random.nextBoolean(); + break; + case 2: + value = random.nextLong(); + break; + case 3: + value = random.nextFloat(); + break; + case 4: + value = random.nextDouble(); + break; + case 5: + value = generateRandomString(20); + break; + default: + value = "default"; + break; + } + + return value; + } +} diff --git a/libraries/countly/app-benchmark/src/main/res/drawable-hdpi/ic_launcher.png b/libraries/countly/app-benchmark/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..96a442e Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/drawable-hdpi/ic_message.png b/libraries/countly/app-benchmark/src/main/res/drawable-hdpi/ic_message.png new file mode 100644 index 0000000..57177b7 Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable-hdpi/ic_message.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/drawable-mdpi/ic_launcher.png b/libraries/countly/app-benchmark/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..359047d Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/drawable-mdpi/ic_message.png b/libraries/countly/app-benchmark/src/main/res/drawable-mdpi/ic_message.png new file mode 100644 index 0000000..3072b75 Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable-mdpi/ic_message.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/drawable-xhdpi/ic_launcher.png b/libraries/countly/app-benchmark/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..71c6d76 Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/drawable-xhdpi/ic_message.png b/libraries/countly/app-benchmark/src/main/res/drawable-xhdpi/ic_message.png new file mode 100644 index 0000000..763767b Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable-xhdpi/ic_message.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/drawable-xxhdpi/ic_launcher.png b/libraries/countly/app-benchmark/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..4df1894 Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/drawable-xxhdpi/ic_message.png b/libraries/countly/app-benchmark/src/main/res/drawable-xxhdpi/ic_message.png new file mode 100644 index 0000000..0a79824 Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable-xxhdpi/ic_message.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/drawable-xxxhdpi/ic_message.png b/libraries/countly/app-benchmark/src/main/res/drawable-xxxhdpi/ic_message.png new file mode 100644 index 0000000..fa7c17a Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable-xxxhdpi/ic_message.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/drawable/ic_message.png b/libraries/countly/app-benchmark/src/main/res/drawable/ic_message.png new file mode 100644 index 0000000..57177b7 Binary files /dev/null and b/libraries/countly/app-benchmark/src/main/res/drawable/ic_message.png differ diff --git a/libraries/countly/app-benchmark/src/main/res/layout/activity_main.xml b/libraries/countly/app-benchmark/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..13f076e --- /dev/null +++ b/libraries/countly/app-benchmark/src/main/res/layout/activity_main.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + +