summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorDmitri Plotnikov <dplotnikov@google.com>2020-08-05 18:15:16 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-08-05 18:15:16 +0000
commit1a33ace6949c55e4fb533bf22b120bb3d226b0c9 (patch)
tree7b7243dcf21fca432dfa00cf645d7ef7cf27dbb1 /core/java
parentf13bc4679ad58d9a4dda0088ec6976cff17ffb25 (diff)
parent6c3defc2828c05c11b88b1893ca231a534a8afde (diff)
Merge "Implement a PowerCalculator to attribute system service power to apps"
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/os/BatteryStats.java17
-rw-r--r--core/java/com/android/internal/os/BatterySipper.java5
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHelper.java8
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java460
-rw-r--r--core/java/com/android/internal/os/BinderCallsStats.java3
-rw-r--r--core/java/com/android/internal/os/BinderInternal.java3
-rw-r--r--core/java/com/android/internal/os/KernelCpuThreadReader.java32
-rw-r--r--core/java/com/android/internal/os/SystemServerCpuThreadReader.java151
-rw-r--r--core/java/com/android/internal/os/SystemServicePowerCalculator.java91
9 files changed, 659 insertions, 111 deletions
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fbe6a5052f3d..b0d449769be4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -743,6 +743,12 @@ public abstract class BatteryStats implements Parcelable {
@UnsupportedAppUsage
public abstract ArrayMap<String, ? extends Pkg> getPackageStats();
+ /**
+ * Returns the proportion of power consumed by the System Service
+ * calls made by this UID.
+ */
+ public abstract double getProportionalSystemServiceUsage();
+
public abstract ControllerActivityCounter getWifiControllerActivity();
public abstract ControllerActivityCounter getBluetoothControllerActivity();
public abstract ControllerActivityCounter getModemControllerActivity();
@@ -2882,6 +2888,17 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getDischargeAmountScreenDozeSinceCharge();
/**
+ * Returns the approximate CPU time (in microseconds) spent by the system server handling
+ * incoming service calls from apps.
+ *
+ * @param cluster the index of the CPU cluster.
+ * @param step the index of the CPU speed. This is not the actual speed of the CPU.
+ * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
+ * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
+ */
+ public abstract long getSystemServiceTimeAtCpuSpeed(int cluster, int step);
+
+ /**
* Returns the total, last, or current battery uptime in microseconds.
*
* @param curTime the elapsed realtime in microseconds.
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index b3ea118d9c73..2620ba0749a9 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -129,6 +129,7 @@ public class BatterySipper implements Comparable<BatterySipper> {
public double videoPowerMah;
public double wakeLockPowerMah;
public double wifiPowerMah;
+ public double systemServiceCpuPowerMah;
// ****************
// This list must be kept current with atoms.proto (frameworks/base/cmds/statsd/src/atoms.proto)
@@ -242,6 +243,7 @@ public class BatterySipper implements Comparable<BatterySipper> {
videoPowerMah += other.videoPowerMah;
proportionalSmearMah += other.proportionalSmearMah;
totalSmearedPowerMah += other.totalSmearedPowerMah;
+ systemServiceCpuPowerMah += other.systemServiceCpuPowerMah;
}
/**
@@ -253,7 +255,8 @@ public class BatterySipper implements Comparable<BatterySipper> {
public double sumPower() {
totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah +
sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
- flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah;
+ flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah
+ + systemServiceCpuPowerMah;
totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
return totalPowerMah;
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index b131ab83cc79..3dfa3c3f6906 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -133,6 +133,7 @@ public class BatteryStatsHelper {
private double mMaxDrainedPower;
PowerCalculator mCpuPowerCalculator;
+ SystemServicePowerCalculator mSystemServicePowerCalculator;
PowerCalculator mWakelockPowerCalculator;
MobileRadioPowerCalculator mMobileRadioPowerCalculator;
PowerCalculator mWifiPowerCalculator;
@@ -396,6 +397,11 @@ public class BatteryStatsHelper {
}
mCpuPowerCalculator.reset();
+ if (mSystemServicePowerCalculator == null) {
+ mSystemServicePowerCalculator = new SystemServicePowerCalculator(mPowerProfile, mStats);
+ }
+ mSystemServicePowerCalculator.reset();
+
if (mMemoryPowerCalculator == null) {
mMemoryPowerCalculator = new MemoryPowerCalculator(mPowerProfile);
}
@@ -588,6 +594,8 @@ public class BatteryStatsHelper {
mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
mMediaPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
+ mSystemServicePowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
+ mStatsType);
final double totalPower = app.sumPower();
if (DEBUG && totalPower != 0) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 58ba16bc61dd..84981515e133 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -144,6 +144,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final boolean DEBUG = false;
public static final boolean DEBUG_ENERGY = false;
private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
+ private static final boolean DEBUG_BINDER_STATS = true;
private static final boolean DEBUG_MEMORY = false;
private static final boolean DEBUG_HISTORY = false;
private static final boolean USE_OLD_HISTORY = false; // for debugging.
@@ -154,7 +155,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- static final int VERSION = 186 + (USE_OLD_HISTORY ? 1000 : 0);
+ static final int VERSION = 187 + (USE_OLD_HISTORY ? 1000 : 0);
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -218,10 +219,13 @@ public class BatteryStatsImpl extends BatteryStats {
new KernelCpuUidClusterTimeReader(true);
@VisibleForTesting
protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+ @VisibleForTesting
+ protected SystemServerCpuThreadReader mSystemServerCpuThreadReader;
private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
= new KernelMemoryBandwidthStats();
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
+
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
}
@@ -267,6 +271,7 @@ public class BatteryStatsImpl extends BatteryStats {
/** Container for Rail Energy Data stats. */
private final RailStats mTmpRailStats = new RailStats();
+
/**
* Use a queue to delay removing UIDs from {@link KernelCpuUidUserSysTimeReader},
* {@link KernelCpuUidActiveTimeReader}, {@link KernelCpuUidClusterTimeReader},
@@ -1007,6 +1012,16 @@ public class BatteryStatsImpl extends BatteryStats {
private long[] mCpuFreqs;
+ /**
+ * Times spent by the system server threads grouped by cluster and CPU speed.
+ */
+ private LongSamplingCounter[][] mSystemServerThreadCpuTimesUs;
+
+ /**
+ * Times spent by the system server threads handling incoming binder requests.
+ */
+ private LongSamplingCounter[][] mBinderThreadCpuTimesUs;
+
@VisibleForTesting
protected PowerProfile mPowerProfile;
@@ -6131,10 +6146,77 @@ public class BatteryStatsImpl extends BatteryStats {
* the power consumption to the calling app.
*/
public void noteBinderCallStats(int workSourceUid, long incrementalCallCount,
- Collection<BinderCallsStats.CallStat> callStats) {
+ Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids) {
synchronized (this) {
getUidStatsLocked(workSourceUid).noteBinderCallStatsLocked(incrementalCallCount,
callStats);
+ mSystemServerCpuThreadReader.setBinderThreadNativeTids(binderThreadNativeTids);
+ }
+ }
+
+ /**
+ * Estimates the proportion of system server CPU activity handling incoming binder calls
+ * that can be attributed to each app
+ */
+ @VisibleForTesting
+ public void updateSystemServiceCallStats() {
+ // Start off by computing the average duration of recorded binder calls,
+ // regardless of which binder or transaction. We will use this as a fallback
+ // for calls that were not sampled at all.
+ int totalRecordedCallCount = 0;
+ long totalRecordedCallTimeMicros = 0;
+ for (int i = 0; i < mUidStats.size(); i++) {
+ Uid uid = mUidStats.valueAt(i);
+ ArraySet<BinderCallStats> binderCallStats = uid.mBinderCallStats;
+ for (int j = binderCallStats.size() - 1; j >= 0; j--) {
+ BinderCallStats stats = binderCallStats.valueAt(j);
+ totalRecordedCallCount += stats.recordedCallCount;
+ totalRecordedCallTimeMicros += stats.recordedCpuTimeMicros;
+ }
+ }
+
+ long totalSystemServiceTimeMicros = 0;
+
+ // For every UID, use recorded durations of sampled binder calls to estimate
+ // the total time the system server spent handling requests from this UID.
+ for (int i = 0; i < mUidStats.size(); i++) {
+ Uid uid = mUidStats.valueAt(i);
+
+ long totalTimeForUid = 0;
+ int totalCallCountForUid = 0;
+ ArraySet<BinderCallStats> binderCallStats = uid.mBinderCallStats;
+ for (int j = binderCallStats.size() - 1; j >= 0; j--) {
+ BinderCallStats stats = binderCallStats.valueAt(j);
+ totalCallCountForUid += stats.callCount;
+ if (stats.recordedCallCount > 0) {
+ totalTimeForUid +=
+ stats.callCount * stats.recordedCpuTimeMicros / stats.recordedCallCount;
+ } else if (totalRecordedCallCount > 0) {
+ totalTimeForUid +=
+ stats.callCount * totalRecordedCallTimeMicros / totalRecordedCallCount;
+ }
+ }
+
+ if (totalCallCountForUid < uid.mBinderCallCount && totalRecordedCallCount > 0) {
+ // Estimate remaining calls, which were not tracked because of binder call
+ // stats sampling
+ totalTimeForUid +=
+ (uid.mBinderCallCount - totalCallCountForUid) * totalRecordedCallTimeMicros
+ / totalRecordedCallCount;
+ }
+
+ uid.mSystemServiceTimeUs = totalTimeForUid;
+ totalSystemServiceTimeMicros += totalTimeForUid;
+ }
+
+ for (int i = 0; i < mUidStats.size(); i++) {
+ Uid uid = mUidStats.valueAt(i);
+ if (totalSystemServiceTimeMicros > 0) {
+ uid.mProportionalSystemServiceUsage =
+ (double) uid.mSystemServiceTimeUs / totalSystemServiceTimeMicros;
+ } else {
+ uid.mProportionalSystemServiceUsage = 0;
+ }
}
}
@@ -6583,7 +6665,7 @@ public class BatteryStatsImpl extends BatteryStats {
/**
* Accumulates stats for a specific binder transaction.
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @VisibleForTesting
protected static class BinderCallStats {
static final Comparator<BinderCallStats> COMPARATOR =
Comparator.comparing(BinderCallStats::getClassName)
@@ -6822,6 +6904,16 @@ public class BatteryStatsImpl extends BatteryStats {
*/
private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>();
+ /**
+ * Estimated total time spent by the system server handling requests from this uid.
+ */
+ private long mSystemServiceTimeUs;
+
+ /**
+ * Estimated proportion of system server binder call CPU cost for this uid.
+ */
+ private double mProportionalSystemServiceUsage;
+
public Uid(BatteryStatsImpl bsi, int uid) {
mBsi = bsi;
mUid = uid;
@@ -6899,7 +6991,6 @@ public class BatteryStatsImpl extends BatteryStats {
return nullIfAllZeros(mCpuClusterTimesMs, STATS_SINCE_CHARGED);
}
-
@Override
public long[] getCpuFreqTimes(int which, int procState) {
if (which < 0 || which >= NUM_PROCESS_STATE) {
@@ -6934,10 +7025,16 @@ public class BatteryStatsImpl extends BatteryStats {
return mBinderCallCount;
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public ArraySet<BinderCallStats> getBinderCallStats() {
return mBinderCallStats;
}
+ @Override
+ public double getProportionalSystemServiceUsage() {
+ return mProportionalSystemServiceUsage;
+ }
+
public void addIsolatedUid(int isolatedUid) {
if (mChildUids == null) {
mChildUids = new IntArray();
@@ -8029,9 +8126,12 @@ public class BatteryStatsImpl extends BatteryStats {
mBinderCallCount = 0;
mBinderCallStats.clear();
+ mProportionalSystemServiceUsage = 0;
+
mLastStepUserTime = mLastStepSystemTime = 0;
mCurStepUserTime = mCurStepSystemTime = 0;
+
return !active;
}
@@ -8373,28 +8473,7 @@ public class BatteryStatsImpl extends BatteryStats {
mUserCpuTime.writeToParcel(out);
mSystemCpuTime.writeToParcel(out);
- if (mCpuClusterSpeedTimesUs != null) {
- out.writeInt(1);
- out.writeInt(mCpuClusterSpeedTimesUs.length);
- for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeedTimesUs) {
- if (cpuSpeeds != null) {
- out.writeInt(1);
- out.writeInt(cpuSpeeds.length);
- for (LongSamplingCounter c : cpuSpeeds) {
- if (c != null) {
- out.writeInt(1);
- c.writeToParcel(out);
- } else {
- out.writeInt(0);
- }
- }
- } else {
- out.writeInt(0);
- }
- }
- } else {
- out.writeInt(0);
- }
+ mBsi.writeCpuSpeedCountersToParcel(out, mCpuClusterSpeedTimesUs);
LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs);
LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs);
@@ -8432,6 +8511,7 @@ public class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+ out.writeDouble(mProportionalSystemServiceUsage);
}
void readJobCompletionsFromParcelLocked(Parcel in) {
@@ -8692,36 +8772,7 @@ public class BatteryStatsImpl extends BatteryStats {
mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
mSystemCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
- if (in.readInt() != 0) {
- int numCpuClusters = in.readInt();
- if (mBsi.mPowerProfile != null && mBsi.mPowerProfile.getNumCpuClusters() != numCpuClusters) {
- throw new ParcelFormatException("Incompatible number of cpu clusters");
- }
-
- mCpuClusterSpeedTimesUs = new LongSamplingCounter[numCpuClusters][];
- for (int cluster = 0; cluster < numCpuClusters; cluster++) {
- if (in.readInt() != 0) {
- int numSpeeds = in.readInt();
- if (mBsi.mPowerProfile != null &&
- mBsi.mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != numSpeeds) {
- throw new ParcelFormatException("Incompatible number of cpu speeds");
- }
-
- final LongSamplingCounter[] cpuSpeeds = new LongSamplingCounter[numSpeeds];
- mCpuClusterSpeedTimesUs[cluster] = cpuSpeeds;
- for (int speed = 0; speed < numSpeeds; speed++) {
- if (in.readInt() != 0) {
- cpuSpeeds[speed] = new LongSamplingCounter(
- mBsi.mOnBatteryTimeBase, in);
- }
- }
- } else {
- mCpuClusterSpeedTimesUs[cluster] = null;
- }
- }
- } else {
- mCpuClusterSpeedTimesUs = null;
- }
+ mCpuClusterSpeedTimesUs = mBsi.readCpuSpeedCountersFromParcel(in);
mCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(in, mBsi.mOnBatteryTimeBase);
mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
@@ -8762,6 +8813,8 @@ public class BatteryStatsImpl extends BatteryStats {
} else {
mWifiRadioApWakeupCount = null;
}
+
+ mProportionalSystemServiceUsage = in.readDouble();
}
public void noteJobsDeferredLocked(int numDeferred, long sinceLast) {
@@ -9904,7 +9957,6 @@ public class BatteryStatsImpl extends BatteryStats {
UserInfoProvider userInfoProvider) {
init(clocks);
-
if (systemDir == null) {
mStatsFile = null;
mBatteryStatsHistory = new BatteryStatsHistory(this, mHistoryBuffer);
@@ -10046,6 +10098,8 @@ public class BatteryStatsImpl extends BatteryStats {
firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
}
+ mSystemServerCpuThreadReader = SystemServerCpuThreadReader.create();
+
if (mEstimatedBatteryCapacity == -1) {
// Initialize the estimated battery capacity to a known preset one.
mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
@@ -10726,6 +10780,9 @@ public class BatteryStatsImpl extends BatteryStats {
mTmpRailStats.reset();
+ resetIfNotNull(mSystemServerThreadCpuTimesUs, false);
+ resetIfNotNull(mBinderThreadCpuTimesUs, false);
+
mLastHistoryStepDetails = null;
mLastStepCpuUserTime = mLastStepCpuSystemTime = 0;
mCurStepCpuUserTime = mCurStepCpuSystemTime = 0;
@@ -10853,7 +10910,7 @@ public class BatteryStatsImpl extends BatteryStats {
return null;
}
- /**
+ /**
* Distribute WiFi energy info and network traffic to apps.
* @param info The energy information from the WiFi controller.
*/
@@ -11772,6 +11829,7 @@ public class BatteryStatsImpl extends BatteryStats {
for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
mKernelCpuSpeedReaders[cluster].readDelta();
}
+ mSystemServerCpuThreadReader.readDelta();
return;
}
@@ -11791,6 +11849,87 @@ public class BatteryStatsImpl extends BatteryStats {
readKernelUidCpuClusterTimesLocked(onBattery);
mNumAllUidCpuTimeReads += 2;
}
+
+ updateSystemServerThreadStats();
+ }
+
+ /**
+ * Estimates the proportion of the System Server CPU activity (per cluster per speed)
+ * spent on handling incoming binder calls.
+ */
+ @VisibleForTesting
+ public void updateSystemServerThreadStats() {
+ // There are some simplifying assumptions made in this algorithm
+ // 1) We assume that if a thread handles incoming binder calls, all of its activity
+ // is spent doing that. Most incoming calls are handled by threads allocated
+ // by the native layer in the binder thread pool, so this assumption is reasonable.
+ // 2) We use the aggregate CPU time spent in different threads as a proxy for the CPU
+ // cost. In reality, in multi-core CPUs, the CPU cost may not be linearly
+ // affected by additional threads.
+
+ SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
+ mSystemServerCpuThreadReader.readDelta();
+
+ int index = 0;
+ int numCpuClusters = mPowerProfile.getNumCpuClusters();
+ if (mSystemServerThreadCpuTimesUs == null) {
+ mSystemServerThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][];
+ mBinderThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][];
+ }
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+ if (mSystemServerThreadCpuTimesUs[cluster] == null) {
+ mSystemServerThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds];
+ mBinderThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds];
+ for (int speed = 0; speed < numSpeeds; speed++) {
+ mSystemServerThreadCpuTimesUs[cluster][speed] =
+ new LongSamplingCounter(mOnBatteryTimeBase);
+ mBinderThreadCpuTimesUs[cluster][speed] =
+ new LongSamplingCounter(mOnBatteryTimeBase);
+ }
+ }
+ for (int speed = 0; speed < numSpeeds; speed++) {
+ mSystemServerThreadCpuTimesUs[cluster][speed].addCountLocked(
+ systemServiceCpuThreadTimes.threadCpuTimesUs[index]);
+ mBinderThreadCpuTimesUs[cluster][speed].addCountLocked(
+ systemServiceCpuThreadTimes.binderThreadCpuTimesUs[index]);
+ index++;
+ }
+ }
+ if (DEBUG_BINDER_STATS) {
+ Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
+ long binderThreadTime = 0;
+ long totalThreadTime = 0;
+ int cpuIndex = 0;
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("cpu").append(cpuIndex).append(": [");
+ int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+ for (int speed = 0; speed < numSpeeds; speed++) {
+ if (speed != 0) {
+ sb.append(", ");
+ }
+ long totalCount = mSystemServerThreadCpuTimesUs[cluster][speed].getCountLocked(
+ 0) / 1000;
+ long binderCount = mBinderThreadCpuTimesUs[cluster][speed].getCountLocked(0)
+ / 1000;
+ sb.append(String.format("%d/%d(%.1f%%)",
+ binderCount,
+ totalCount,
+ totalCount != 0 ? (double) binderCount * 100 / totalCount : 0));
+
+ totalThreadTime += totalCount;
+ binderThreadTime += binderCount;
+ index++;
+ }
+ cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
+ Slog.d(TAG, sb.toString());
+ }
+ Slog.d(TAG, "Total system server thread time (ms): " + totalThreadTime);
+ Slog.d(TAG, String.format("Total Binder thread time (ms): %d (%.1f%%)",
+ binderThreadTime,
+ binderThreadTime != 0 ? (double) binderThreadTime * 100 / totalThreadTime : 0));
+ }
}
/**
@@ -12998,6 +13137,75 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+
+ @Override
+ public long getSystemServiceTimeAtCpuSpeed(int cluster, int step) {
+ // Estimates the time spent by the system server handling incoming binder requests.
+ //
+ // The data that we can get from the kernel is this:
+ // - CPU duration for a (thread - cluster - CPU speed) combination
+ // - CPU duration for a (UID - cluster - CPU speed) combination
+ //
+ // The configuration we have in the Power Profile is this:
+ // - Average CPU power for a (cluster - CPU speed) combination.
+ //
+ // The model used by BatteryStats can be illustrated with this example:
+ //
+ // - Let's say the system server has 10 threads.
+ // - These 10 threads spent 1000 ms of CPU time in aggregate
+ // - Of the 10 threads 4 were execute exclusively incoming binder calls.
+ // - These 4 "binder" threads consumed 600 ms of CPU time in aggregate
+ // - The real time spent by the system server UID doing all of this is, say, 200 ms.
+ //
+ // We will assume that power consumption is proportional to the time spent by the CPU
+ // across all threads. This is a crude assumption, but we don't have more detailed data.
+ // Thus,
+ // binderRealTime = realTime * aggregateBinderThreadTime / aggregateAllThreadTime
+ //
+ // In our example,
+ // binderRealTime = 200 * 600 / 1000 = 120ms
+ //
+ // We can then multiply this estimated time by the average power to obtain an estimate
+ // of the total power consumed by incoming binder calls for the given cluster/speed
+ // combination.
+
+ if (mSystemServerThreadCpuTimesUs == null) {
+ return 0;
+ }
+
+ if (cluster < 0 || cluster >= mSystemServerThreadCpuTimesUs.length) {
+ return 0;
+ }
+
+ final LongSamplingCounter[] threadTimesForCluster = mSystemServerThreadCpuTimesUs[cluster];
+
+ if (step < 0 || step >= threadTimesForCluster.length) {
+ return 0;
+ }
+
+ Uid systemUid = mUidStats.get(Process.SYSTEM_UID);
+ if (systemUid == null) {
+ return 0;
+ }
+
+ final long uidTimeAtCpuSpeed = systemUid.getTimeAtCpuSpeed(cluster, step,
+ BatteryStats.STATS_SINCE_CHARGED);
+ if (uidTimeAtCpuSpeed == 0) {
+ return 0;
+ }
+
+ final long uidThreadTime =
+ threadTimesForCluster[step].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
+
+ if (uidThreadTime == 0) {
+ return 0;
+ }
+
+ final long binderThreadTime = mBinderThreadCpuTimesUs[cluster][step].getCountLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+ return uidTimeAtCpuSpeed * binderThreadTime / uidThreadTime;
+ }
+
/**
* Retrieve the statistics object for a particular uid, creating if needed.
*/
@@ -13327,45 +13535,7 @@ public class BatteryStatsImpl extends BatteryStats {
pw.print(uid.getUserCpuTimeUs(STATS_SINCE_CHARGED) / 1000); pw.print(" ");
pw.println(uid.getSystemCpuTimeUs(STATS_SINCE_CHARGED) / 1000);
}
- pw.println("Per UID system service calls:");
- BinderTransactionNameResolver nameResolver = new BinderTransactionNameResolver();
- for (int i = 0; i < size; i++) {
- int u = mUidStats.keyAt(i);
- Uid uid = mUidStats.get(u);
- long binderCallCount = uid.getBinderCallCount();
- if (binderCallCount != 0) {
- pw.print(" ");
- pw.print(u);
- pw.print(" system service calls: ");
- pw.print(binderCallCount);
- ArraySet<BinderCallStats> binderCallStats = uid.getBinderCallStats();
- if (!binderCallStats.isEmpty()) {
- pw.println(", including");
- BinderCallStats[] bcss = new BinderCallStats[binderCallStats.size()];
- binderCallStats.toArray(bcss);
- for (BinderCallStats bcs : bcss) {
- bcs.ensureMethodName(nameResolver);
- }
- Arrays.sort(bcss, BinderCallStats.COMPARATOR);
- for (BinderCallStats callStats : bcss) {
- pw.print(" ");
- pw.print(callStats.getClassName());
- pw.print('#');
- pw.print(callStats.getMethodName());
- pw.print(" calls: ");
- pw.print(callStats.callCount);
- if (callStats.recordedCallCount != 0) {
- pw.print(" time: ");
- pw.print(callStats.callCount * callStats.recordedCpuTimeMicros
- / callStats.recordedCallCount / 1000);
- }
- pw.println();
- }
- } else {
- pw.println();
- }
- }
- }
+
pw.println("Per UID CPU active time in ms:");
for (int i = 0; i < size; i++) {
int u = mUidStats.keyAt(i);
@@ -13390,6 +13560,30 @@ public class BatteryStatsImpl extends BatteryStats {
pw.print(" "); pw.print(u); pw.print(": "); pw.println(Arrays.toString(times));
}
}
+
+ updateSystemServiceCallStats();
+ if (mSystemServerThreadCpuTimesUs != null) {
+ pw.println("Per UID System server binder time in ms:");
+ for (int i = 0; i < size; i++) {
+ int u = mUidStats.keyAt(i);
+ Uid uid = mUidStats.get(u);
+ double proportionalSystemServiceUsage = uid.getProportionalSystemServiceUsage();
+
+ long time = 0;
+ for (int cluster = 0; cluster < mSystemServerThreadCpuTimesUs.length; cluster++) {
+ int numSpeeds = mSystemServerThreadCpuTimesUs[cluster].length;
+ for (int speed = 0; speed < numSpeeds; speed++) {
+ time += getSystemServiceTimeAtCpuSpeed(cluster, speed)
+ * proportionalSystemServiceUsage;
+ }
+ }
+
+ pw.print(" ");
+ pw.print(u);
+ pw.print(": ");
+ pw.println(time);
+ }
+ }
}
final ReentrantLock mWriteLock = new ReentrantLock();
@@ -14908,6 +15102,9 @@ public class BatteryStatsImpl extends BatteryStats {
u.readFromParcelLocked(mOnBatteryTimeBase, mOnBatteryScreenOffTimeBase, in);
mUidStats.append(uid, u);
}
+
+ mSystemServerThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in);
+ mBinderThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in);
}
public void writeToParcel(Parcel out, int flags) {
@@ -14923,6 +15120,8 @@ public class BatteryStatsImpl extends BatteryStats {
// Need to update with current kernel wake lock counts.
pullPendingStateUpdatesLocked();
+ updateSystemServiceCallStats();
+
// Pull the clock time. This may update the time and make a new history entry
// if we had originally pulled a time before the RTC was set.
getStartClockTime();
@@ -15105,6 +15304,73 @@ public class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+ writeCpuSpeedCountersToParcel(out, mSystemServerThreadCpuTimesUs);
+ writeCpuSpeedCountersToParcel(out, mBinderThreadCpuTimesUs);
+ }
+
+ private void writeCpuSpeedCountersToParcel(Parcel out, LongSamplingCounter[][] counters) {
+ if (counters == null) {
+ out.writeInt(0);
+ return;
+ }
+
+ out.writeInt(1);
+ out.writeInt(counters.length);
+ for (int i = 0; i < counters.length; i++) {
+ LongSamplingCounter[] counterArray = counters[i];
+ if (counterArray == null) {
+ out.writeInt(0);
+ continue;
+ }
+
+ out.writeInt(1);
+ out.writeInt(counterArray.length);
+ for (int j = 0; j < counterArray.length; j++) {
+ LongSamplingCounter c = counterArray[j];
+ if (c != null) {
+ out.writeInt(1);
+ c.writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
+ }
+ }
+ }
+
+ private LongSamplingCounter[][] readCpuSpeedCountersFromParcel(Parcel in) {
+ LongSamplingCounter[][] counters;
+ if (in.readInt() != 0) {
+ int numCpuClusters = in.readInt();
+ if (mPowerProfile != null
+ && mPowerProfile.getNumCpuClusters() != numCpuClusters) {
+ throw new ParcelFormatException("Incompatible number of cpu clusters");
+ }
+
+ counters = new LongSamplingCounter[numCpuClusters][];
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ if (in.readInt() != 0) {
+ int numSpeeds = in.readInt();
+ if (mPowerProfile != null
+ && mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != numSpeeds) {
+ throw new ParcelFormatException("Incompatible number of cpu speeds");
+ }
+
+ final LongSamplingCounter[] cpuSpeeds = new LongSamplingCounter[numSpeeds];
+ counters[cluster] = cpuSpeeds;
+ for (int speed = 0; speed < numSpeeds; speed++) {
+ if (in.readInt() != 0) {
+ cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ }
+ }
+ } else {
+ counters[cluster] = null;
+ }
+ }
+ } else {
+ counters = null;
+ }
+
+ return counters;
}
@UnsupportedAppUsage
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index e09ef49acd10..201626abd820 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -115,7 +115,8 @@ public class BinderCallsStats implements BinderInternal.Observer {
if (uidEntry != null) {
ArrayMap<CallStatKey, CallStat> callStats = uidEntry.mCallStats;
mCallStatsObserver.noteCallStats(uidEntry.workSourceUid,
- uidEntry.incrementalCallCount, callStats.values());
+ uidEntry.incrementalCallCount, callStats.values(),
+ mNativeTids.toArray());
uidEntry.incrementalCallCount = 0;
for (int j = callStats.size() - 1; j >= 0; j--) {
callStats.valueAt(j).incrementalCallCount = 0;
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index feb5aab94adc..f14d5f2bbbeb 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -142,7 +142,8 @@ public class BinderInternal {
* Notes incoming binder call stats associated with this work source UID.
*/
void noteCallStats(int workSourceUid, long incrementalCallCount,
- Collection<BinderCallsStats.CallStat> callStats);
+ Collection<BinderCallsStats.CallStat> callStats,
+ int[] binderThreadNativeTids);
}
/**
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
index 34076700cd95..2ba372a47cf3 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -225,19 +225,22 @@ public class KernelCpuThreadReader {
/** Set the number of frequency buckets to use */
void setNumBuckets(int numBuckets) {
- if (numBuckets < 1) {
- Slog.w(TAG, "Number of buckets must be at least 1, but was " + numBuckets);
- return;
- }
// If `numBuckets` hasn't changed since the last set, do nothing
if (mFrequenciesKhz != null && mFrequenciesKhz.length == numBuckets) {
return;
}
- mFrequencyBucketCreator =
- new FrequencyBucketCreator(mProcTimeInStateReader.getFrequenciesKhz(), numBuckets);
- mFrequenciesKhz =
- mFrequencyBucketCreator.bucketFrequencies(
- mProcTimeInStateReader.getFrequenciesKhz());
+
+ final long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
+ if (numBuckets != 0) {
+ mFrequencyBucketCreator = new FrequencyBucketCreator(frequenciesKhz, numBuckets);
+ mFrequenciesKhz = mFrequencyBucketCreator.bucketFrequencies(frequenciesKhz);
+ } else {
+ mFrequencyBucketCreator = null;
+ mFrequenciesKhz = new int[frequenciesKhz.length];
+ for (int i = 0; i < frequenciesKhz.length; i++) {
+ mFrequenciesKhz[i] = (int) frequenciesKhz[i];
+ }
+ }
}
/** Set the UID predicate for {@link #getProcessCpuUsage} */
@@ -320,8 +323,15 @@ public class KernelCpuThreadReader {
if (cpuUsagesLong == null) {
return null;
}
- int[] cpuUsages = mFrequencyBucketCreator.bucketValues(cpuUsagesLong);
-
+ final int[] cpuUsages;
+ if (mFrequencyBucketCreator != null) {
+ cpuUsages = mFrequencyBucketCreator.bucketValues(cpuUsagesLong);
+ } else {
+ cpuUsages = new int[cpuUsagesLong.length];
+ for (int i = 0; i < cpuUsagesLong.length; i++) {
+ cpuUsages[i] = (int) cpuUsagesLong[i];
+ }
+ }
return new ThreadCpuUsage(threadId, threadName, cpuUsages);
}
diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
new file mode 100644
index 000000000000..1cdd42c7403e
--- /dev/null
+++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2020 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.internal.os;
+
+import android.os.Process;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Reads /proc/UID/task/TID/time_in_state files to obtain statistics on CPU usage
+ * by various threads of the System Server.
+ */
+public class SystemServerCpuThreadReader {
+ private KernelCpuThreadReader mKernelCpuThreadReader;
+ private int[] mBinderThreadNativeTids;
+
+ private int[] mThreadCpuTimesUs;
+ private int[] mBinderThreadCpuTimesUs;
+ private long[] mLastThreadCpuTimesUs;
+ private long[] mLastBinderThreadCpuTimesUs;
+
+ /**
+ * Times (in microseconds) spent by the system server UID.
+ */
+ public static class SystemServiceCpuThreadTimes {
+ // All threads
+ public long[] threadCpuTimesUs;
+ // Just the threads handling incoming binder calls
+ public long[] binderThreadCpuTimesUs;
+ }
+
+ private SystemServiceCpuThreadTimes mDeltaCpuThreadTimes = new SystemServiceCpuThreadTimes();
+
+ /**
+ * Creates a configured instance of SystemServerCpuThreadReader.
+ */
+ public static SystemServerCpuThreadReader create() {
+ return new SystemServerCpuThreadReader(
+ KernelCpuThreadReader.create(0, uid -> uid == Process.myUid()));
+ }
+
+ @VisibleForTesting
+ public SystemServerCpuThreadReader(Path procPath, int systemServerUid) throws IOException {
+ this(new KernelCpuThreadReader(0, uid -> uid == systemServerUid, null, null,
+ new KernelCpuThreadReader.Injector() {
+ @Override
+ public int getUidForPid(int pid) {
+ return systemServerUid;
+ }
+ }));
+ }
+
+ @VisibleForTesting
+ public SystemServerCpuThreadReader(KernelCpuThreadReader kernelCpuThreadReader) {
+ mKernelCpuThreadReader = kernelCpuThreadReader;
+ }
+
+ public void setBinderThreadNativeTids(int[] nativeTids) {
+ mBinderThreadNativeTids = nativeTids;
+ }
+
+ /**
+ * Returns delta of CPU times, per thread, since the previous call to this method.
+ */
+ public SystemServiceCpuThreadTimes readDelta() {
+ if (mBinderThreadCpuTimesUs == null) {
+ int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz().length;
+ mThreadCpuTimesUs = new int[numCpuFrequencies];
+ mBinderThreadCpuTimesUs = new int[numCpuFrequencies];
+
+ mLastThreadCpuTimesUs = new long[numCpuFrequencies];
+ mLastBinderThreadCpuTimesUs = new long[numCpuFrequencies];
+
+ mDeltaCpuThreadTimes.threadCpuTimesUs = new long[numCpuFrequencies];
+ mDeltaCpuThreadTimes.binderThreadCpuTimesUs = new long[numCpuFrequencies];
+ }
+
+ Arrays.fill(mThreadCpuTimesUs, 0);
+ Arrays.fill(mBinderThreadCpuTimesUs, 0);
+
+ ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsage =
+ mKernelCpuThreadReader.getProcessCpuUsage();
+ int processCpuUsageSize = processCpuUsage.size();
+ for (int i = 0; i < processCpuUsageSize; i++) {
+ KernelCpuThreadReader.ProcessCpuUsage pcu = processCpuUsage.get(i);
+ ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = pcu.threadCpuUsages;
+ if (threadCpuUsages != null) {
+ int threadCpuUsagesSize = threadCpuUsages.size();
+ for (int j = 0; j < threadCpuUsagesSize; j++) {
+ KernelCpuThreadReader.ThreadCpuUsage tcu = threadCpuUsages.get(j);
+ boolean isBinderThread = isBinderThread(tcu.threadId);
+
+ final int len = Math.min(tcu.usageTimesMillis.length, mThreadCpuTimesUs.length);
+ for (int k = 0; k < len; k++) {
+ int usageTimeUs = tcu.usageTimesMillis[k] * 1000;
+ mThreadCpuTimesUs[k] += usageTimeUs;
+ if (isBinderThread) {
+ mBinderThreadCpuTimesUs[k] += usageTimeUs;
+ }
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < mThreadCpuTimesUs.length; i++) {
+ if (mThreadCpuTimesUs[i] < mLastThreadCpuTimesUs[i]) {
+ mDeltaCpuThreadTimes.threadCpuTimesUs[i] = mThreadCpuTimesUs[i];
+ mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] = mBinderThreadCpuTimesUs[i];
+ } else {
+ mDeltaCpuThreadTimes.threadCpuTimesUs[i] =
+ mThreadCpuTimesUs[i] - mLastThreadCpuTimesUs[i];
+ mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] =
+ mBinderThreadCpuTimesUs[i] - mLastBinderThreadCpuTimesUs[i];
+ }
+ mLastThreadCpuTimesUs[i] = mThreadCpuTimesUs[i];
+ mLastBinderThreadCpuTimesUs[i] = mBinderThreadCpuTimesUs[i];
+ }
+
+ return mDeltaCpuThreadTimes;
+ }
+
+ private boolean isBinderThread(int threadId) {
+ if (mBinderThreadNativeTids != null) {
+ for (int i = 0; i < mBinderThreadNativeTids.length; i++) {
+ if (threadId == mBinderThreadNativeTids[i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
new file mode 100644
index 000000000000..481b901b3c69
--- /dev/null
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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.internal.os;
+
+import android.os.BatteryStats;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Estimates the amount of power consumed by the System Server handling requests from
+ * a given app.
+ */
+public class SystemServicePowerCalculator extends PowerCalculator {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "SystemServicePowerCalc";
+
+ private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
+
+ private final PowerProfile mPowerProfile;
+ private final BatteryStats mBatteryStats;
+ // Tracks system server CPU [cluster][speed] power in milliAmp-microseconds
+ private double[][] mSystemServicePowerMaUs;
+
+ public SystemServicePowerCalculator(PowerProfile powerProfile, BatteryStats batteryStats) {
+ mPowerProfile = powerProfile;
+ mBatteryStats = batteryStats;
+ }
+
+ @Override
+ public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ final double proportionalUsage = u.getProportionalSystemServiceUsage();
+ if (proportionalUsage > 0) {
+ if (mSystemServicePowerMaUs == null) {
+ updateSystemServicePower();
+ }
+
+ double cpuPowerMaUs = 0;
+ int numCpuClusters = mPowerProfile.getNumCpuClusters();
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+ for (int speed = 0; speed < numSpeeds; speed++) {
+ cpuPowerMaUs += mSystemServicePowerMaUs[cluster][speed] * proportionalUsage;
+ }
+ }
+
+ app.systemServiceCpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
+ }
+ }
+
+ private void updateSystemServicePower() {
+ final int numCpuClusters = mPowerProfile.getNumCpuClusters();
+ mSystemServicePowerMaUs = new double[numCpuClusters][];
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+ mSystemServicePowerMaUs[cluster] = new double[numSpeeds];
+ for (int speed = 0; speed < numSpeeds; speed++) {
+ mSystemServicePowerMaUs[cluster][speed] =
+ mBatteryStats.getSystemServiceTimeAtCpuSpeed(cluster, speed)
+ * mPowerProfile.getAveragePowerForCpuCore(cluster, speed);
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "System service power per CPU cluster and frequency");
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ Log.d(TAG, "Cluster[" + cluster + "]: "
+ + Arrays.toString(mSystemServicePowerMaUs[cluster]));
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ mSystemServicePowerMaUs = null;
+ }
+}