Merge "Track PSS in loadtest."

This commit is contained in:
Stefan Lafon
2017-12-07 20:23:37 +00:00
committed by Android (Google) Code Review
13 changed files with 456 additions and 172 deletions

View File

@@ -39,5 +39,6 @@
</activity>
<receiver android:name=".LoadtestActivity$PusherAlarmReceiver" />
<receiver android:name=".LoadtestActivity$StopperAlarmReceiver"/>
<receiver android:name=".PerfData$PerfAlarmReceiver"/>
</application>
</manifest>

View File

@@ -166,12 +166,6 @@
android:layout_height="wrap_content"
android:text="@string/display_output"
android:textSize="30dp"/>
<Button
android:id="@+id/display_perf"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/display_perf"
android:textSize="30dp"/>
<Space
android:layout_width="1dp"
@@ -179,6 +173,7 @@
<TextView
android:id="@+id/report_text"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@@ -21,7 +21,6 @@
<string name="bucket_label">bucket size (mins):&#160;</string>
<string name="burst_label">burst:&#160;</string>
<string name="display_output">Show metrics data</string>
<string name="display_perf">Show perf data</string>
<string name="placebo">placebo</string>
<string name="period_label">logging period (secs):&#160;</string>
<string name="replication_label">metric replication:&#160;</string>

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2017 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.statsd.loadtest;
import android.annotation.Nullable;
import android.content.Context;
import android.util.Log;
import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class BatteryDataRecorder extends PerfDataRecorder {
private static final String TAG = "loadtest.BatteryDataRecorder";
private static final String DUMP_FILENAME = TAG + "_dump.tmp";
public BatteryDataRecorder(boolean placebo, int replication, long bucketMins, long periodSecs,
int burst) {
super(placebo, replication, bucketMins, periodSecs, burst);
}
@Override
public void startRecording(Context context) {
// Reset batterystats.
runDumpsysStats(context, DUMP_FILENAME, "batterystats", "--reset");
}
@Override
public void onAlarm(Context context) {
// Nothing to do as for battery, the whole data is in the final dumpsys call.
}
@Override
public void stopRecording(Context context) {
StringBuilder sb = new StringBuilder();
// Don't use --checkin.
runDumpsysStats(context, DUMP_FILENAME, "batterystats");
readDumpData(context, DUMP_FILENAME, new BatteryStatsParser(), sb);
writeData(context, "battery_", "time,battery_level", sb);
}
}

View File

