Update ImageStats test.

1) Add option to process more than one stats file name.

2) Metrics collected from files will be posted under the test
   named after the file name.

3) In addition to aggregate metrics, collect all the individual file
   sizes. Individual file sizes will be used at the time of debugging.

Parsing the json file will be taken care in the follow up CL.

Bug: b/203247205

Test: ImageStatsTest
Change-Id: Icd37cc3621d3e5e6e853628e36679a1c2e68c643
diff --git a/src/com/android/build/tests/ImageStats.java b/src/com/android/build/tests/ImageStats.java
index 03aa574..6f59f5c 100644
--- a/src/com/android/build/tests/ImageStats.java
+++ b/src/com/android/build/tests/ImageStats.java
@@ -27,6 +27,7 @@
 import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.proto.TfMetricProtoUtil;
 
 import java.io.BufferedReader;
@@ -38,6 +39,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -64,7 +66,7 @@
                             + "download it), otherwise it refers to a local file, typically used for debugging "
                             + "purposes",
             mandatory = true)
-    private String mStatsFileName = null;
+    private Set<String> mStatsFileNames = new HashSet<>();
 
     @Option(
             name = "file-from-build-info",
@@ -109,31 +111,31 @@
     @Override
     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
         File statsFile;
-        if (mFileFromBuildInfo) {
-            statsFile = mBuildInfo.getFile(mStatsFileName);
-        } else {
-            statsFile = new File(mStatsFileName);
-        }
-        long start = System.currentTimeMillis();
-        Map<String, String> fileSizes = null;
         // fixed run name, 1 test to run
-        listener.testRunStarted("image-stats", 1);
-        if (statsFile == null || !statsFile.exists()) {
-            throw new RuntimeException(
-                    "Invalid image stats file (<null>) specified or it does not exist.");
-        } else {
-            TestDescription td = new TestDescription(ImageStats.class.getName(), FILE_SIZES);
+        long start = System.currentTimeMillis();
+        listener.testRunStarted("image-stats-run", 1);
+        for (String statsFileName : mStatsFileNames) {
+            if (mFileFromBuildInfo) {
+                statsFile = mBuildInfo.getFile(statsFileName);
+            } else {
+                statsFile = new File(statsFileName);
+            }
+            Map<String, String> finalFileSizeMetrics = new HashMap<>();
+            if (statsFile == null || !statsFile.exists()) {
+                throw new RuntimeException(
+                        "Invalid image stats file (<null>) specified or it does not exist.");
+            }
+            // Use stats file name to uniquely identify the test and post only the metrics
+            // collected from that file under the test.
+            TestDescription td = new TestDescription(FileUtil.getBaseName(statsFileName),
+                    FILE_SIZES);
             listener.testStarted(td);
             try (InputStream in = new FileInputStream(statsFile)) {
-                fileSizes =
-                        performAggregation(
-                                parseFileSizes(in),
-                                processAggregationPatterns(mAggregationPattern));
+                parseFinalMetrics(in, finalFileSizeMetrics);
             } catch (IOException ioe) {
-                String message =
-                        String.format(
-                                "Failed to parse image stats file: %s",
-                                statsFile.getAbsolutePath());
+                String message = String.format(
+                        "Failed to parse image stats file: %s",
+                        statsFile.getAbsolutePath());
                 CLog.e(message);
                 CLog.e(ioe);
                 listener.testFailed(td, ioe.toString());
@@ -143,19 +145,39 @@
                         System.currentTimeMillis() - start, new HashMap<String, Metric>());
                 throw new RuntimeException(message, ioe);
             }
-            String logOutput = String.format("File sizes: %s", fileSizes.toString());
+            String logOutput = String.format("File sizes: %s", finalFileSizeMetrics.toString());
             if (mFileFromBuildInfo) {
                 CLog.v(logOutput);
             } else {
                 // assume local debug, print outloud
                 CLog.logAndDisplay(Log.LogLevel.VERBOSE, logOutput);
             }
-            listener.testEnded(td, TfMetricProtoUtil.upgradeConvert(fileSizes));
+            listener.testEnded(td, TfMetricProtoUtil.upgradeConvert(finalFileSizeMetrics));
+
         }
         listener.testRunEnded(System.currentTimeMillis() - start, new HashMap<String, Metric>());
     }
 
     /**
+     * Parse the aggregated metrics that matches the patterns and all individual file size metrics.
+     *
+     * @param in an unread {@link InputStream} for the content of the file sizes; the stream will be
+     *     fully read after executing the method
+     * @param finalFileSizeMetrics final map that will have all the metrics of aggregated and
+     *     individual file names and their corresponding values.
+     * @throws IOException
+     */
+    protected void parseFinalMetrics(InputStream in,
+            Map<String, String> finalFileSizeMetrics) throws IOException {
+        Map<String, Long> individualFileSizes = parseFileSizes(in);
+        // Add aggregated metrics.
+        finalFileSizeMetrics.putAll(performAggregation(individualFileSizes,
+                processAggregationPatterns(mAggregationPattern)));
+        // Add individual file size metrics.
+        finalFileSizeMetrics.putAll(convertMestricsToString(individualFileSizes));
+    }
+
+    /**
      * Processes text files like 'installed-files.txt' (as built by standard Android build rules for
      * device targets) into a map of file path to file sizes
      *
@@ -304,4 +326,19 @@
         ret.put(LABEL_CATEGORIZED, Long.toString(total - uncategorized));
         return ret;
     }
+
+    /**
+     * Convert the metric type to String which is compatible for posting the results.
+     *
+     * @param allIndividualFileSizeMetrics
+     * @return
+     */
+    private Map<String, String> convertMestricsToString(
+            Map<String, Long> allIndividualFileSizeMetrics) {
+        Map<String, String> compatibleMetrics = new HashMap<>();
+        for (Entry<String, Long> fileSizeEntry : allIndividualFileSizeMetrics.entrySet()) {
+            compatibleMetrics.put(fileSizeEntry.getKey(), String.valueOf(fileSizeEntry.getValue()));
+        }
+        return compatibleMetrics;
+    }
 }
