Create LyricCpuUtilizationCollector for collecting Lyric CPU utilization metrics.

Added test in ag/14494388

Bug: 159682839
Test: atest
Test: forrest sponge2/6aa83458-2424-498b-90d7-27747a0c6268
Change-Id: Icfd85a25f1ab70fe3826ca98ce8391c2980c2668
diff --git a/libraries/collectors-helper/lyric/Android.bp b/libraries/collectors-helper/lyric/Android.bp
new file mode 100644
index 0000000..811caca
--- /dev/null
+++ b/libraries/collectors-helper/lyric/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Used for collecting Lyric specific metrics..
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "lyric-metric-helper",
+    defaults: ["tradefed_errorprone_defaults"],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.uiautomator",
+        "collector-helper-utilities",
+        "guava",
+    ],
+
+    sdk_version: "current",
+}
diff --git a/libraries/collectors-helper/lyric/src/com/android/helpers/LyricCpuUtilizationHelper.java b/libraries/collectors-helper/lyric/src/com/android/helpers/LyricCpuUtilizationHelper.java
new file mode 100644
index 0000000..3fadc06
--- /dev/null
+++ b/libraries/collectors-helper/lyric/src/com/android/helpers/LyricCpuUtilizationHelper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.helpers;
+
+import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This is a collector helper that collects the dumpsys output for specified services and puts them
+ * into files.
+ */
+public class LyricCpuUtilizationHelper implements ICollectorHelper<Double> {
+
+    private static final String TAG = LyricCpuUtilizationHelper.class.getSimpleName();
+
+    private static final String DUMPSYS_CMD = "dumpsys media.camera";
+
+    private static final Pattern TIME_REGEX_PATTERN = Pattern.compile("(\\d+\\.?\\d*)(ms|us|)");
+
+    private static final String TIME_REGEX = "(\\d+\\.?\\d*)(?:ms|us|)";
+
+    private static final Pattern CPU_USAGE_PATTERN =
+            Pattern.compile(
+                    String.format(
+                            "CPU Usage during ProcessInput for \\[(?:>|\\s)] p\\d+ (.*) after"
+                                + " (\\d+) invocations - User: %s \\(Max: %s Min:%s\\) System: %s"
+                                + " \\(Max: %s Min:%s\\) Wall: %s \\(Max: %s Min:%s\\)",
+                            TIME_REGEX,
+                            TIME_REGEX,
+                            TIME_REGEX,
+                            TIME_REGEX,
+                            TIME_REGEX,
+                            TIME_REGEX,
+                            TIME_REGEX,
+                            TIME_REGEX,
+                            TIME_REGEX));
+
+    private static final String METRIC_KEY = "cpu_util_%s_%s";
+
+    private UiDevice mUiDevice;
+
+    @Override
+    public boolean startCollecting() {
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        return true;
+    }
+
+    @Override
+    public Map<String, Double> getMetrics() {
+        Map<String, Double> metrics = new HashMap<>();
+        try {
+            String res = mUiDevice.executeShellCommand(DUMPSYS_CMD);
+            BufferedReader bufReader = new BufferedReader(new StringReader(res));
+            String line = bufReader.readLine();
+            while (line != null) {
+                metrics.putAll(processLine(line));
+                line = bufReader.readLine();
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to collect Lyric CPU metrics.");
+        }
+        return metrics;
+    }
+
+    @Override
+    public boolean stopCollecting() {
+        return true;
+    }
+
+    @VisibleForTesting
+    static Map<String, Double> processLine(String line) {
+        Matcher matcher = CPU_USAGE_PATTERN.matcher(line);
+        Map<String, Double> metrics = new HashMap<>();
+        if (!matcher.find()) {
+            return metrics;
+        }
+        String node = matcher.group(1).replace(":", "-");
+        metrics.put(
+                String.format(METRIC_KEY, node, "number_of_invocations"),
+                Double.parseDouble(matcher.group(2)));
+        metrics.put(String.format(METRIC_KEY, node, "user_time"), parseTime(matcher.group(3)));
+        metrics.put(String.format(METRIC_KEY, node, "user_time_max"), parseTime(matcher.group(4)));
+        metrics.put(String.format(METRIC_KEY, node, "user_time_min"), parseTime(matcher.group(5)));
+        metrics.put(String.format(METRIC_KEY, node, "system_time"), parseTime(matcher.group(6)));
+        metrics.put(
+                String.format(METRIC_KEY, node, "system_time_max"), parseTime(matcher.group(7)));
+        metrics.put(
+                String.format(METRIC_KEY, node, "system_time_min"), parseTime(matcher.group(8)));
+        metrics.put(String.format(METRIC_KEY, node, "wall_time"), parseTime(matcher.group(9)));
+        metrics.put(String.format(METRIC_KEY, node, "wall_time_max"), parseTime(matcher.group(10)));
+        metrics.put(String.format(METRIC_KEY, node, "wall_time_min"), parseTime(matcher.group(11)));
+
+        return metrics;
+    }
+
+    private static Double parseTime(String timeString) {
+        Matcher matcher = TIME_REGEX_PATTERN.matcher(timeString);
+        if (!matcher.find()) {
+            throw new IllegalArgumentException("Time string does not match the expected format.");
+        }
+        double value = Double.parseDouble(matcher.group(1));
+        String unit = matcher.group(2);
+        if (unit.equals("us")) {
+            value /= 1000;
+        }
+        return value;
+    }
+}
diff --git a/libraries/collectors-helper/lyric/test/Android.bp b/libraries/collectors-helper/lyric/test/Android.bp
new file mode 100644
index 0000000..4e604b7
--- /dev/null
+++ b/libraries/collectors-helper/lyric/test/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "lyric-helper-test",
+    defaults: ["tradefed_errorprone_defaults"],
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "androidx.test.runner",
+        "junit",
+        "mockito-target",
+        "lyric-metric-helper",
+    ],
+
+    sdk_version: "current",
+}
diff --git a/libraries/collectors-helper/lyric/test/src/com/android/helpers/LyricCpuUtilizationHelperTest.java b/libraries/collectors-helper/lyric/test/src/com/android/helpers/LyricCpuUtilizationHelperTest.java
new file mode 100644
index 0000000..0a0bc91
--- /dev/null
+++ b/libraries/collectors-helper/lyric/test/src/com/android/helpers/LyricCpuUtilizationHelperTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.helpers;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+/**
+ * Android unit test for {@link LyricCpuUtilizationHelper}
+ *
+ * <p>To run: atest CollectorsHelperTest:com.android.helpers.tests.LyricCpuUtilizationHelper
+ */
+@RunWith(AndroidJUnit4.class)
+public class LyricCpuUtilizationHelperTest {
+
+    private static final String METRIC_KEY = "cpu_util_%s_%s";
+
+    @Test
+    public void testProcessLine() {
+        String testString =
+                "CPU Usage during ProcessInput for [>] p2 cam2_retiming:empty_group after 593"
+                        + " invocations - User: 1.709118ms (Max: 3.67ms Min:0) System: 425.17025us"
+                        + " (Max: 3.372ms Min:0) Wall: 50.14003675ms (Max: 55.676595ms"
+                        + " Min:50.046468ms)";
+
+        Map<String, Double> metrics = LyricCpuUtilizationHelper.processLine(testString);
+
+        String node = "cam2_retiming-empty_group";
+        assertEquals(
+                Double.valueOf(593),
+                metrics.get(String.format(METRIC_KEY, node, "number_of_invocations")));
+        assertEquals(
+                Double.valueOf(1.709118),
+                metrics.get(String.format(METRIC_KEY, node, "user_time")));
+        assertEquals(
+                Double.valueOf(3.67),
+                metrics.get(String.format(METRIC_KEY, node, "user_time_max")));
+        assertEquals(
+                Double.valueOf(0), metrics.get(String.format(METRIC_KEY, node, "user_time_min")));
+        assertEquals(
+                Double.valueOf(425.17025),
+                metrics.get(String.format(METRIC_KEY, node, "system_time")));
+        assertEquals(
+                Double.valueOf(3.372),
+                metrics.get(String.format(METRIC_KEY, node, "system_time_max")));
+        assertEquals(
+                Double.valueOf(0), metrics.get(String.format(METRIC_KEY, node, "system_time_min")));
+        assertEquals(
+                Double.valueOf(50.14003675),
+                metrics.get(String.format(METRIC_KEY, node, "wall_time")));
+        assertEquals(
+                Double.valueOf(55.676595),
+                metrics.get(String.format(METRIC_KEY, node, "wall_time_max")));
+        assertEquals(
+                Double.valueOf(50.046468),
+                metrics.get(String.format(METRIC_KEY, node, "wall_time_min")));
+    }
+}
diff --git a/libraries/device-collectors/src/main/Android.bp b/libraries/device-collectors/src/main/Android.bp
index 33cd7ba..f9bee71 100644
--- a/libraries/device-collectors/src/main/Android.bp
+++ b/libraries/device-collectors/src/main/Android.bp
@@ -28,6 +28,7 @@
         "androidx.test.uiautomator",
         "jank-helper",
         "junit",
+        "lyric-metric-helper",
         "memory-helper",
         "perfetto-helper",
         "power-helper",
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/LyricCpuUtilizationCollector.java b/libraries/device-collectors/src/main/java/android/device/collectors/LyricCpuUtilizationCollector.java
new file mode 100644
index 0000000..b9719a0
--- /dev/null
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/LyricCpuUtilizationCollector.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.device.collectors;
+
+import android.device.collectors.annotations.OptionClass;
+
+import com.android.helpers.LyricCpuUtilizationHelper;
+
+@OptionClass(alias = "lyric-cpu-utilization-collector")
+public class LyricCpuUtilizationCollector extends BaseCollectionListener<Double> {
+
+    public LyricCpuUtilizationCollector() {
+        createHelperInstance(new LyricCpuUtilizationHelper());
+    }
+}