Adding initial protos for batterystats dump.
Timer is the basic stat tracking object that's used for a lot of the
data in batterystats.
ControllerActivity data is tracked both per uid and for the entire
system, so it makes sense to define it now independently.
For the BatteryStatsHelper change: there's a small window of time after
boot where getSystemService will return null. I ran into it.
BUG: 65750808
Test: flash device and check incident proto output
Change-Id: I15e7d046a43f76d5f53d05b5138ea40f42e19d59
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index bf0a264..9881927 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -19,6 +19,7 @@
import android.app.job.JobParameters;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
import android.telephony.SignalStrength;
import android.text.format.DateFormat;
import android.util.ArrayMap;
@@ -29,11 +30,13 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import android.view.Display;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -190,7 +193,7 @@
public static final int STATS_SINCE_UNPLUGGED = 2;
// NOTE: Update this list if you add/change any stats above.
- // These characters are supposed to represent "total", "last", "current",
+ // These characters are supposed to represent "total", "last", "current",
// and "unplugged". They were shortened for efficiency sake.
private static final String[] STAT_NAMES = { "l", "c", "u" };
@@ -220,7 +223,7 @@
* New in version 27:
* - Always On Display (screen doze mode) time and power
*/
- static final String CHECKIN_VERSION = "27";
+ static final int CHECKIN_VERSION = 27;
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -2988,6 +2991,31 @@
}
/**
+ * Dump a given timer stat to the proto stream.
+ *
+ * @param proto the ProtoOutputStream to log to
+ * @param fieldId type of data, the field to save to (e.g. AggregatedBatteryStats.WAKELOCK)
+ * @param timer a {@link Timer} to dump stats for
+ * @param rawRealtimeUs the current elapsed realtime of the system in microseconds
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+ */
+ private static void dumpTimer(ProtoOutputStream proto, long fieldId,
+ Timer timer, long rawRealtime, int which) {
+ if (timer == null) {
+ return;
+ }
+ // Convert from microseconds to milliseconds with rounding
+ final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ final int count = timer.getCountLocked(which);
+ if (totalTimeMs != 0 || count != 0) {
+ final long token = proto.start(fieldId);
+ proto.write(TimerProto.DURATION_MS, totalTimeMs);
+ proto.write(TimerProto.COUNT, count);
+ proto.end(token);
+ }
+ }
+
+ /**
* Checks if the ControllerActivityCounter has any data worth dumping.
*/
private static boolean controllerActivityHasData(ControllerActivityCounter counter, int which) {
@@ -3039,6 +3067,38 @@
pw.println();
}
+ /**
+ * Dumps the ControllerActivityCounter if it has any data worth dumping.
+ */
+ private static void dumpControllerActivityProto(ProtoOutputStream proto, long fieldId,
+ ControllerActivityCounter counter,
+ int which) {
+ if (!controllerActivityHasData(counter, which)) {
+ return;
+ }
+
+ final long cToken = proto.start(fieldId);
+
+ proto.write(ControllerActivityProto.IDLE_DURATION_MS,
+ counter.getIdleTimeCounter().getCountLocked(which));
+ proto.write(ControllerActivityProto.RX_DURATION_MS,
+ counter.getRxTimeCounter().getCountLocked(which));
+ proto.write(ControllerActivityProto.POWER_MAH,
+ counter.getPowerCounter().getCountLocked(which) / (1000 * 60 * 60));
+
+ long tToken;
+ LongCounter[] txCounters = counter.getTxTimeCounters();
+ for (int i = 0; i < txCounters.length; ++i) {
+ LongCounter c = txCounters[i];
+ tToken = proto.start(ControllerActivityProto.TX);
+ proto.write(ControllerActivityProto.TxLevel.LEVEL, i);
+ proto.write(ControllerActivityProto.TxLevel.DURATION_MS, c.getCountLocked(which));
+ proto.end(tToken);
+ }
+
+ proto.end(cToken);
+ }
+
private final void printControllerActivityIfInteresting(PrintWriter pw, StringBuilder sb,
String prefix, String controllerName,
ControllerActivityCounter counter,
@@ -5491,6 +5551,7 @@
}
public void prepareForDumpLocked() {
+ // We don't need to require subclasses implement this.
}
public static class HistoryPrinter {
@@ -6312,6 +6373,7 @@
}
}
+ // This is called from BatteryStatsService.
@SuppressWarnings("unused")
public void dumpCheckinLocked(Context context, PrintWriter pw,
List<ApplicationInfo> apps, int flags, long histStart) {
@@ -6323,10 +6385,7 @@
long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
- final boolean filtering = (flags &
- (DUMP_HISTORY_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0;
-
- if ((flags&DUMP_INCLUDE_HISTORY) != 0 || (flags&DUMP_HISTORY_ONLY) != 0) {
+ if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
if (startIteratingHistoryLocked()) {
try {
for (int i=0; i<getHistoryStringPoolSize(); i++) {
@@ -6350,7 +6409,7 @@
}
}
- if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
+ if ((flags & DUMP_HISTORY_ONLY) != 0) {
return;
}
@@ -6383,7 +6442,7 @@
}
}
}
- if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
+ if ((flags & DUMP_DAILY_ONLY) == 0) {
dumpDurationSteps(pw, "", DISCHARGE_STEP_DATA, getDischargeLevelStepTracker(), true);
String[] lineArgs = new String[1];
long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime() * 1000);
@@ -6403,4 +6462,33 @@
(flags&DUMP_DEVICE_WIFI_ONLY) != 0);
}
}
+
+ /** Dump batterystats data to a proto. @hide */
+ public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps,
+ int flags, long historyStart) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ final long bToken = proto.start(BatteryStatsServiceDumpProto.BATTERYSTATS);
+ prepareForDumpLocked();
+
+ proto.write(BatteryStatsProto.REPORT_VERSION, CHECKIN_VERSION);
+ proto.write(BatteryStatsProto.PARCEL_VERSION, getParcelVersion());
+ proto.write(BatteryStatsProto.START_PLATFORM_VERSION, getStartPlatformVersion());
+ proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion());
+
+ long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
+ if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
+ if (startIteratingHistoryLocked()) {
+ // TODO: implement dumpProtoHistoryLocked(proto);
+ }
+ }
+
+ if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
+ // TODO: implement dumpProtoAppsLocked(proto, apps);
+ // TODO: implement dumpProtoSystemLocked(proto);
+ }
+
+ proto.end(bToken);
+ proto.flush();
+ }
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index f085e29..15dc6f5 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -143,6 +143,9 @@
public static boolean checkWifiOnly(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
+ if (cm == null) {
+ return false;
+ }
return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
new file mode 100644
index 0000000..86e31d0
--- /dev/null
+++ b/core/proto/android/os/batterystats.proto
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+
+package android.os;
+
+message BatteryStatsProto {
+ int32 report_version = 1;
+ int64 parcel_version = 2;
+ string start_platform_version = 3;
+ string end_platform_version = 4;
+ BatteryHistoryProto history = 5;
+ repeated UidProto uids = 6;
+ SystemProto system = 7;
+}
+
+message BatteryHistoryProto {
+}
+
+message ControllerActivityProto {
+ // Time (milliseconds) spent in the idle state.
+ int64 idle_duration_ms = 1;
+ // Time (milliseconds) spent in the receive state.
+ int64 rx_duration_ms = 2;
+ // Total power (mAh) consumed by the controller in all states. The value may
+ // always be 0 if the device doesn't support power calculations.
+ int64 power_mah = 3;
+
+ // Represents a transmit level, where each level may draw a different amount
+ // of power. The levels themselves are controller-specific (and may possibly
+ // be device specific...yet to be confirmed).
+ message TxLevel {
+ // Transmit level. Higher levels draw more power.
+ int32 level = 1;
+ // Time spent in this specific transmit level state.
+ int64 duration_ms = 2;
+ }
+ repeated TxLevel tx = 4;
+}
+
+message SystemProto {
+}
+
+message TimerProto {
+ int64 duration_ms = 1;
+ int64 count = 2;
+}
+
+message UidProto {
+ // Combination of app ID and user ID.
+ int32 uid = 1;
+ repeated string package_names = 2;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index c3ceb1c..b3a606f 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -23,6 +23,7 @@
import "frameworks/base/libs/incident/proto/android/section.proto";
import "frameworks/base/core/proto/android/service/appwidget.proto";
import "frameworks/base/core/proto/android/service/battery.proto";
+import "frameworks/base/core/proto/android/service/batterystats.proto";
import "frameworks/base/core/proto/android/service/fingerprint.proto";
import "frameworks/base/core/proto/android/service/diskstats.proto";
import "frameworks/base/core/proto/android/service/netstats.proto";
@@ -84,6 +85,11 @@
(section).args = "notification --proto"
];
+ android.service.batterystats.BatteryStatsServiceDumpProto batterystats = 3005 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "batterystats --proto"
+ ];
+
android.service.battery.BatteryServiceDumpProto battery = 3006 [
(section).type = SECTION_DUMPSYS,
(section).args = "battery --proto"
diff --git a/core/proto/android/service/batterystats.proto b/core/proto/android/service/batterystats.proto
new file mode 100644
index 0000000..4e989b7
--- /dev/null
+++ b/core/proto/android/service/batterystats.proto
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+syntax = "proto3";
+
+package android.service.batterystats;
+
+option java_multiple_files = true;
+option java_outer_classname = "BatteryStatsServiceProto";
+
+import "frameworks/base/core/proto/android/os/batterystats.proto";
+
+message BatteryStatsServiceDumpProto {
+ android.os.BatteryStatsProto batterystats = 1;
+}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index e839003..7c9cd00 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -220,7 +220,7 @@
// Shutdown the thread we made.
mWorker.shutdown();
}
-
+
public static IBatteryStats getService() {
if (sService != null) {
return sService;
@@ -611,21 +611,21 @@
mStats.notePhoneOnLocked();
}
}
-
+
public void notePhoneOff() {
enforceCallingPermission();
synchronized (mStats) {
mStats.notePhoneOffLocked();
}
}
-
+
public void notePhoneSignalStrength(SignalStrength signalStrength) {
enforceCallingPermission();
synchronized (mStats) {
mStats.notePhoneSignalStrengthLocked(signalStrength);
}
}
-
+
public void notePhoneDataConnectionState(int dataType, boolean hasData) {
enforceCallingPermission();
synchronized (mStats) {
@@ -647,7 +647,7 @@
mStats.noteWifiOnLocked();
}
}
-
+
public void noteWifiOff() {
enforceCallingPermission();
synchronized (mStats) {
@@ -806,7 +806,7 @@
mStats.noteFullWifiLockAcquiredLocked(uid);
}
}
-
+
public void noteFullWifiLockReleased(int uid) {
enforceCallingPermission();
synchronized (mStats) {
@@ -1043,7 +1043,7 @@
});
});
}
-
+
public long getAwakeTimeBattery() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BATTERY_STATS, null);
@@ -1186,6 +1186,7 @@
int flags = 0;
boolean useCheckinFormat = false;
+ boolean toProto = false;
boolean isRealCheckin = false;
boolean noOutput = false;
boolean writeData = false;
@@ -1212,6 +1213,8 @@
} else if ("-c".equals(arg)) {
useCheckinFormat = true;
flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
+ } else if ("--proto".equals(arg)) {
+ toProto = true;
} else if ("--charged".equals(arg)) {
flags |= BatteryStats.DUMP_CHARGED_ONLY;
} else if ("--daily".equals(arg)) {
@@ -1304,7 +1307,45 @@
}
}
- if (useCheckinFormat) {
+ if (toProto) {
+ List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+ PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
+ if (isRealCheckin) {
+ // For a real checkin, first we want to prefer to use the last complete checkin
+ // file if there is one.
+ synchronized (mStats.mCheckinFile) {
+ if (mStats.mCheckinFile.exists()) {
+ try {
+ byte[] raw = mStats.mCheckinFile.readFully();
+ if (raw != null) {
+ Parcel in = Parcel.obtain();
+ in.unmarshall(raw, 0, raw.length);
+ in.setDataPosition(0);
+ BatteryStatsImpl checkinStats = new BatteryStatsImpl(
+ null, mStats.mHandler, null, mUserManagerUserInfoProvider);
+ checkinStats.readSummaryFromParcel(in);
+ in.recycle();
+ checkinStats.dumpProtoLocked(mContext, fd, apps, flags,
+ historyStart);
+ mStats.mCheckinFile.delete();
+ return;
+ }
+ } catch (IOException | ParcelFormatException e) {
+ Slog.w(TAG, "Failure reading checkin file "
+ + mStats.mCheckinFile.getBaseFile(), e);
+ }
+ }
+ }
+ }
+ if (DBG) Slog.d(TAG, "begin dumpProtoLocked from UID " + Binder.getCallingUid());
+ synchronized (mStats) {
+ mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart);
+ if (writeData) {
+ mStats.writeAsyncLocked();
+ }
+ }
+ if (DBG) Slog.d(TAG, "end dumpProtoLocked");
+ } else if (useCheckinFormat) {
List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
if (isRealCheckin) {