diff options
13 files changed, 456 insertions, 172 deletions
diff --git a/cmds/statsd/tools/loadtest/AndroidManifest.xml b/cmds/statsd/tools/loadtest/AndroidManifest.xml index d74c707a011f..2bf8ca95d846 100644 --- a/cmds/statsd/tools/loadtest/AndroidManifest.xml +++ b/cmds/statsd/tools/loadtest/AndroidManifest.xml @@ -39,5 +39,6 @@ </activity> <receiver android:name=".LoadtestActivity$PusherAlarmReceiver" /> <receiver android:name=".LoadtestActivity$StopperAlarmReceiver"/> + <receiver android:name=".PerfData$PerfAlarmReceiver"/> </application> </manifest> diff --git a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml index 82964ab1d821..1e28f6730709 100644 --- a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml +++ b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml @@ -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" /> diff --git a/cmds/statsd/tools/loadtest/res/values/strings.xml b/cmds/statsd/tools/loadtest/res/values/strings.xml index 991350332da0..cb38298873f0 100644 --- a/cmds/statsd/tools/loadtest/res/values/strings.xml +++ b/cmds/statsd/tools/loadtest/res/values/strings.xml @@ -21,7 +21,6 @@ <string name="bucket_label">bucket size (mins): </string> <string name="burst_label">burst: </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): </string> <string name="replication_label">metric replication: </string> diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java new file mode 100644 index 000000000000..d2ff892680a7 --- /dev/null +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java @@ -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); + } +} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java index 96e6bef600d1..203d97acefd8 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java @@ -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) { diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java index 071e1f9c504a..0d890fbb6e56 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java @@ -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; diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java index 3ae85a7d0bf2..522dea61d188 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java @@ -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) { diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemInfoParser.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemInfoParser.java new file mode 100644 index 000000000000..01eebf2ad1cf --- /dev/null +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemInfoParser.java @@ -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(",", ""); + } +} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java new file mode 100644 index 000000000000..d82a0eadea65 --- /dev/null +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java @@ -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); + } +} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java index 57f85b5db317..81a84f53b503 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java @@ -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; diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java index e3e23f58a26f..466524756c48 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java @@ -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 void publishData(Context context, boolean placebo, int replication, long bucketMins, - long periodSecs, int burst) { - publishBatteryData(context, placebo, replication, bucketMins, periodSecs, burst); + 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); + } } - 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 AlarmManager mAlarmMgr; - 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); - } + /** 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 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); + public void onDestroy() { + if (mPendingIntent != null) { + mAlarmMgr.cancel(mPendingIntent); + mPendingIntent = null; } - return sb.toString(); } - private static void closeQuietly(@Nullable Closeable c) { - if (c != null) { - try { - c.close(); - } catch (IOException ignore) { - } + @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); + + 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; } } diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java new file mode 100644 index 000000000000..15a8e5c87131 --- /dev/null +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java @@ -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) { + } + } + } +} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfParser.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfParser.java new file mode 100644 index 000000000000..e000918fa0f7 --- /dev/null +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfParser.java @@ -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); +} |