[RESTRICT AUTOMERGE][CarTelemetryService] Add memory metrics test to kitchen sink

Bug: 202047696
Test: manual testing, the steps are also listed in the bug
1. Open kitchensink app & navigate to Telemetry tests
2. Click on Send MetricsConfig on process memory
3. Wait for 10 minutes
4. Click on Get Report process memory
5. verify that memory metrics data is available
6. Verify that metrics configuration is already deleted by
adding the same config again. If config is not deleted,
the status code should be 1, otherwise the status code should
be 0. Alternatively you can click on remove metrics config
and check logcat

Change-Id: I891d6fae8bb11ef9d9c3503361f16b95429e5279
diff --git a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
index 1005b03..00481fe 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
@@ -41,6 +41,7 @@
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.time.Duration;
@@ -149,6 +150,8 @@
     private PersistableBundle loadBundle() {
         try (FileInputStream fileInputStream = new FileInputStream(mSavedStatsConfigsFile)) {
             return PersistableBundle.readFromStream(fileInputStream);
+        } catch (FileNotFoundException e) {
+            return new PersistableBundle();
         } catch (IOException e) {
             // TODO(b/199947533): handle failure
             Slog.e(CarLog.TAG_TELEMETRY,
@@ -159,6 +162,10 @@
 
     /** Writes the PersistableBundle containing stats config keys and versions to disk. */
     private void saveBundle() {
+        if (mSavedStatsConfigs.size() == 0) {
+            mSavedStatsConfigsFile.delete();
+            return;
+        }
         try (FileOutputStream fileOutputStream = new FileOutputStream(mSavedStatsConfigsFile)) {
             mSavedStatsConfigs.writeToStream(fileOutputStream);
         } catch (IOException e) {
@@ -175,13 +182,14 @@
         Preconditions.checkArgument(
                 publisherParam.getPublisherCase() == PublisherCase.STATS,
                 "Subscribers only with StatsPublisher are supported by this class.");
-
         synchronized (mLock) {
             long configKey = addStatsConfigLocked(subscriber);
             mConfigKeyToSubscribers.put(configKey, subscriber);
         }
 
         if (!mIsPullingReports.getAndSet(true)) {
+            Slog.d(CarLog.TAG_TELEMETRY, "Stats report will be pulled in "
+                    + PULL_REPORTS_PERIOD.toMinutes() + " minutes.");
             mTelemetryHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
         }
     }
@@ -246,6 +254,8 @@
         }
 
         if (mIsPullingReports.get()) {
+            Slog.d(CarLog.TAG_TELEMETRY, "Stats report will be pulled in "
+                    + PULL_REPORTS_PERIOD.toMinutes() + " minutes.");
             mTelemetryHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
         }
     }
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
index 2576b7d..3110d82 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
@@ -39,6 +39,29 @@
             android:layout_height="wrap_content"
             android:text="@string/get_on_gear_change"/>
     </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/on_process_memory_state_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/send_on_process_memory_config"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/send_process_memory_state_config"/>
+        <Button
+            android:id="@+id/remove_on_process_memory_config"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/remove_process_memory_state_config"/>
+        <Button
+            android:id="@+id/get_on_process_memory_report"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/get_report_process_memory_state_config"/>
+    </LinearLayout>
+
     <TextView
         android:id="@+id/output_textview"
         android:layout_width="wrap_content"
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index d43e715..77e3989 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -385,5 +385,8 @@
     <!-- CarTelemetryService Test -->
     <string name="send_on_gear_change" translatable="false">Send MetricsConfig on_gear_change</string>
     <string name="remove_on_gear_change" translatable="false">Remove MetricsConfig on_gear_change</string>
-    <string name="get_on_gear_change" translatable="false">Get on_gear_change Report</string>
+    <string name="get_on_gear_change" translatable="false">Get Report on_gear_change</string>
+    <string name="send_process_memory_state_config" translatable="false">Send MetricsConfig process_memory</string>
+    <string name="remove_process_memory_state_config" translatable="false">Remove MetricsConfig process_memory</string>
+    <string name="get_report_process_memory_state_config" translatable="false">Get Report process_memory</string>
 </resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
index 13392b4..5a76abc 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
@@ -16,6 +16,8 @@
 
 package com.google.android.car.kitchensink.telemetry;
 
+import static com.android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.PROCESS_MEMORY_STATE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.telemetry.CarTelemetryManager;
@@ -35,6 +37,7 @@
 
 import com.google.android.car.kitchensink.KitchenSinkActivity;
 import com.google.android.car.kitchensink.R;
+import com.google.protobuf.InvalidProtocolBufferException;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -42,6 +45,8 @@
 import java.util.concurrent.Executors;
 
 public class CarTelemetryTestFragment extends Fragment {
+
+    /** Hello World test from vehicle property publisher by injecting gear change. */
     private static final String LUA_SCRIPT_ON_GEAR_CHANGE =
             "function onGearChange(published_data, state)\n"
                     + "    result = {data = \"Hello World!\"}\n"
@@ -55,23 +60,69 @@
                                     .setReadRate(0f)
                                     .build()
                     ).build();
-    private static final TelemetryProto.Subscriber VEHICLE_PROPERTY_SUBSCRIBER =
-            TelemetryProto.Subscriber.newBuilder()
-                    .setHandler("onGearChange")
-                    .setPublisher(VEHICLE_PROPERTY_PUBLISHER)
-                    .setPriority(0)
-                    .build();
     private static final TelemetryProto.MetricsConfig METRICS_CONFIG_ON_GEAR_CHANGE_V1 =
             TelemetryProto.MetricsConfig.newBuilder()
                     .setName("my_metrics_config")
                     .setVersion(1)
                     .setScript(LUA_SCRIPT_ON_GEAR_CHANGE)
-                    .addSubscribers(VEHICLE_PROPERTY_SUBSCRIBER)
+                    .addSubscribers(
+                            TelemetryProto.Subscriber.newBuilder()
+                                    .setHandler("onGearChange")
+                                    .setPublisher(VEHICLE_PROPERTY_PUBLISHER)
+                                    .setPriority(100)) // low priority
                     .build();
-    private static final MetricsConfigKey KEY_V1 = new MetricsConfigKey(
+    private static final MetricsConfigKey ON_GEAR_CHANGE_KEY_V1 = new MetricsConfigKey(
             METRICS_CONFIG_ON_GEAR_CHANGE_V1.getName(),
             METRICS_CONFIG_ON_GEAR_CHANGE_V1.getVersion());
 
+    /** Memory metrics test. */
+    private static final String LUA_SCRIPT_ON_PROCESS_MEMORY_STATE = new StringBuilder()
+            .append("function calculateAverage(tbl)\n")
+            .append("    sum = 0\n")
+            .append("    size = 0\n")
+            .append("    for _, value in ipairs(tbl) do\n")
+            .append("        sum = sum + value\n")
+            .append("        size = size + 1\n")
+            .append("    end\n")
+            .append("    return sum/size\n")
+            .append("end\n")
+            .append("function onProcessMemory(published_data, state)\n")
+            .append("    result = {}\n")
+            .append("    result.page_fault_avg = calculateAverage(published_data.page_fault)\n")
+            .append("    result.major_page_fault_avg = calculateAverage("
+                    + "published_data.page_major_fault)\n")
+            .append("    result.oom_adj_score_avg = calculateAverage("
+                    + "published_data.oom_adj_score)\n")
+            .append("    result.rss_in_bytes_avg = calculateAverage(published_data.rss_in_bytes)\n")
+            .append("    result.swap_in_bytes_avg = calculateAverage("
+                    + "published_data.swap_in_bytes)\n")
+            .append("    result.cache_in_bytes_avg = calculateAverage("
+                    + "published_data.cache_in_bytes)\n")
+            .append("    on_script_finished(result)\n")
+            .append("end\n")
+            .toString();
+
+    private static final TelemetryProto.Publisher PROCESS_MEMORY_PUBLISHER =
+            TelemetryProto.Publisher.newBuilder()
+                    .setStats(
+                            TelemetryProto.StatsPublisher.newBuilder()
+                                    .setSystemMetric(PROCESS_MEMORY_STATE)
+                    ).build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_PROCESS_MEMORY_V1 =
+            TelemetryProto.MetricsConfig.newBuilder()
+                    .setName("process_memory_metrics_config")
+                    .setVersion(1)
+                    .setScript(LUA_SCRIPT_ON_PROCESS_MEMORY_STATE)
+                    .addSubscribers(
+                            TelemetryProto.Subscriber.newBuilder()
+                                    .setHandler("onProcessMemory")
+                                    .setPublisher(PROCESS_MEMORY_PUBLISHER)
+                                    .setPriority(0)) // high priority
+                    .build();
+    private static final MetricsConfigKey PROCESS_MEMORY_KEY_V1 = new MetricsConfigKey(
+            METRICS_CONFIG_PROCESS_MEMORY_V1.getName(),
+            METRICS_CONFIG_PROCESS_MEMORY_V1.getVersion());
+
     private final Executor mExecutor = Executors.newSingleThreadExecutor();
 
     private CarTelemetryManager mCarTelemetryManager;
@@ -105,6 +156,15 @@
         removeGearConfigBtn.setOnClickListener(this::onRemoveGearChangeConfigBtnClick);
         getGearReportBtn.setOnClickListener(this::onGetGearChangeReportBtnClick);
 
+        mOutputTextView = view.findViewById(R.id.output_textview);
+        Button sendProcessMemConfigBtn = view.findViewById(R.id.send_on_process_memory_config);
+        Button getProcessMemReportBtn = view.findViewById(R.id.get_on_process_memory_report);
+        Button removeProcessMemConfigBtn = view.findViewById(R.id.remove_on_process_memory_config);
+
+        sendProcessMemConfigBtn.setOnClickListener(this::onSendProcessMemoryConfigBtnClick);
+        removeProcessMemConfigBtn.setOnClickListener(this::onRemoveProcessMemoryConfigBtnClick);
+        getProcessMemReportBtn.setOnClickListener(this::onGetProcessMemoryReportBtnClick);
+
         return view;
     }
 
@@ -114,19 +174,36 @@
 
     private void onSendGearChangeConfigBtnClick(View view) {
         showOutput("Sending MetricsConfig that listen for gear change...");
-        mCarTelemetryManager.addMetricsConfig(KEY_V1,
+        mCarTelemetryManager.addMetricsConfig(ON_GEAR_CHANGE_KEY_V1,
                 METRICS_CONFIG_ON_GEAR_CHANGE_V1.toByteArray());
     }
 
     private void onRemoveGearChangeConfigBtnClick(View view) {
         showOutput("Removing MetricsConfig that listens for gear change...");
-        mCarTelemetryManager.removeMetricsConfig(KEY_V1);
+        mCarTelemetryManager.removeMetricsConfig(ON_GEAR_CHANGE_KEY_V1);
     }
 
     private void onGetGearChangeReportBtnClick(View view) {
         showOutput("Fetching report... If nothing shows up after a few seconds, "
                 + "then no result exists");
-        mCarTelemetryManager.sendFinishedReports(KEY_V1);
+        mCarTelemetryManager.sendFinishedReports(ON_GEAR_CHANGE_KEY_V1);
+    }
+
+    private void onSendProcessMemoryConfigBtnClick(View view) {
+        showOutput("Sending MetricsConfig that listen for process memory state...");
+        mCarTelemetryManager.addMetricsConfig(PROCESS_MEMORY_KEY_V1,
+                METRICS_CONFIG_PROCESS_MEMORY_V1.toByteArray());
+    }
+
+    private void onRemoveProcessMemoryConfigBtnClick(View view) {
+        showOutput("Removing MetricsConfig that listens for process memory state...");
+        mCarTelemetryManager.removeMetricsConfig(PROCESS_MEMORY_KEY_V1);
+    }
+
+    private void onGetProcessMemoryReportBtnClick(View view) {
+        showOutput("Fetching report for process memory state... If nothing shows up after "
+                + "a few seconds, then no result exists");
+        mCarTelemetryManager.sendFinishedReports(PROCESS_MEMORY_KEY_V1);
     }
 
     @Override
@@ -155,6 +232,14 @@
 
         @Override
         public void onError(@NonNull MetricsConfigKey key, @NonNull byte[] error) {
+            try {
+                TelemetryProto.TelemetryError telemetryError =
+                        TelemetryProto.TelemetryError.parseFrom(error);
+                showOutput("Error is " + telemetryError);
+            } catch (InvalidProtocolBufferException e) {
+                showOutput("Unable to parse error result for MetricsConfig " + key.getName()
+                        + ". " + e.getMessage());
+            }
         }
 
         @Override