diff --git a/tests/src/com/android/build/tests/ImageStatsTest.java b/tests/src/com/android/build/tests/ImageStatsTest.java
index 3cc9e1d..aca5642 100644
--- a/tests/src/com/android/build/tests/ImageStatsTest.java
+++ b/tests/src/com/android/build/tests/ImageStatsTest.java
@@ -44,6 +44,9 @@
                     + "      500380  /system/fonts/NotoSansCuneiform-Regular.ttf\n"
                     + "      126391  /system/framework/core-oj.jar\n"
                     + "      122641  /system/framework/com.quicinc.cne.jar\n";
+    private static final String TEST_DATA_SMALL =
+            "   164424453  /system/app/WallpapersBReel2017/WallpapersBReel2017.apk\n"
+                    + "   124082279  /system/app/Chrome/Chrome.apk\n";
     private static final Map<String, Long> PARSED_TEST_DATA = new HashMap<>();
 
     static {
@@ -147,4 +150,33 @@
                 "385519430",
                 ret.get("total"));
     }
+
+    /** Verifies all the individual file size metrics are added as expected.*/
+    @Test
+    public void testParseFinalMetrics() throws Exception {
+        Map<String, String> finalMetrics = new HashMap<>();
+        mImageStats.parseFinalMetrics(new ByteArrayInputStream(TEST_DATA_SMALL.getBytes()),
+                finalMetrics);
+        Assert.assertEquals("Total number of metrics is not as expected", 5, finalMetrics.size());
+        Assert.assertEquals(
+                "Failed to get WallpapersBReel2017.apk file metris.",
+                "164424453",
+                finalMetrics.get("/system/app/WallpapersBReel2017/WallpapersBReel2017.apk"));
+        Assert.assertEquals(
+                "Failed to get Chrome.apk file metris'",
+                "124082279",
+                finalMetrics.get("/system/app/Chrome/Chrome.apk"));
+        Assert.assertEquals(
+                "failed to verify aggregated size for category 'categorized'",
+                "0",
+                finalMetrics.get("categorized"));
+        Assert.assertEquals(
+                "failed to verify aggregated size for category 'uncategorized'",
+                "288506732",
+                finalMetrics.get("uncategorized"));
+        Assert.assertEquals(
+                "failed to verify aggregated size for category 'total'",
+                "288506732",
+                finalMetrics.get("total"));
+    }
 }