@@ -21,13 +21,13 @@ import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class BatteryStatsParser {
public class BatteryStatsParser implements PerfParser {
private static final Pattern LINE_PATTERN =
Pattern.compile("\\s*\\+*(\\S*)\\s\\(\\d+\\)\\s(\\d\\d\\d)\\s.*");
private static final Pattern TIME_PATTERN =
Pattern.compile("(\\d+)?(h)?(\\d+)?(m)?(\\d+)?(s)?(\\d+)?(ms)?");
private static final String TAG = "BatteryStatsParser";
private static final String TAG = "loadtest.BatteryStatsParser";
private boolean mHistoryStarted;
private boolean mHistoryEnded;
@@ -35,6 +35,7 @@ public class BatteryStatsParser {
public BatteryStatsParser() {
}
@Override
@Nullable
public String parseLine(String line) {
if (mHistoryEnded) {

View File

@@ -18,7 +18,6 @@ package com.android.statsd.loadtest;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import android.util.StatsLog;
import com.android.internal.os.StatsdConfigProto.Bucket;
import com.android.internal.os.StatsdConfigProto.Predicate;
@@ -45,7 +44,7 @@ import java.util.List;
public class ConfigFactory {
public static final String CONFIG_NAME = "LOADTEST";
private static final String TAG = "ConfigFactory";
private static final String TAG = "loadtest.ConfigFactory";
private final StatsdConfig mTemplate;

View File

@@ -65,8 +65,9 @@ import android.widget.Toast;
public class LoadtestActivity extends Activity {
private static final String TAG = "StatsdLoadtest";
private static final String TYPE = "type";
private static final String ALARM = "push_alarm";
public static final String TYPE = "type";
private static final String PUSH_ALARM = "push_alarm";
public static final String PERF_ALARM = "perf_alarm";
private static final String START = "start";
private static final String STOP = "stop";
@@ -74,7 +75,7 @@ public class LoadtestActivity extends Activity {
@Override
public void onReceive(Context context, Intent intent) {
Intent activityIntent = new Intent(context, LoadtestActivity.class);
activityIntent.putExtra(TYPE, ALARM);
activityIntent.putExtra(TYPE, PUSH_ALARM);
context.startActivity(activityIntent);
}
}
@@ -105,6 +106,9 @@ public class LoadtestActivity extends Activity {
private TextView mReportText;
private CheckBox mPlaceboCheckBox;
/** When the load test started. */
private long mStartedTimeMillis;
/** For measuring perf data. */
private PerfData mPerfData;
@@ -180,7 +184,7 @@ public class LoadtestActivity extends Activity {
@Override
public void onClick(View view) {
if (mStarted) {
stopLoadtest(true);
stopLoadtest();
} else {
startLoadtest();
}
@@ -194,20 +198,11 @@ public class LoadtestActivity extends Activity {
}
});
findViewById(R.id.display_perf).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mPerfData.publishData(LoadtestActivity.this, mPlacebo, mReplication, mBucketMins,
mPeriodSecs, mBurst);
}
});
mAlarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
mStatsManager = (StatsManager) getSystemService("stats");
mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mFactory = new ConfigFactory(this);
mPerfData = new PerfData();
stopLoadtest(false);
stopLoadtest();
mReportText.setText("");
}
@@ -218,14 +213,17 @@ public class LoadtestActivity extends Activity {
return;
}
switch (type) {
case ALARM:
onAlarm(intent);
break;
case PERF_ALARM:
onPerfAlarm();
break;
case PUSH_ALARM:
onAlarm();
break;
case START:
startLoadtest();
break;
case STOP:
stopLoadtest(true);
case STOP:
stopLoadtest();
break;
default:
throw new IllegalArgumentException("Unknown type: " + type);
@@ -235,12 +233,23 @@ public class LoadtestActivity extends Activity {
@Override
public void onDestroy() {
Log.d(TAG, "Destroying");
stopLoadtest(false);
mPerfData.onDestroy();
stopLoadtest();
clearConfigs();
super.onDestroy();
}
private void onAlarm(Intent intent) {
private void onPerfAlarm() {
if (mPerfData != null) {
mPerfData.onAlarm(this);
}
// Piggy-back on that alarm to show the elapsed time.
long elapsedTimeMins = (long) Math.floor(
(SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000);
mReportText.setText("Loadtest in progress. Elapsed time = " + elapsedTimeMins + " min(s)");
}
private void onAlarm() {
Log.d(TAG, "ON ALARM");
// Set the next task.
@@ -259,7 +268,7 @@ public class LoadtestActivity extends Activity {
/** Schedules the next cycle of pushing atoms into logd. */
private void scheduleNext() {
Intent intent = new Intent(this, PusherAlarmReceiver.class);
intent.putExtra(TYPE, ALARM);
intent.putExtra(TYPE, PUSH_ALARM);
mPushPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
long nextTime = SystemClock.elapsedRealtime() + mPeriodSecs * 1000;
mAlarmMgr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, mPushPendingIntent);
@@ -271,7 +280,7 @@ public class LoadtestActivity extends Activity {
}
// Clean up the state.
stopLoadtest(false);
stopLoadtest();
// Prepare to push a sequence of atoms to logd.
mPusher = new SequencePusher(mBurst, mPlacebo);
@@ -291,15 +300,17 @@ public class LoadtestActivity extends Activity {
// Log atoms.
scheduleNext();
// Reset battery data.
mPerfData.resetData(this);
// Start tracking performance.
mPerfData = new PerfData(this, mPlacebo, mReplication, mBucketMins, mPeriodSecs, mBurst);
mPerfData.startRecording(this);
mReportText.setText("");
mReportText.setText("Loadtest in progress.");
mStartedTimeMillis = SystemClock.elapsedRealtime();
updateStarted(true);
}
private synchronized void stopLoadtest(boolean publishPerfData) {
private synchronized void stopLoadtest() {
if (mPushPendingIntent != null) {
Log.d(TAG, "Canceling pre-existing push alarm");
mAlarmMgr.cancel(mPushPendingIntent);
@@ -314,12 +325,17 @@ public class LoadtestActivity extends Activity {
mWakeLock.release();
mWakeLock = null;
}
fetchAndDisplayData();
if (mPerfData != null) {
mPerfData.stopRecording(this);
mPerfData.onDestroy();
mPerfData = null;
}
long elapsedTimeMins = (long) Math.floor(
(SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000);
mReportText.setText("Loadtest ended. Elapsed time = " + elapsedTimeMins + " min(s)");
clearConfigs();
updateStarted(false);
if (publishPerfData) {
mPerfData.publishData(this, mPlacebo, mReplication, mBucketMins, mPeriodSecs, mBurst);
}
}
private synchronized void updateStarted(boolean started) {

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2017 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.statsd.loadtest;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.util.Log;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Parses PSS info from dumpsys meminfo */
public class MemInfoParser implements PerfParser {
private static final Pattern LINE_PATTERN =
Pattern.compile("\\s*(\\d*,*\\d*)K:\\s(\\S*)\\s\\.*");
private static final String PSS_BY_PROCESS = "Total PSS by process:";
private static final String TAG = "loadtest.MemInfoParser";
private boolean mPssStarted;
private boolean mPssEnded;
private final long mStartTimeMillis;
public MemInfoParser(long startTimeMillis) {
mStartTimeMillis = startTimeMillis;
}
@Override
@Nullable
public String parseLine(String line) {
if (mPssEnded) {
return null;
}
if (!mPssStarted) {
if (line.contains(PSS_BY_PROCESS)) {
mPssStarted = true;
}
return null;
}
if (line.isEmpty()) {
mPssEnded = true;
return null;
}
Matcher lineMatcher = LINE_PATTERN.matcher(line);
if (lineMatcher.find() && lineMatcher.group(1) != null && lineMatcher.group(2) != null) {
if (lineMatcher.group(2).equals("statsd")) {
long timeDeltaMillis = SystemClock.elapsedRealtime() - mStartTimeMillis;
return timeDeltaMillis + "," + convertToPss(lineMatcher.group(1));
}
}
return null;
}
private String convertToPss(String input) {
return input.replace(",", "");
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2017 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.statsd.loadtest;
import android.content.Context;
import android.os.SystemClock;
import android.util.Log;
public class MemoryDataRecorder extends PerfDataRecorder {
private static final String TAG = "loadtest.MemoryDataDataRecorder";
private static final String DUMP_FILENAME = TAG + "_dump.tmp";
private long mStartTimeMillis;
private StringBuilder mSb;
public MemoryDataRecorder(boolean placebo, int replication, long bucketMins, long periodSecs,
int burst) {
super(placebo, replication, bucketMins, periodSecs, burst);
}
@Override
public void startRecording(Context context) {
mStartTimeMillis = SystemClock.elapsedRealtime();
mSb = new StringBuilder();
}
@Override
public void onAlarm(Context context) {
Log.d(TAG, "GOT ALARM IN MEM");
runDumpsysStats(context, DUMP_FILENAME, "meminfo");
readDumpData(context, DUMP_FILENAME, new MemInfoParser(mStartTimeMillis), mSb);
}
@Override
public void stopRecording(Context context) {
writeData(context, "meminfo_", "time,pss", mSb);
}
}

View File

@@ -15,29 +15,14 @@
*/
package com.android.statsd.loadtest;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.util.StatsLog;
import android.util.StatsManager;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.os.IStatsManager;
import android.os.ServiceManager;
import android.view.View.OnFocusChangeListener;
public abstract class NumericalWatcher implements TextWatcher {
private static final String TAG = "NumericalWatcher";
private static final String TAG = "loadtest.NumericalWatcher";
private final TextView mTextView;
private final int mMin;

View File

@@ -16,147 +16,86 @@
package com.android.statsd.loadtest;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.util.Log;
import android.os.Debug;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/** Prints some information about the device via Dumpsys in order to evaluate health metrics. */
public class PerfData {
public class PerfData extends PerfDataRecorder {
private static final String TAG = "PerfData";
private static final String DUMP_FILENAME = TAG + "_dump.tmp";
private static final String TAG = "loadtest.PerfData";
public void resetData(Context context) {
runDumpsysStats(context, "batterystats", "--reset");
/** Polling period for performance snapshots like memory. */
private static final long POLLING_PERIOD_MILLIS = 1 * 60 * 1000;
public final static class PerfAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent activityIntent = new Intent(context, LoadtestActivity.class);
activityIntent.putExtra(LoadtestActivity.TYPE, LoadtestActivity.PERF_ALARM);
context.startActivity(activityIntent);
}
}
public void publishData(Context context, boolean placebo, int replication, long bucketMins,
long periodSecs, int burst) {
publishBatteryData(context, placebo, replication, bucketMins, periodSecs, burst);
private AlarmManager mAlarmMgr;
/** Used to periodically poll some dumpsys data. */
private PendingIntent mPendingIntent;
private final Set<PerfDataRecorder> mRecorders;
public PerfData(Context context, boolean placebo, int replication, long bucketMins,
long periodSecs, int burst) {
super(placebo, replication, bucketMins, periodSecs, burst);
mRecorders = new HashSet();
mRecorders.add(new BatteryDataRecorder(placebo, replication, bucketMins, periodSecs, burst));
mRecorders.add(new MemoryDataRecorder(placebo, replication, bucketMins, periodSecs, burst));
mAlarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
}
private void publishBatteryData(Context context, boolean placebo, int replication,
long bucketMins, long periodSecs, int burst) {
// Don't use --checkin.
runDumpsysStats(context, "batterystats");
writeBatteryData(context, placebo, replication, bucketMins, periodSecs, burst);
}
private void runDumpsysStats(Context context, String cmd, String... args) {
boolean success = false;
// Call dumpsys Dump statistics to a file.
FileOutputStream fo = null;
try {
fo = context.openFileOutput(DUMP_FILENAME, Context.MODE_PRIVATE);
if (!Debug.dumpService(cmd, fo.getFD(), args)) {
Log.w(TAG, "Dumpsys failed.");
}
success = true;
} catch (IOException | SecurityException | NullPointerException e) {
// SecurityException may occur when trying to dump multi-user info.
// NPE can occur during dumpService (root cause unknown).
throw new RuntimeException(e);
} finally {
closeQuietly(fo);
public void onDestroy() {
if (mPendingIntent != null) {
mAlarmMgr.cancel(mPendingIntent);
mPendingIntent = null;
}
}
private String readDumpFile(Context context) {
StringBuilder sb = new StringBuilder();
FileInputStream fi = null;
BufferedReader br = null;
try {
fi = context.openFileInput(DUMP_FILENAME);
br = new BufferedReader(new InputStreamReader(fi));
String line = br.readLine();
while (line != null) {
sb.append(line);
sb.append(System.lineSeparator());
line = br.readLine();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
closeQuietly(br);
}
return sb.toString();
}
@Override
public void startRecording(Context context) {
Intent intent = new Intent(context, PerfAlarmReceiver.class);
intent.putExtra(LoadtestActivity.TYPE, LoadtestActivity.PERF_ALARM);
mPendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
mAlarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, -1 /* now */,
POLLING_PERIOD_MILLIS, mPendingIntent);
private static void closeQuietly(@Nullable Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ignore) {
}
for (PerfDataRecorder recorder : mRecorders) {
recorder.startRecording(context);
}
}
public void writeBatteryData(Context context, boolean placebo, int replication, long bucketMins,
long periodSecs, int burst) {
BatteryStatsParser parser = new BatteryStatsParser();
FileInputStream fi = null;
BufferedReader br = null;
String suffix = new SimpleDateFormat("YYYY_MM_dd_HH_mm_ss").format(new Date());
File batteryDataFile = new File(getStorageDir(), "battery_" + suffix + ".csv");
Log.d(TAG, "Writing battery data to " + batteryDataFile.getAbsolutePath());
FileWriter writer = null;
try {
fi = context.openFileInput(DUMP_FILENAME);
writer = new FileWriter(batteryDataFile);
writer.append("time,battery_level"
+ getColumnName(placebo, replication, bucketMins, periodSecs, burst) + "\n");
br = new BufferedReader(new InputStreamReader(fi));
String line = br.readLine();
while (line != null) {
String recordLine = parser.parseLine(line);
if (recordLine != null) {
writer.append(recordLine);
}
line = br.readLine();
}
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
closeQuietly(writer);
closeQuietly(br);
@Override
public void onAlarm(Context context) {
for (PerfDataRecorder recorder : mRecorders) {
recorder.onAlarm(context);
}
}
private File getStorageDir() {
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS), "loadtest");
if (!file.mkdirs()) {
Log.e(TAG, "Directory not created");
@Override
public void stopRecording(Context context) {
if (mPendingIntent != null) {
mAlarmMgr.cancel(mPendingIntent);
mPendingIntent = null;
}
return file;
}
private String getColumnName(boolean placebo, int replication, long bucketMins, long periodSecs,
int burst) {
if (placebo) {
return "_placebo_p=" + periodSecs;
for (PerfDataRecorder recorder : mRecorders) {
recorder.stopRecording(context);
}
return "_r=" + replication + "_bkt=" + bucketMins + "_p=" + periodSecs + "_bst=" + burst;
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (C) 2017 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.statsd.loadtest;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import android.os.Debug;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public abstract class PerfDataRecorder {
private static final String TAG = "loadtest.PerfDataRecorder";
protected final String mFileSuffix;
protected final String mColumnSuffix;
protected PerfDataRecorder(boolean placebo, int replication, long bucketMins, long periodSecs,
int burst) {
mFileSuffix = new SimpleDateFormat("YYYY_MM_dd_HH_mm_ss").format(new Date());
mColumnSuffix = getColumnSuffix(placebo, replication, bucketMins, periodSecs, burst);
}
/** Starts recording performance data. */
public abstract void startRecording(Context context);
/** Called periodically. For the recorder to sample data, if needed. */
public abstract void onAlarm(Context context);
/** Stops recording performance data, and writes it to disk. */
public abstract void stopRecording(Context context);
/** Runs the dumpsys command. */
protected void runDumpsysStats(Context context, String dumpFilename, String cmd,
String... args) {
boolean success = false;
// Call dumpsys Dump statistics to a file.
FileOutputStream fo = null;
try {
fo = context.openFileOutput(dumpFilename, Context.MODE_PRIVATE);
if (!Debug.dumpService(cmd, fo.getFD(), args)) {
Log.w(TAG, "Dumpsys failed.");
}
success = true;
} catch (IOException | SecurityException | NullPointerException e) {
// SecurityException may occur when trying to dump multi-user info.
// NPE can occur during dumpService (root cause unknown).
throw new RuntimeException(e);
} finally {
closeQuietly(fo);
}
}
/**
* Reads a text file and parses each line, one by one. The result of the parsing is stored
* in the passed {@link StringBuffer}.
*/
protected void readDumpData(Context context, String dumpFilename, PerfParser parser,
StringBuilder sb) {
FileInputStream fi = null;
BufferedReader br = null;
try {
fi = context.openFileInput(dumpFilename);
br = new BufferedReader(new InputStreamReader(fi));
String line = br.readLine();
while (line != null) {
String recordLine = parser.parseLine(line);
if (recordLine != null) {
sb.append(recordLine).append('\n');
}
line = br.readLine();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
closeQuietly(br);
}
}
/** Writes CSV data to a file. */
protected void writeData(Context context, String filePrefix, String columnPrefix,
StringBuilder sb) {
File dataFile = new File(getStorageDir(), filePrefix + mFileSuffix + ".csv");
FileWriter writer = null;
try {
writer = new FileWriter(dataFile);
writer.append(columnPrefix + mColumnSuffix + "\n");
writer.append(sb.toString());
writer.flush();
Log.d(TAG, "Finished writing data at " + dataFile.getAbsolutePath());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
closeQuietly(writer);
}
}
/** Gets the suffix to use in the column name for perf data. */
private String getColumnSuffix(boolean placebo, int replication, long bucketMins,
long periodSecs, int burst) {
if (placebo) {
return "_placebo_p=" + periodSecs;
}
return "_r=" + replication + "_bkt=" + bucketMins + "_p=" + periodSecs + "_bst=" + burst;
}
private File getStorageDir() {
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS), "loadtest");
if (!file.mkdirs()) {
Log.e(TAG, "Directory not created");
}
return file;
}
private void closeQuietly(@Nullable Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ignore) {
}
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2017 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.statsd.loadtest;
import android.annotation.Nullable;
public interface PerfParser {
/**
* Parses one line of the dumpsys output, and returns a string to write to the data file,
* or null if no string should be written.
*/
@Nullable String parseLine(String line);
}