1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.helpers; 18 19 import android.util.Log; 20 21 import androidx.annotation.VisibleForTesting; 22 import androidx.test.InstrumentationRegistry; 23 import androidx.test.uiautomator.UiDevice; 24 25 import java.io.IOException; 26 import java.nio.file.Files; 27 import java.nio.file.Path; 28 import java.nio.file.Paths; 29 import java.util.ArrayList; 30 import java.util.HashMap; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.stream.Collectors; 34 import java.util.stream.Stream; 35 36 /** 37 * Helper to run the generic collector that runs binary files that output metrics in a fixed format. 38 * <a href="http://go/generic-collector">(Design doc)</a> 39 */ 40 public class GenericExecutableCollectorHelper implements ICollectorHelper<String> { 41 private static final String TAG = GenericExecutableCollectorHelper.class.getSimpleName(); 42 private static final String CSV_SEPARATOR = ","; 43 private static final String METRIC_KEY_SEPARATOR = "_"; 44 45 private Path mExecutableDir; 46 private UiDevice mUiDevice; 47 private List<Path> mExecutableFilePaths; 48 49 /** 50 * Setup 51 * 52 * @param executableDir a string of executable directory 53 */ setUp(String executableDir)54 public void setUp(String executableDir) { 55 mExecutableDir = Paths.get(executableDir); 56 mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 57 if (mExecutableDir == null || !Files.isDirectory(mExecutableDir)) { 58 throw new IllegalArgumentException( 59 "Executable directory was not a directory or was not specified."); 60 } 61 mExecutableFilePaths = listFilesInAllSubdirs(mExecutableDir); 62 Log.i( 63 TAG, 64 String.format( 65 "Found the following files: %s", 66 mExecutableFilePaths.stream() 67 .map(Path::toString) 68 .collect(Collectors.joining(", ")))); 69 if (mExecutableFilePaths.isEmpty()) { 70 throw new IllegalArgumentException( 71 String.format("No test file found in the directory %s", mExecutableDir)); 72 } 73 } 74 75 @Override startCollecting()76 public boolean startCollecting() { 77 return true; 78 } 79 80 @Override getMetrics()81 public Map<String, String> getMetrics() { 82 Map<String, String> results = new HashMap<>(); 83 mExecutableFilePaths.forEach( 84 (path) -> { 85 try { 86 results.putAll(execAndGetResults(path)); 87 } catch (IOException e) { 88 Log.e(TAG, String.format("Failed to execute file: %s", path), e); 89 } 90 }); 91 return results; 92 } 93 94 @Override stopCollecting()95 public boolean stopCollecting() { 96 return true; 97 } 98 99 /** 100 * List all files from a directory, including all levels of sub-directories. 101 * 102 * @param dir: a path of directory 103 * @return return: a list of paths of executable files 104 */ listFilesInAllSubdirs(Path dir)105 private List<Path> listFilesInAllSubdirs(Path dir) { 106 List<Path> result = new ArrayList<>(); 107 try (Stream<Path> allFilesAndDirs = Files.walk(dir)) { 108 result = allFilesAndDirs.filter(Files::isRegularFile).collect(Collectors.toList()); 109 } catch (IOException e) { 110 Log.e(TAG, String.format("Failed to walk the files under path %s", dir), e); 111 } 112 return result; 113 } 114 115 /** 116 * Running the binary by shell command and reformatting the output. 117 * 118 * <p>Example of output = "name,binder_use,binder_started,count\n" + "DockObserver,0,32,2\n" + 119 * "SurfaceFlinger,0,5,8\n" + "SurfaceFlingerAIDL,0,5,8\n"; 120 * 121 * <p>Example of lines = ["name,binder_use,binder_started,count", "DockObserver,0,32,2", 122 * "SurfaceFlinger,0,5,8", "SurfaceFlingerAIDL,0,5,8"] 123 * 124 * <p>Example of headers = ["name", "binder_use", "binder_started", "count"] 125 * 126 * <p>Example of result = { "DockObserver_binder_use" : 0 "DockObserver_binder_started" : 32 127 * "DockObserver_count" : 2 } 128 * 129 * @param executable: a path of the executable file path 130 * @return result: a map including the metrics and values from the output 131 * @throws IOException if the shell command runs into errors 132 */ execAndGetResults(Path executable)133 private Map<String, String> execAndGetResults(Path executable) throws IOException { 134 String prefix = mExecutableDir.relativize(executable).toString(); 135 Map<String, String> result = new HashMap<>(); 136 String output = executeShellCommand(executable.toString()); 137 if (output.length() <= 0) { 138 return result; 139 } 140 String[] lines = output.split(System.lineSeparator()); 141 String[] headers = lines[0].split(CSV_SEPARATOR); 142 for (int row = 1; row < lines.length; row++) { 143 String[] l = lines[row].split(CSV_SEPARATOR); 144 for (int col = 1; col < l.length; col++) { 145 result.put(String.join(METRIC_KEY_SEPARATOR, prefix, l[0], headers[col]), l[col]); 146 } 147 } 148 return result; 149 } 150 151 /** 152 * Execute a shell command and return its output. 153 * 154 * @param command a string of command 155 */ 156 @VisibleForTesting executeShellCommand(String command)157 public String executeShellCommand(String command) throws IOException { 158 return mUiDevice.executeShellCommand(command); 159 } 160 } 161