1/* 2 * Copyright (C) 2017 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 */ 16package com.android.statsd.loadtest; 17 18import android.annotation.Nullable; 19import android.content.Context; 20import android.os.Environment; 21import android.util.Log; 22import android.os.Debug; 23 24import com.android.internal.os.StatsdConfigProto.TimeUnit; 25import java.io.BufferedReader; 26import java.io.Closeable; 27import java.io.File; 28import java.io.FileInputStream; 29import java.io.FileOutputStream; 30import java.io.FileWriter; 31import java.io.InputStreamReader; 32import java.io.IOException; 33import java.text.SimpleDateFormat; 34import java.util.Date; 35 36public abstract class PerfDataRecorder { 37 private static final String TAG = "loadtest.PerfDataRecorder"; 38 39 protected final String mTimeAsString; 40 protected final String mColumnSuffix; 41 42 protected PerfDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs, 43 int burst, boolean includeCountMetric, boolean includeDurationMetric, 44 boolean includeEventMetric, boolean includeValueMetric, boolean includeGaugeMetric) { 45 mTimeAsString = new SimpleDateFormat("YYYY_MM_dd_HH_mm_ss").format(new Date()); 46 mColumnSuffix = getColumnSuffix(placebo, replication, bucket, periodSecs, burst, 47 includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric, 48 includeGaugeMetric); 49 } 50 51 /** Starts recording performance data. */ 52 public abstract void startRecording(Context context); 53 54 /** Called periodically. For the recorder to sample data, if needed. */ 55 public abstract void onAlarm(Context context); 56 57 /** Stops recording performance data, and writes it to disk. */ 58 public abstract void stopRecording(Context context); 59 60 /** Runs the dumpsys command. */ 61 protected void runDumpsysStats(Context context, String dumpFilename, String cmd, 62 String... args) { 63 boolean success = false; 64 // Call dumpsys Dump statistics to a file. 65 FileOutputStream fo = null; 66 try { 67 fo = context.openFileOutput(dumpFilename, Context.MODE_PRIVATE); 68 if (!Debug.dumpService(cmd, fo.getFD(), args)) { 69 Log.w(TAG, "Dumpsys failed."); 70 } 71 success = true; 72 } catch (IOException | SecurityException | NullPointerException e) { 73 // SecurityException may occur when trying to dump multi-user info. 74 // NPE can occur during dumpService (root cause unknown). 75 throw new RuntimeException(e); 76 } finally { 77 closeQuietly(fo); 78 } 79 } 80 81 /** 82 * Reads a text file and parses each line, one by one. The result of the parsing is stored 83 * in the passed {@link StringBuffer}. 84 */ 85 protected void readDumpData(Context context, String dumpFilename, PerfParser parser, 86 StringBuilder sb) { 87 FileInputStream fi = null; 88 BufferedReader br = null; 89 try { 90 fi = context.openFileInput(dumpFilename); 91 br = new BufferedReader(new InputStreamReader(fi)); 92 String line = br.readLine(); 93 while (line != null) { 94 String recordLine = parser.parseLine(line); 95 if (recordLine != null) { 96 sb.append(recordLine).append('\n'); 97 } 98 line = br.readLine(); 99 } 100 } catch (IOException e) { 101 throw new RuntimeException(e); 102 } finally { 103 closeQuietly(br); 104 } 105 } 106 107 /** Writes CSV data to a file. */ 108 protected void writeData(Context context, String filePrefix, String columnPrefix, 109 StringBuilder sb) { 110 File dataFile = new File(getStorageDir(), filePrefix + mTimeAsString + ".csv"); 111 112 FileWriter writer = null; 113 try { 114 writer = new FileWriter(dataFile); 115 writer.append(columnPrefix + mColumnSuffix + "\n"); 116 writer.append(sb.toString()); 117 writer.flush(); 118 Log.d(TAG, "Finished writing data at " + dataFile.getAbsolutePath()); 119 } catch (IOException e) { 120 throw new RuntimeException(e); 121 } finally { 122 closeQuietly(writer); 123 } 124 } 125 126 /** Gets the suffix to use in the column name for perf data. */ 127 private String getColumnSuffix(boolean placebo, int replication, TimeUnit bucket, 128 long periodSecs, int burst, boolean includeCountMetric, boolean includeDurationMetric, 129 boolean includeEventMetric, boolean includeValueMetric, boolean includeGaugeMetric) { 130 if (placebo) { 131 return "_placebo_p=" + periodSecs; 132 } 133 StringBuilder sb = new StringBuilder() 134 .append("_r=" + replication) 135 .append("_bkt=" + bucket) 136 .append("_p=" + periodSecs) 137 .append("_bst=" + burst) 138 .append("_m="); 139 if (includeCountMetric) { 140 sb.append("c"); 141 } 142 if (includeEventMetric) { 143 sb.append("e"); 144 } 145 if (includeDurationMetric) { 146 sb.append("d"); 147 } 148 if (includeGaugeMetric) { 149 sb.append("g"); 150 } 151 if (includeValueMetric) { 152 sb.append("v"); 153 } 154 return sb.toString(); 155 } 156 157 private File getStorageDir() { 158 File file = new File(Environment.getExternalStoragePublicDirectory( 159 Environment.DIRECTORY_DOCUMENTS), "loadtest/" + mTimeAsString); 160 if (!file.mkdirs()) { 161 Log.e(TAG, "Directory not created"); 162 } 163 return file; 164 } 165 166 private void closeQuietly(@Nullable Closeable c) { 167 if (c != null) { 168 try { 169 c.close(); 170 } catch (IOException ignore) { 171 } 172 } 173 } 174} 175