Merge "[RESTRICT AUTOMERGE][CarTelemetryService] Add memory metrics test to kitchen sink" into sc-v2-dev
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