Merge "BatteryStats: Acquire network stats without BatteryStatsImpl lock" into oc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ceb24e88cb
@@ -56,6 +56,7 @@ import android.util.LogWriter;
|
|||||||
import android.util.LongSparseArray;
|
import android.util.LongSparseArray;
|
||||||
import android.util.LongSparseLongArray;
|
import android.util.LongSparseLongArray;
|
||||||
import android.util.MutableInt;
|
import android.util.MutableInt;
|
||||||
|
import android.util.Pools;
|
||||||
import android.util.PrintWriterPrinter;
|
import android.util.PrintWriterPrinter;
|
||||||
import android.util.Printer;
|
import android.util.Printer;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
@@ -66,6 +67,7 @@ import android.util.TimeUtils;
|
|||||||
import android.util.Xml;
|
import android.util.Xml;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.net.NetworkStatsFactory;
|
import com.android.internal.net.NetworkStatsFactory;
|
||||||
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.ArrayUtils;
|
||||||
@@ -173,7 +175,6 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
|
|
||||||
private final PlatformIdleStateCallback mPlatformIdleStateCallback;
|
private final PlatformIdleStateCallback mPlatformIdleStateCallback;
|
||||||
|
|
||||||
|
|
||||||
final class MyHandler extends Handler {
|
final class MyHandler extends Handler {
|
||||||
public MyHandler(Looper looper) {
|
public MyHandler(Looper looper) {
|
||||||
super(looper, null, true);
|
super(looper, null, true);
|
||||||
@@ -228,11 +229,11 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface ExternalStatsSync {
|
public interface ExternalStatsSync {
|
||||||
public static final int UPDATE_CPU = 0x01;
|
int UPDATE_CPU = 0x01;
|
||||||
public static final int UPDATE_WIFI = 0x02;
|
int UPDATE_WIFI = 0x02;
|
||||||
public static final int UPDATE_RADIO = 0x04;
|
int UPDATE_RADIO = 0x04;
|
||||||
public static final int UPDATE_BT = 0x08;
|
int UPDATE_BT = 0x08;
|
||||||
public static final int UPDATE_ALL = UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT;
|
int UPDATE_ALL = UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT;
|
||||||
|
|
||||||
void scheduleSync(String reason, int flags);
|
void scheduleSync(String reason, int flags);
|
||||||
void scheduleCpuSyncDueToRemovedUid(int uid);
|
void scheduleCpuSyncDueToRemovedUid(int uid);
|
||||||
@@ -572,8 +573,6 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
private int mMinLearnedBatteryCapacity = -1;
|
private int mMinLearnedBatteryCapacity = -1;
|
||||||
private int mMaxLearnedBatteryCapacity = -1;
|
private int mMaxLearnedBatteryCapacity = -1;
|
||||||
|
|
||||||
private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
|
|
||||||
|
|
||||||
private long[] mCpuFreqs;
|
private long[] mCpuFreqs;
|
||||||
|
|
||||||
private PowerProfile mPowerProfile;
|
private PowerProfile mPowerProfile;
|
||||||
@@ -637,19 +636,9 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
|
|
||||||
private void init(Clocks clocks) {
|
private void init(Clocks clocks) {
|
||||||
mClocks = clocks;
|
mClocks = clocks;
|
||||||
mMobileNetworkStats = new NetworkStats[] {
|
|
||||||
new NetworkStats(mClocks.elapsedRealtime(), 50),
|
|
||||||
new NetworkStats(mClocks.elapsedRealtime(), 50),
|
|
||||||
new NetworkStats(mClocks.elapsedRealtime(), 50)
|
|
||||||
};
|
|
||||||
mWifiNetworkStats = new NetworkStats[] {
|
|
||||||
new NetworkStats(mClocks.elapsedRealtime(), 50),
|
|
||||||
new NetworkStats(mClocks.elapsedRealtime(), 50),
|
|
||||||
new NetworkStats(mClocks.elapsedRealtime(), 50)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static interface TimeBaseObs {
|
public interface TimeBaseObs {
|
||||||
void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime);
|
void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime);
|
||||||
void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime);
|
void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime);
|
||||||
}
|
}
|
||||||
@@ -4200,7 +4189,10 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
getUidStatsLocked(uid).noteMobileRadioApWakeupLocked();
|
getUidStatsLocked(uid).noteMobileRadioApWakeupLocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void noteMobileRadioPowerState(int powerState, long timestampNs, int uid) {
|
/**
|
||||||
|
* Updates the radio power state and returns true if an external stats collection should occur.
|
||||||
|
*/
|
||||||
|
public boolean noteMobileRadioPowerStateLocked(int powerState, long timestampNs, int uid) {
|
||||||
final long elapsedRealtime = mClocks.elapsedRealtime();
|
final long elapsedRealtime = mClocks.elapsedRealtime();
|
||||||
final long uptime = mClocks.uptimeMillis();
|
final long uptime = mClocks.uptimeMillis();
|
||||||
if (mMobileRadioPowerState != powerState) {
|
if (mMobileRadioPowerState != powerState) {
|
||||||
@@ -4237,13 +4229,15 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime);
|
mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime);
|
||||||
} else {
|
} else {
|
||||||
mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
|
mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
|
||||||
updateMobileRadioStateLocked(realElapsedRealtimeMs, null);
|
|
||||||
mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
|
mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
|
||||||
|
// Tell the caller to collect radio network/power stats.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notePowerSaveMode(boolean enabled) {
|
public void notePowerSaveModeLocked(boolean enabled) {
|
||||||
if (mPowerSaveModeEnabled != enabled) {
|
if (mPowerSaveModeEnabled != enabled) {
|
||||||
int stepState = enabled ? STEP_LEVEL_MODE_POWER_SAVE : 0;
|
int stepState = enabled ? STEP_LEVEL_MODE_POWER_SAVE : 0;
|
||||||
mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_POWER_SAVE) ^ stepState;
|
mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_POWER_SAVE) ^ stepState;
|
||||||
@@ -5243,28 +5237,26 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
|
|
||||||
public void noteNetworkInterfaceTypeLocked(String iface, int networkType) {
|
public void noteNetworkInterfaceTypeLocked(String iface, int networkType) {
|
||||||
if (TextUtils.isEmpty(iface)) return;
|
if (TextUtils.isEmpty(iface)) return;
|
||||||
if (ConnectivityManager.isNetworkTypeMobile(networkType)) {
|
|
||||||
mMobileIfaces = includeInStringArray(mMobileIfaces, iface);
|
|
||||||
if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mMobileIfaces);
|
|
||||||
} else {
|
|
||||||
mMobileIfaces = excludeFromStringArray(mMobileIfaces, iface);
|
|
||||||
if (DEBUG) Slog.d(TAG, "Note non-mobile iface " + iface + ": " + mMobileIfaces);
|
|
||||||
}
|
|
||||||
if (ConnectivityManager.isNetworkTypeWifi(networkType)) {
|
|
||||||
mWifiIfaces = includeInStringArray(mWifiIfaces, iface);
|
|
||||||
if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces);
|
|
||||||
} else {
|
|
||||||
mWifiIfaces = excludeFromStringArray(mWifiIfaces, iface);
|
|
||||||
if (DEBUG) Slog.d(TAG, "Note non-wifi iface " + iface + ": " + mWifiIfaces);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void noteNetworkStatsEnabledLocked() {
|
synchronized (mModemNetworkLock) {
|
||||||
// During device boot, qtaguid isn't enabled until after the inital
|
if (ConnectivityManager.isNetworkTypeMobile(networkType)) {
|
||||||
// loading of battery stats. Now that they're enabled, take our initial
|
mModemIfaces = includeInStringArray(mModemIfaces, iface);
|
||||||
// snapshot for future delta calculation.
|
if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mModemIfaces);
|
||||||
updateMobileRadioStateLocked(mClocks.elapsedRealtime(), null);
|
} else {
|
||||||
updateWifiStateLocked(null);
|
mModemIfaces = excludeFromStringArray(mModemIfaces, iface);
|
||||||
|
if (DEBUG) Slog.d(TAG, "Note non-mobile iface " + iface + ": " + mModemIfaces);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (mWifiNetworkLock) {
|
||||||
|
if (ConnectivityManager.isNetworkTypeWifi(networkType)) {
|
||||||
|
mWifiIfaces = includeInStringArray(mWifiIfaces, iface);
|
||||||
|
if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces);
|
||||||
|
} else {
|
||||||
|
mWifiIfaces = excludeFromStringArray(mWifiIfaces, iface);
|
||||||
|
if (DEBUG) Slog.d(TAG, "Note non-wifi iface " + iface + ": " + mWifiIfaces);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
|
@Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
|
||||||
@@ -8713,27 +8705,25 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
mPlatformIdleStateCallback = null;
|
mPlatformIdleStateCallback = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPowerProfile(PowerProfile profile) {
|
public void setPowerProfileLocked(PowerProfile profile) {
|
||||||
synchronized (this) {
|
mPowerProfile = profile;
|
||||||
mPowerProfile = profile;
|
|
||||||
|
|
||||||
// We need to initialize the KernelCpuSpeedReaders to read from
|
// We need to initialize the KernelCpuSpeedReaders to read from
|
||||||
// the first cpu of each core. Once we have the PowerProfile, we have access to this
|
// the first cpu of each core. Once we have the PowerProfile, we have access to this
|
||||||
// information.
|
// information.
|
||||||
final int numClusters = mPowerProfile.getNumCpuClusters();
|
final int numClusters = mPowerProfile.getNumCpuClusters();
|
||||||
mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
|
mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
|
||||||
int firstCpuOfCluster = 0;
|
int firstCpuOfCluster = 0;
|
||||||
for (int i = 0; i < numClusters; i++) {
|
for (int i = 0; i < numClusters; i++) {
|
||||||
final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i);
|
final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i);
|
||||||
mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
|
mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
|
||||||
numSpeedSteps);
|
numSpeedSteps);
|
||||||
firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
|
firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mEstimatedBatteryCapacity == -1) {
|
if (mEstimatedBatteryCapacity == -1) {
|
||||||
// Initialize the estimated battery capacity to a known preset one.
|
// Initialize the estimated battery capacity to a known preset one.
|
||||||
mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
|
mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8741,7 +8731,7 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
mCallback = cb;
|
mCallback = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRadioScanningTimeout(long timeout) {
|
public void setRadioScanningTimeoutLocked(long timeout) {
|
||||||
if (mPhoneSignalScanningTimer != null) {
|
if (mPhoneSignalScanningTimer != null) {
|
||||||
mPhoneSignalScanningTimer.setTimeout(timeout);
|
mPhoneSignalScanningTimer.setTimeout(timeout);
|
||||||
}
|
}
|
||||||
@@ -9433,277 +9423,293 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] mMobileIfaces = EmptyArray.STRING;
|
private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
|
||||||
|
private final Pools.Pool<NetworkStats> mNetworkStatsPool = new Pools.SynchronizedPool<>(6);
|
||||||
|
|
||||||
|
private final Object mWifiNetworkLock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("mWifiNetworkLock")
|
||||||
private String[] mWifiIfaces = EmptyArray.STRING;
|
private String[] mWifiIfaces = EmptyArray.STRING;
|
||||||
|
|
||||||
private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
|
@GuardedBy("mWifiNetworkLock")
|
||||||
|
private NetworkStats mLastWifiNetworkStats = new NetworkStats(0, -1);
|
||||||
|
|
||||||
private static final int NETWORK_STATS_LAST = 0;
|
private final Object mModemNetworkLock = new Object();
|
||||||
private static final int NETWORK_STATS_NEXT = 1;
|
|
||||||
private static final int NETWORK_STATS_DELTA = 2;
|
|
||||||
|
|
||||||
private NetworkStats[] mMobileNetworkStats;
|
@GuardedBy("mModemNetworkLock")
|
||||||
private NetworkStats[] mWifiNetworkStats;
|
private String[] mModemIfaces = EmptyArray.STRING;
|
||||||
|
|
||||||
/**
|
@GuardedBy("mModemNetworkLock")
|
||||||
* Retrieves the delta of network stats for the given network ifaces. Uses networkStatsBuffer
|
private NetworkStats mLastModemNetworkStats = new NetworkStats(0, -1);
|
||||||
* as a buffer of NetworkStats objects to cycle through when computing deltas.
|
|
||||||
*/
|
private NetworkStats readNetworkStatsLocked(String[] ifaces) {
|
||||||
private NetworkStats getNetworkStatsDeltaLocked(String[] ifaces,
|
try {
|
||||||
NetworkStats[] networkStatsBuffer)
|
if (!ArrayUtils.isEmpty(ifaces)) {
|
||||||
throws IOException {
|
return mNetworkStatsFactory.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
|
||||||
if (!SystemProperties.getBoolean(NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED,
|
NetworkStats.TAG_NONE, mNetworkStatsPool.acquire());
|
||||||
false)) {
|
}
|
||||||
return null;
|
} catch (IOException e) {
|
||||||
|
Slog.e(TAG, "failed to read network stats for ifaces: " + Arrays.toString(ifaces));
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
final NetworkStats stats = mNetworkStatsFactory.readNetworkStatsDetail(NetworkStats.UID_ALL,
|
|
||||||
ifaces, NetworkStats.TAG_NONE, networkStatsBuffer[NETWORK_STATS_NEXT]);
|
|
||||||
networkStatsBuffer[NETWORK_STATS_DELTA] = NetworkStats.subtract(stats,
|
|
||||||
networkStatsBuffer[NETWORK_STATS_LAST], null, null,
|
|
||||||
networkStatsBuffer[NETWORK_STATS_DELTA]);
|
|
||||||
networkStatsBuffer[NETWORK_STATS_NEXT] = networkStatsBuffer[NETWORK_STATS_LAST];
|
|
||||||
networkStatsBuffer[NETWORK_STATS_LAST] = stats;
|
|
||||||
return networkStatsBuffer[NETWORK_STATS_DELTA];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Distribute WiFi energy info and network traffic to apps.
|
* Distribute WiFi energy info and network traffic to apps.
|
||||||
* @param info The energy information from the WiFi controller.
|
* @param info The energy information from the WiFi controller.
|
||||||
*/
|
*/
|
||||||
public void updateWifiStateLocked(@Nullable final WifiActivityEnergyInfo info) {
|
public void updateWifiState(@Nullable final WifiActivityEnergyInfo info) {
|
||||||
if (DEBUG_ENERGY) {
|
if (DEBUG_ENERGY) {
|
||||||
Slog.d(TAG, "Updating wifi stats");
|
Slog.d(TAG, "Updating wifi stats: " + Arrays.toString(mWifiIfaces));
|
||||||
}
|
}
|
||||||
|
|
||||||
final long elapsedRealtimeMs = mClocks.elapsedRealtime();
|
// Grab a separate lock to acquire the network stats, which may do I/O.
|
||||||
NetworkStats delta = null;
|
NetworkStats delta = null;
|
||||||
try {
|
synchronized (mWifiNetworkLock) {
|
||||||
if (!ArrayUtils.isEmpty(mWifiIfaces)) {
|
final NetworkStats latestStats = readNetworkStatsLocked(mWifiIfaces);
|
||||||
delta = getNetworkStatsDeltaLocked(mWifiIfaces, mWifiNetworkStats);
|
if (latestStats != null) {
|
||||||
|
delta = NetworkStats.subtract(latestStats, mLastWifiNetworkStats, null, null,
|
||||||
|
mNetworkStatsPool.acquire());
|
||||||
|
mNetworkStatsPool.release(mLastWifiNetworkStats);
|
||||||
|
mLastWifiNetworkStats = latestStats;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
Slog.wtf(TAG, "Failed to get wifi network stats", e);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mOnBatteryInternal) {
|
synchronized (this) {
|
||||||
return;
|
if (!mOnBatteryInternal) {
|
||||||
}
|
if (delta != null) {
|
||||||
|
mNetworkStatsPool.release(delta);
|
||||||
SparseLongArray rxPackets = new SparseLongArray();
|
|
||||||
SparseLongArray txPackets = new SparseLongArray();
|
|
||||||
long totalTxPackets = 0;
|
|
||||||
long totalRxPackets = 0;
|
|
||||||
if (delta != null) {
|
|
||||||
final int size = delta.size();
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
|
|
||||||
|
|
||||||
if (DEBUG_ENERGY) {
|
|
||||||
Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
|
|
||||||
+ " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
|
|
||||||
+ " txPackets=" + entry.txPackets);
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.rxBytes == 0 && entry.txBytes == 0) {
|
final long elapsedRealtimeMs = mClocks.elapsedRealtime();
|
||||||
// Skip the lookup below since there is no work to do.
|
SparseLongArray rxPackets = new SparseLongArray();
|
||||||
continue;
|
SparseLongArray txPackets = new SparseLongArray();
|
||||||
}
|
long totalTxPackets = 0;
|
||||||
|
long totalRxPackets = 0;
|
||||||
|
if (delta != null) {
|
||||||
|
NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||||
|
final int size = delta.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
entry = delta.getValues(i, entry);
|
||||||
|
|
||||||
final Uid u = getUidStatsLocked(mapUid(entry.uid));
|
if (DEBUG_ENERGY) {
|
||||||
if (entry.rxBytes != 0) {
|
Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
|
||||||
u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
|
+ " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
|
||||||
entry.rxPackets);
|
+ " txPackets=" + entry.txPackets);
|
||||||
if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
|
}
|
||||||
u.noteNetworkActivityLocked(NETWORK_WIFI_BG_RX_DATA, entry.rxBytes,
|
|
||||||
|
if (entry.rxBytes == 0 && entry.txBytes == 0) {
|
||||||
|
// Skip the lookup below since there is no work to do.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Uid u = getUidStatsLocked(mapUid(entry.uid));
|
||||||
|
if (entry.rxBytes != 0) {
|
||||||
|
u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
|
||||||
entry.rxPackets);
|
entry.rxPackets);
|
||||||
|
if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
|
||||||
|
u.noteNetworkActivityLocked(NETWORK_WIFI_BG_RX_DATA, entry.rxBytes,
|
||||||
|
entry.rxPackets);
|
||||||
|
}
|
||||||
|
mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
|
||||||
|
entry.rxBytes);
|
||||||
|
mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
|
||||||
|
entry.rxPackets);
|
||||||
|
|
||||||
|
rxPackets.put(u.getUid(), entry.rxPackets);
|
||||||
|
|
||||||
|
// Sum the total number of packets so that the Rx Power can
|
||||||
|
// be evenly distributed amongst the apps.
|
||||||
|
totalRxPackets += entry.rxPackets;
|
||||||
}
|
}
|
||||||
mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
|
|
||||||
entry.rxBytes);
|
|
||||||
mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
|
|
||||||
entry.rxPackets);
|
|
||||||
|
|
||||||
rxPackets.put(u.getUid(), entry.rxPackets);
|
if (entry.txBytes != 0) {
|
||||||
|
u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
|
||||||
// Sum the total number of packets so that the Rx Power can
|
|
||||||
// be evenly distributed amongst the apps.
|
|
||||||
totalRxPackets += entry.rxPackets;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.txBytes != 0) {
|
|
||||||
u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
|
|
||||||
entry.txPackets);
|
|
||||||
if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
|
|
||||||
u.noteNetworkActivityLocked(NETWORK_WIFI_BG_TX_DATA, entry.txBytes,
|
|
||||||
entry.txPackets);
|
entry.txPackets);
|
||||||
|
if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
|
||||||
|
u.noteNetworkActivityLocked(NETWORK_WIFI_BG_TX_DATA, entry.txBytes,
|
||||||
|
entry.txPackets);
|
||||||
|
}
|
||||||
|
mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
|
||||||
|
entry.txBytes);
|
||||||
|
mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
|
||||||
|
entry.txPackets);
|
||||||
|
|
||||||
|
txPackets.put(u.getUid(), entry.txPackets);
|
||||||
|
|
||||||
|
// Sum the total number of packets so that the Tx Power can
|
||||||
|
// be evenly distributed amongst the apps.
|
||||||
|
totalTxPackets += entry.txPackets;
|
||||||
}
|
}
|
||||||
mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
|
|
||||||
entry.txBytes);
|
|
||||||
mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
|
|
||||||
entry.txPackets);
|
|
||||||
|
|
||||||
txPackets.put(u.getUid(), entry.txPackets);
|
|
||||||
|
|
||||||
// Sum the total number of packets so that the Tx Power can
|
|
||||||
// be evenly distributed amongst the apps.
|
|
||||||
totalTxPackets += entry.txPackets;
|
|
||||||
}
|
}
|
||||||
}
|
mNetworkStatsPool.release(delta);
|
||||||
}
|
delta = null;
|
||||||
|
|
||||||
if (info != null) {
|
|
||||||
mHasWifiReporting = true;
|
|
||||||
|
|
||||||
// Measured in mAms
|
|
||||||
final long txTimeMs = info.getControllerTxTimeMillis();
|
|
||||||
final long rxTimeMs = info.getControllerRxTimeMillis();
|
|
||||||
final long idleTimeMs = info.getControllerIdleTimeMillis();
|
|
||||||
final long totalTimeMs = txTimeMs + rxTimeMs + idleTimeMs;
|
|
||||||
|
|
||||||
long leftOverRxTimeMs = rxTimeMs;
|
|
||||||
long leftOverTxTimeMs = txTimeMs;
|
|
||||||
|
|
||||||
if (DEBUG_ENERGY) {
|
|
||||||
Slog.d(TAG, "------ BEGIN WiFi power blaming ------");
|
|
||||||
Slog.d(TAG, " Tx Time: " + txTimeMs + " ms");
|
|
||||||
Slog.d(TAG, " Rx Time: " + rxTimeMs + " ms");
|
|
||||||
Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms");
|
|
||||||
Slog.d(TAG, " Total Time: " + totalTimeMs + " ms");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long totalWifiLockTimeMs = 0;
|
if (info != null) {
|
||||||
long totalScanTimeMs = 0;
|
mHasWifiReporting = true;
|
||||||
|
|
||||||
// On the first pass, collect some totals so that we can normalize power
|
// Measured in mAms
|
||||||
// calculations if we need to.
|
final long txTimeMs = info.getControllerTxTimeMillis();
|
||||||
final int uidStatsSize = mUidStats.size();
|
final long rxTimeMs = info.getControllerRxTimeMillis();
|
||||||
for (int i = 0; i < uidStatsSize; i++) {
|
final long idleTimeMs = info.getControllerIdleTimeMillis();
|
||||||
final Uid uid = mUidStats.valueAt(i);
|
final long totalTimeMs = txTimeMs + rxTimeMs + idleTimeMs;
|
||||||
|
|
||||||
// Sum the total scan power for all apps.
|
long leftOverRxTimeMs = rxTimeMs;
|
||||||
totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
|
long leftOverTxTimeMs = txTimeMs;
|
||||||
elapsedRealtimeMs * 1000) / 1000;
|
|
||||||
|
|
||||||
// Sum the total time holding wifi lock for all apps.
|
|
||||||
totalWifiLockTimeMs += uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
|
|
||||||
elapsedRealtimeMs * 1000) / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG_ENERGY && totalScanTimeMs > rxTimeMs) {
|
|
||||||
Slog.d(TAG, " !Estimated scan time > Actual rx time (" + totalScanTimeMs + " ms > "
|
|
||||||
+ rxTimeMs + " ms). Normalizing scan time.");
|
|
||||||
}
|
|
||||||
if (DEBUG_ENERGY && totalScanTimeMs > txTimeMs) {
|
|
||||||
Slog.d(TAG, " !Estimated scan time > Actual tx time (" + totalScanTimeMs + " ms > "
|
|
||||||
+ txTimeMs + " ms). Normalizing scan time.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actually assign and distribute power usage to apps.
|
|
||||||
for (int i = 0; i < uidStatsSize; i++) {
|
|
||||||
final Uid uid = mUidStats.valueAt(i);
|
|
||||||
|
|
||||||
long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
|
|
||||||
elapsedRealtimeMs * 1000) / 1000;
|
|
||||||
if (scanTimeSinceMarkMs > 0) {
|
|
||||||
// Set the new mark so that next time we get new data since this point.
|
|
||||||
uid.mWifiScanTimer.setMark(elapsedRealtimeMs);
|
|
||||||
|
|
||||||
long scanRxTimeSinceMarkMs = scanTimeSinceMarkMs;
|
|
||||||
long scanTxTimeSinceMarkMs = scanTimeSinceMarkMs;
|
|
||||||
|
|
||||||
// Our total scan time is more than the reported Tx/Rx time.
|
|
||||||
// This is possible because the cost of a scan is approximate.
|
|
||||||
// Let's normalize the result so that we evenly blame each app
|
|
||||||
// scanning.
|
|
||||||
//
|
|
||||||
// This means that we may have apps that transmitted/received packets not be
|
|
||||||
// blamed for this, but this is fine as scans are relatively more expensive.
|
|
||||||
if (totalScanTimeMs > rxTimeMs) {
|
|
||||||
scanRxTimeSinceMarkMs = (rxTimeMs * scanRxTimeSinceMarkMs) /
|
|
||||||
totalScanTimeMs;
|
|
||||||
}
|
|
||||||
if (totalScanTimeMs > txTimeMs) {
|
|
||||||
scanTxTimeSinceMarkMs = (txTimeMs * scanTxTimeSinceMarkMs) /
|
|
||||||
totalScanTimeMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG_ENERGY) {
|
|
||||||
Slog.d(TAG, " ScanTime for UID " + uid.getUid() + ": Rx:"
|
|
||||||
+ scanRxTimeSinceMarkMs + " ms Tx:"
|
|
||||||
+ scanTxTimeSinceMarkMs + " ms)");
|
|
||||||
}
|
|
||||||
|
|
||||||
ControllerActivityCounterImpl activityCounter =
|
|
||||||
uid.getOrCreateWifiControllerActivityLocked();
|
|
||||||
activityCounter.getRxTimeCounter().addCountLocked(scanRxTimeSinceMarkMs);
|
|
||||||
activityCounter.getTxTimeCounters()[0].addCountLocked(scanTxTimeSinceMarkMs);
|
|
||||||
leftOverRxTimeMs -= scanRxTimeSinceMarkMs;
|
|
||||||
leftOverTxTimeMs -= scanTxTimeSinceMarkMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Distribute evenly the power consumed while Idle to each app holding a WiFi
|
|
||||||
// lock.
|
|
||||||
final long wifiLockTimeSinceMarkMs = uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
|
|
||||||
elapsedRealtimeMs * 1000) / 1000;
|
|
||||||
if (wifiLockTimeSinceMarkMs > 0) {
|
|
||||||
// Set the new mark so that next time we get new data since this point.
|
|
||||||
uid.mFullWifiLockTimer.setMark(elapsedRealtimeMs);
|
|
||||||
|
|
||||||
final long myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs)
|
|
||||||
/ totalWifiLockTimeMs;
|
|
||||||
if (DEBUG_ENERGY) {
|
|
||||||
Slog.d(TAG, " IdleTime for UID " + uid.getUid() + ": "
|
|
||||||
+ myIdleTimeMs + " ms");
|
|
||||||
}
|
|
||||||
uid.getOrCreateWifiControllerActivityLocked().getIdleTimeCounter()
|
|
||||||
.addCountLocked(myIdleTimeMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG_ENERGY) {
|
|
||||||
Slog.d(TAG, " New RxPower: " + leftOverRxTimeMs + " ms");
|
|
||||||
Slog.d(TAG, " New TxPower: " + leftOverTxTimeMs + " ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Distribute the remaining Tx power appropriately between all apps that transmitted
|
|
||||||
// packets.
|
|
||||||
for (int i = 0; i < txPackets.size(); i++) {
|
|
||||||
final Uid uid = getUidStatsLocked(txPackets.keyAt(i));
|
|
||||||
final long myTxTimeMs = (txPackets.valueAt(i) * leftOverTxTimeMs) / totalTxPackets;
|
|
||||||
if (DEBUG_ENERGY) {
|
if (DEBUG_ENERGY) {
|
||||||
Slog.d(TAG, " TxTime for UID " + uid.getUid() + ": " + myTxTimeMs + " ms");
|
Slog.d(TAG, "------ BEGIN WiFi power blaming ------");
|
||||||
|
Slog.d(TAG, " Tx Time: " + txTimeMs + " ms");
|
||||||
|
Slog.d(TAG, " Rx Time: " + rxTimeMs + " ms");
|
||||||
|
Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms");
|
||||||
|
Slog.d(TAG, " Total Time: " + totalTimeMs + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
long totalWifiLockTimeMs = 0;
|
||||||
|
long totalScanTimeMs = 0;
|
||||||
|
|
||||||
|
// On the first pass, collect some totals so that we can normalize power
|
||||||
|
// calculations if we need to.
|
||||||
|
final int uidStatsSize = mUidStats.size();
|
||||||
|
for (int i = 0; i < uidStatsSize; i++) {
|
||||||
|
final Uid uid = mUidStats.valueAt(i);
|
||||||
|
|
||||||
|
// Sum the total scan power for all apps.
|
||||||
|
totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
|
||||||
|
elapsedRealtimeMs * 1000) / 1000;
|
||||||
|
|
||||||
|
// Sum the total time holding wifi lock for all apps.
|
||||||
|
totalWifiLockTimeMs += uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
|
||||||
|
elapsedRealtimeMs * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_ENERGY && totalScanTimeMs > rxTimeMs) {
|
||||||
|
Slog.d(TAG,
|
||||||
|
" !Estimated scan time > Actual rx time (" + totalScanTimeMs + " ms > "
|
||||||
|
+ rxTimeMs + " ms). Normalizing scan time.");
|
||||||
|
}
|
||||||
|
if (DEBUG_ENERGY && totalScanTimeMs > txTimeMs) {
|
||||||
|
Slog.d(TAG,
|
||||||
|
" !Estimated scan time > Actual tx time (" + totalScanTimeMs + " ms > "
|
||||||
|
+ txTimeMs + " ms). Normalizing scan time.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually assign and distribute power usage to apps.
|
||||||
|
for (int i = 0; i < uidStatsSize; i++) {
|
||||||
|
final Uid uid = mUidStats.valueAt(i);
|
||||||
|
|
||||||
|
long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
|
||||||
|
elapsedRealtimeMs * 1000) / 1000;
|
||||||
|
if (scanTimeSinceMarkMs > 0) {
|
||||||
|
// Set the new mark so that next time we get new data since this point.
|
||||||
|
uid.mWifiScanTimer.setMark(elapsedRealtimeMs);
|
||||||
|
|
||||||
|
long scanRxTimeSinceMarkMs = scanTimeSinceMarkMs;
|
||||||
|
long scanTxTimeSinceMarkMs = scanTimeSinceMarkMs;
|
||||||
|
|
||||||
|
// Our total scan time is more than the reported Tx/Rx time.
|
||||||
|
// This is possible because the cost of a scan is approximate.
|
||||||
|
// Let's normalize the result so that we evenly blame each app
|
||||||
|
// scanning.
|
||||||
|
//
|
||||||
|
// This means that we may have apps that transmitted/received packets not be
|
||||||
|
// blamed for this, but this is fine as scans are relatively more expensive.
|
||||||
|
if (totalScanTimeMs > rxTimeMs) {
|
||||||
|
scanRxTimeSinceMarkMs = (rxTimeMs * scanRxTimeSinceMarkMs) /
|
||||||
|
totalScanTimeMs;
|
||||||
|
}
|
||||||
|
if (totalScanTimeMs > txTimeMs) {
|
||||||
|
scanTxTimeSinceMarkMs = (txTimeMs * scanTxTimeSinceMarkMs) /
|
||||||
|
totalScanTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_ENERGY) {
|
||||||
|
Slog.d(TAG, " ScanTime for UID " + uid.getUid() + ": Rx:"
|
||||||
|
+ scanRxTimeSinceMarkMs + " ms Tx:"
|
||||||
|
+ scanTxTimeSinceMarkMs + " ms)");
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerActivityCounterImpl activityCounter =
|
||||||
|
uid.getOrCreateWifiControllerActivityLocked();
|
||||||
|
activityCounter.getRxTimeCounter().addCountLocked(scanRxTimeSinceMarkMs);
|
||||||
|
activityCounter.getTxTimeCounters()[0].addCountLocked(
|
||||||
|
scanTxTimeSinceMarkMs);
|
||||||
|
leftOverRxTimeMs -= scanRxTimeSinceMarkMs;
|
||||||
|
leftOverTxTimeMs -= scanTxTimeSinceMarkMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribute evenly the power consumed while Idle to each app holding a WiFi
|
||||||
|
// lock.
|
||||||
|
final long wifiLockTimeSinceMarkMs =
|
||||||
|
uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
|
||||||
|
elapsedRealtimeMs * 1000) / 1000;
|
||||||
|
if (wifiLockTimeSinceMarkMs > 0) {
|
||||||
|
// Set the new mark so that next time we get new data since this point.
|
||||||
|
uid.mFullWifiLockTimer.setMark(elapsedRealtimeMs);
|
||||||
|
|
||||||
|
final long myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs)
|
||||||
|
/ totalWifiLockTimeMs;
|
||||||
|
if (DEBUG_ENERGY) {
|
||||||
|
Slog.d(TAG, " IdleTime for UID " + uid.getUid() + ": "
|
||||||
|
+ myIdleTimeMs + " ms");
|
||||||
|
}
|
||||||
|
uid.getOrCreateWifiControllerActivityLocked().getIdleTimeCounter()
|
||||||
|
.addCountLocked(myIdleTimeMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
uid.getOrCreateWifiControllerActivityLocked().getTxTimeCounters()[0]
|
|
||||||
.addCountLocked(myTxTimeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Distribute the remaining Rx power appropriately between all apps that received
|
|
||||||
// packets.
|
|
||||||
for (int i = 0; i < rxPackets.size(); i++) {
|
|
||||||
final Uid uid = getUidStatsLocked(rxPackets.keyAt(i));
|
|
||||||
final long myRxTimeMs = (rxPackets.valueAt(i) * leftOverRxTimeMs) / totalRxPackets;
|
|
||||||
if (DEBUG_ENERGY) {
|
if (DEBUG_ENERGY) {
|
||||||
Slog.d(TAG, " RxTime for UID " + uid.getUid() + ": " + myRxTimeMs + " ms");
|
Slog.d(TAG, " New RxPower: " + leftOverRxTimeMs + " ms");
|
||||||
|
Slog.d(TAG, " New TxPower: " + leftOverTxTimeMs + " ms");
|
||||||
}
|
}
|
||||||
uid.getOrCreateWifiControllerActivityLocked().getRxTimeCounter()
|
|
||||||
.addCountLocked(myRxTimeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any left over power use will be picked up by the WiFi category in BatteryStatsHelper.
|
// Distribute the remaining Tx power appropriately between all apps that transmitted
|
||||||
|
// packets.
|
||||||
|
for (int i = 0; i < txPackets.size(); i++) {
|
||||||
|
final Uid uid = getUidStatsLocked(txPackets.keyAt(i));
|
||||||
|
final long myTxTimeMs = (txPackets.valueAt(i) * leftOverTxTimeMs)
|
||||||
|
/ totalTxPackets;
|
||||||
|
if (DEBUG_ENERGY) {
|
||||||
|
Slog.d(TAG, " TxTime for UID " + uid.getUid() + ": " + myTxTimeMs + " ms");
|
||||||
|
}
|
||||||
|
uid.getOrCreateWifiControllerActivityLocked().getTxTimeCounters()[0]
|
||||||
|
.addCountLocked(myTxTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
// Update WiFi controller stats.
|
// Distribute the remaining Rx power appropriately between all apps that received
|
||||||
mWifiActivity.getRxTimeCounter().addCountLocked(info.getControllerRxTimeMillis());
|
// packets.
|
||||||
mWifiActivity.getTxTimeCounters()[0].addCountLocked(info.getControllerTxTimeMillis());
|
for (int i = 0; i < rxPackets.size(); i++) {
|
||||||
mWifiActivity.getIdleTimeCounter().addCountLocked(info.getControllerIdleTimeMillis());
|
final Uid uid = getUidStatsLocked(rxPackets.keyAt(i));
|
||||||
|
final long myRxTimeMs = (rxPackets.valueAt(i) * leftOverRxTimeMs)
|
||||||
|
/ totalRxPackets;
|
||||||
|
if (DEBUG_ENERGY) {
|
||||||
|
Slog.d(TAG, " RxTime for UID " + uid.getUid() + ": " + myRxTimeMs + " ms");
|
||||||
|
}
|
||||||
|
uid.getOrCreateWifiControllerActivityLocked().getRxTimeCounter()
|
||||||
|
.addCountLocked(myRxTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
// POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
|
// Any left over power use will be picked up by the WiFi category in BatteryStatsHelper.
|
||||||
final double opVolt = mPowerProfile.getAveragePower(
|
|
||||||
PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
|
|
||||||
if (opVolt != 0) {
|
// Update WiFi controller stats.
|
||||||
// We store the power drain as mAms.
|
mWifiActivity.getRxTimeCounter().addCountLocked(info.getControllerRxTimeMillis());
|
||||||
mWifiActivity.getPowerCounter().addCountLocked(
|
mWifiActivity.getTxTimeCounters()[0].addCountLocked(
|
||||||
(long)(info.getControllerEnergyUsed() / opVolt));
|
info.getControllerTxTimeMillis());
|
||||||
|
mWifiActivity.getIdleTimeCounter().addCountLocked(
|
||||||
|
info.getControllerIdleTimeMillis());
|
||||||
|
|
||||||
|
// POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
|
||||||
|
final double opVolt = mPowerProfile.getAveragePower(
|
||||||
|
PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
|
||||||
|
if (opVolt != 0) {
|
||||||
|
// We store the power drain as mAms.
|
||||||
|
mWifiActivity.getPowerCounter().addCountLocked(
|
||||||
|
(long) (info.getControllerEnergyUsed() / opVolt));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9711,133 +9717,148 @@ public class BatteryStatsImpl extends BatteryStats {
|
|||||||
/**
|
/**
|
||||||
* Distribute Cell radio energy info and network traffic to apps.
|
* Distribute Cell radio energy info and network traffic to apps.
|
||||||
*/
|
*/
|
||||||
public void updateMobileRadioStateLocked(final long elapsedRealtimeMs,
|
public void updateMobileRadioState(@Nullable final ModemActivityInfo activityInfo) {
|
||||||
final ModemActivityInfo activityInfo) {
|
|
||||||
if (DEBUG_ENERGY) {
|
if (DEBUG_ENERGY) {
|
||||||
Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
|
Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grab a separate lock to acquire the network stats, which may do I/O.
|
||||||
NetworkStats delta = null;
|
NetworkStats delta = null;
|
||||||
try {
|
synchronized (mModemNetworkLock) {
|
||||||
if (!ArrayUtils.isEmpty(mMobileIfaces)) {
|
final NetworkStats latestStats = readNetworkStatsLocked(mModemIfaces);
|
||||||
delta = getNetworkStatsDeltaLocked(mMobileIfaces, mMobileNetworkStats);
|
if (latestStats != null) {
|
||||||
|
delta = NetworkStats.subtract(latestStats, mLastModemNetworkStats, null, null,
|
||||||
|
mNetworkStatsPool.acquire());
|
||||||
|
mNetworkStatsPool.release(mLastModemNetworkStats);
|
||||||
|
mLastModemNetworkStats = latestStats;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
Slog.wtf(TAG, "Failed to get mobile network stats", e);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mOnBatteryInternal) {
|
synchronized (this) {
|
||||||
return;
|
if (!mOnBatteryInternal) {
|
||||||
}
|
if (delta != null) {
|
||||||
|
mNetworkStatsPool.release(delta);
|
||||||
long radioTime = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
|
|
||||||
elapsedRealtimeMs * 1000);
|
|
||||||
mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs);
|
|
||||||
|
|
||||||
long totalRxPackets = 0;
|
|
||||||
long totalTxPackets = 0;
|
|
||||||
if (delta != null) {
|
|
||||||
final int size = delta.size();
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
|
|
||||||
if (entry.rxPackets == 0 && entry.txPackets == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
if (DEBUG_ENERGY) {
|
|
||||||
Slog.d(TAG, "Mobile uid " + entry.uid + ": delta rx=" + entry.rxBytes
|
|
||||||
+ " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
|
|
||||||
+ " txPackets=" + entry.txPackets);
|
|
||||||
}
|
|
||||||
|
|
||||||
totalRxPackets += entry.rxPackets;
|
|
||||||
totalTxPackets += entry.txPackets;
|
|
||||||
|
|
||||||
final Uid u = getUidStatsLocked(mapUid(entry.uid));
|
|
||||||
u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes, entry.rxPackets);
|
|
||||||
u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes, entry.txPackets);
|
|
||||||
if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
|
|
||||||
u.noteNetworkActivityLocked(NETWORK_MOBILE_BG_RX_DATA,
|
|
||||||
entry.rxBytes, entry.rxPackets);
|
|
||||||
u.noteNetworkActivityLocked(NETWORK_MOBILE_BG_TX_DATA,
|
|
||||||
entry.txBytes, entry.txPackets);
|
|
||||||
}
|
|
||||||
|
|
||||||
mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
|
|
||||||
entry.rxBytes);
|
|
||||||
mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
|
|
||||||
entry.txBytes);
|
|
||||||
mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
|
|
||||||
entry.rxPackets);
|
|
||||||
mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
|
|
||||||
entry.txPackets);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now distribute proportional blame to the apps that did networking.
|
final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
|
||||||
long totalPackets = totalRxPackets + totalTxPackets;
|
long radioTime = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
|
||||||
if (totalPackets > 0) {
|
elapsedRealtimeMs * 1000);
|
||||||
|
mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs);
|
||||||
|
|
||||||
|
long totalRxPackets = 0;
|
||||||
|
long totalTxPackets = 0;
|
||||||
|
if (delta != null) {
|
||||||
|
NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||||
|
final int size = delta.size();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
|
entry = delta.getValues(i, entry);
|
||||||
if (entry.rxPackets == 0 && entry.txPackets == 0) {
|
if (entry.rxPackets == 0 && entry.txPackets == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DEBUG_ENERGY) {
|
||||||
|
Slog.d(TAG, "Mobile uid " + entry.uid + ": delta rx=" + entry.rxBytes
|
||||||
|
+ " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
|
||||||
|
+ " txPackets=" + entry.txPackets);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalRxPackets += entry.rxPackets;
|
||||||
|
totalTxPackets += entry.txPackets;
|
||||||
|
|
||||||
final Uid u = getUidStatsLocked(mapUid(entry.uid));
|
final Uid u = getUidStatsLocked(mapUid(entry.uid));
|
||||||
|
u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
|
||||||
|
entry.rxPackets);
|
||||||
|
u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes,
|
||||||
|
entry.txPackets);
|
||||||
|
if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
|
||||||
|
u.noteNetworkActivityLocked(NETWORK_MOBILE_BG_RX_DATA,
|
||||||
|
entry.rxBytes, entry.rxPackets);
|
||||||
|
u.noteNetworkActivityLocked(NETWORK_MOBILE_BG_TX_DATA,
|
||||||
|
entry.txBytes, entry.txPackets);
|
||||||
|
}
|
||||||
|
|
||||||
// Distribute total radio active time in to this app.
|
mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
|
||||||
final long appPackets = entry.rxPackets + entry.txPackets;
|
entry.rxBytes);
|
||||||
final long appRadioTime = (radioTime * appPackets) / totalPackets;
|
mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
|
||||||
u.noteMobileRadioActiveTimeLocked(appRadioTime);
|
entry.txBytes);
|
||||||
|
mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
|
||||||
|
entry.rxPackets);
|
||||||
|
mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
|
||||||
|
entry.txPackets);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove this app from the totals, so that we don't lose any time
|
// Now distribute proportional blame to the apps that did networking.
|
||||||
// due to rounding.
|
long totalPackets = totalRxPackets + totalTxPackets;
|
||||||
radioTime -= appRadioTime;
|
if (totalPackets > 0) {
|
||||||
totalPackets -= appPackets;
|
for (int i = 0; i < size; i++) {
|
||||||
|
entry = delta.getValues(i, entry);
|
||||||
if (activityInfo != null) {
|
if (entry.rxPackets == 0 && entry.txPackets == 0) {
|
||||||
ControllerActivityCounterImpl activityCounter =
|
continue;
|
||||||
u.getOrCreateModemControllerActivityLocked();
|
|
||||||
if (totalRxPackets > 0 && entry.rxPackets > 0) {
|
|
||||||
final long rxMs = (entry.rxPackets * activityInfo.getRxTimeMillis())
|
|
||||||
/ totalRxPackets;
|
|
||||||
activityCounter.getRxTimeCounter().addCountLocked(rxMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalTxPackets > 0 && entry.txPackets > 0) {
|
final Uid u = getUidStatsLocked(mapUid(entry.uid));
|
||||||
for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
|
|
||||||
long txMs = entry.txPackets * activityInfo.getTxTimeMillis()[lvl];
|
// Distribute total radio active time in to this app.
|
||||||
txMs /= totalTxPackets;
|
final long appPackets = entry.rxPackets + entry.txPackets;
|
||||||
activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs);
|
final long appRadioTime = (radioTime * appPackets) / totalPackets;
|
||||||
|
u.noteMobileRadioActiveTimeLocked(appRadioTime);
|
||||||
|
|
||||||
|
// Remove this app from the totals, so that we don't lose any time
|
||||||
|
// due to rounding.
|
||||||
|
radioTime -= appRadioTime;
|
||||||
|
totalPackets -= appPackets;
|
||||||
|
|
||||||
|
if (activityInfo != null) {
|
||||||
|
ControllerActivityCounterImpl activityCounter =
|
||||||
|
u.getOrCreateModemControllerActivityLocked();
|
||||||
|
if (totalRxPackets > 0 && entry.rxPackets > 0) {
|
||||||
|
final long rxMs = (entry.rxPackets * activityInfo.getRxTimeMillis())
|
||||||
|
/ totalRxPackets;
|
||||||
|
activityCounter.getRxTimeCounter().addCountLocked(rxMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalTxPackets > 0 && entry.txPackets > 0) {
|
||||||
|
for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
|
||||||
|
long txMs =
|
||||||
|
entry.txPackets * activityInfo.getTxTimeMillis()[lvl];
|
||||||
|
txMs /= totalTxPackets;
|
||||||
|
activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (radioTime > 0) {
|
||||||
|
// Whoops, there is some radio time we can't blame on an app!
|
||||||
|
mMobileRadioActiveUnknownTime.addCountLocked(radioTime);
|
||||||
|
mMobileRadioActiveUnknownCount.addCountLocked(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
mNetworkStatsPool.release(delta);
|
||||||
|
delta = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (radioTime > 0) {
|
if (activityInfo != null) {
|
||||||
// Whoops, there is some radio time we can't blame on an app!
|
mHasModemReporting = true;
|
||||||
mMobileRadioActiveUnknownTime.addCountLocked(radioTime);
|
mModemActivity.getIdleTimeCounter().addCountLocked(
|
||||||
mMobileRadioActiveUnknownCount.addCountLocked(1);
|
activityInfo.getIdleTimeMillis());
|
||||||
}
|
mModemActivity.getRxTimeCounter().addCountLocked(activityInfo.getRxTimeMillis());
|
||||||
}
|
for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
|
||||||
|
mModemActivity.getTxTimeCounters()[lvl]
|
||||||
|
.addCountLocked(activityInfo.getTxTimeMillis()[lvl]);
|
||||||
|
}
|
||||||
|
|
||||||
if (activityInfo != null) {
|
// POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
|
||||||
mHasModemReporting = true;
|
final double opVolt = mPowerProfile.getAveragePower(
|
||||||
mModemActivity.getIdleTimeCounter().addCountLocked(activityInfo.getIdleTimeMillis());
|
PowerProfile.POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
|
||||||
mModemActivity.getRxTimeCounter().addCountLocked(activityInfo.getRxTimeMillis());
|
if (opVolt != 0) {
|
||||||
for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
|
// We store the power drain as mAms.
|
||||||
mModemActivity.getTxTimeCounters()[lvl]
|
mModemActivity.getPowerCounter().addCountLocked(
|
||||||
.addCountLocked(activityInfo.getTxTimeMillis()[lvl]);
|
(long) (activityInfo.getEnergyUsed() / opVolt));
|
||||||
}
|
}
|
||||||
|
|
||||||
// POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
|
|
||||||
final double opVolt = mPowerProfile.getAveragePower(
|
|
||||||
PowerProfile.POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
|
|
||||||
if (opVolt != 0) {
|
|
||||||
// We store the power drain as mAms.
|
|
||||||
mModemActivity.getPowerCounter().addCountLocked(
|
|
||||||
(long) (activityInfo.getEnergyUsed() / opVolt));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
package com.android.server.am;
|
package com.android.server.am;
|
||||||
|
|
||||||
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
|
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
|
||||||
|
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO;
|
||||||
|
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI;
|
||||||
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.bluetooth.BluetoothActivityEnergyInfo;
|
import android.bluetooth.BluetoothActivityEnergyInfo;
|
||||||
@@ -231,10 +233,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
|
|
||||||
public void publish(Context context) {
|
public void publish(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
|
synchronized (mStats) {
|
||||||
com.android.internal.R.integer.config_radioScanningTimeout)
|
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
|
||||||
* 1000L);
|
com.android.internal.R.integer.config_radioScanningTimeout)
|
||||||
mStats.setPowerProfile(new PowerProfile(context));
|
* 1000L);
|
||||||
|
mStats.setPowerProfileLocked(new PowerProfile(context));
|
||||||
|
}
|
||||||
ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
|
ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,9 +249,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
public void initPowerManagement() {
|
public void initPowerManagement() {
|
||||||
final PowerManagerInternal powerMgr = LocalServices.getService(PowerManagerInternal.class);
|
final PowerManagerInternal powerMgr = LocalServices.getService(PowerManagerInternal.class);
|
||||||
powerMgr.registerLowPowerModeObserver(this);
|
powerMgr.registerLowPowerModeObserver(this);
|
||||||
mStats.notePowerSaveMode(
|
synchronized (mStats) {
|
||||||
powerMgr.getLowPowerState(ServiceType.BATTERY_STATS)
|
mStats.notePowerSaveModeLocked(
|
||||||
.batterySaverEnabled);
|
powerMgr.getLowPowerState(ServiceType.BATTERY_STATS)
|
||||||
|
.batterySaverEnabled);
|
||||||
|
}
|
||||||
(new WakeupReasonThread()).start();
|
(new WakeupReasonThread()).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +286,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
@Override
|
@Override
|
||||||
public void onLowPowerModeChanged(PowerSaveState result) {
|
public void onLowPowerModeChanged(PowerSaveState result) {
|
||||||
synchronized (mStats) {
|
synchronized (mStats) {
|
||||||
mStats.notePowerSaveMode(result.batterySaverEnabled);
|
mStats.notePowerSaveModeLocked(result.batterySaverEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,8 +612,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
|
|
||||||
public void noteMobileRadioPowerState(int powerState, long timestampNs, int uid) {
|
public void noteMobileRadioPowerState(int powerState, long timestampNs, int uid) {
|
||||||
enforceCallingPermission();
|
enforceCallingPermission();
|
||||||
|
boolean update;
|
||||||
synchronized (mStats) {
|
synchronized (mStats) {
|
||||||
mStats.noteMobileRadioPowerState(powerState, timestampNs, uid);
|
update = mStats.noteMobileRadioPowerStateLocked(powerState, timestampNs, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update) {
|
||||||
|
mHandler.scheduleSync("modem-data", UPDATE_RADIO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,7 +770,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM) ? "active"
|
powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM) ? "active"
|
||||||
: "inactive";
|
: "inactive";
|
||||||
mHandler.scheduleSync("wifi-data: " + type,
|
mHandler.scheduleSync("wifi-data: " + type,
|
||||||
BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI);
|
UPDATE_WIFI);
|
||||||
}
|
}
|
||||||
mStats.noteWifiRadioPowerState(powerState, tsNanos, uid);
|
mStats.noteWifiRadioPowerState(powerState, tsNanos, uid);
|
||||||
}
|
}
|
||||||
@@ -917,9 +928,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
@Override
|
@Override
|
||||||
public void noteNetworkStatsEnabled() {
|
public void noteNetworkStatsEnabled() {
|
||||||
enforceCallingPermission();
|
enforceCallingPermission();
|
||||||
synchronized (mStats) {
|
mHandler.scheduleSync("network-stats-enabled", UPDATE_RADIO | UPDATE_WIFI);
|
||||||
mStats.noteNetworkStatsEnabledLocked();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -985,9 +994,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (mStats) {
|
mStats.updateWifiState(info);
|
||||||
mStats.updateWifiStateLocked(info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1012,9 +1019,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (mStats) {
|
mStats.updateMobileRadioState(info);
|
||||||
mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime(), info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOnBattery() {
|
public boolean isOnBattery() {
|
||||||
@@ -1489,7 +1494,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
|
if ((updateFlags & UPDATE_WIFI) != 0) {
|
||||||
if (mWifiManager == null) {
|
if (mWifiManager == null) {
|
||||||
mWifiManager = IWifiManager.Stub.asInterface(
|
mWifiManager = IWifiManager.Stub.asInterface(
|
||||||
ServiceManager.getService(Context.WIFI_SERVICE));
|
ServiceManager.getService(Context.WIFI_SERVICE));
|
||||||
@@ -1558,14 +1563,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
mStats.updateKernelWakelocksLocked();
|
mStats.updateKernelWakelocksLocked();
|
||||||
mStats.updateKernelMemoryBandwidthLocked();
|
mStats.updateKernelMemoryBandwidthLocked();
|
||||||
|
|
||||||
if (wifiInfo != null) {
|
|
||||||
if (wifiInfo.isValid()) {
|
|
||||||
mStats.updateWifiStateLocked(extractDelta(wifiInfo));
|
|
||||||
} else {
|
|
||||||
Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bluetoothInfo != null) {
|
if (bluetoothInfo != null) {
|
||||||
if (bluetoothInfo.isValid()) {
|
if (bluetoothInfo.isValid()) {
|
||||||
mStats.updateBluetoothStateLocked(bluetoothInfo);
|
mStats.updateBluetoothStateLocked(bluetoothInfo);
|
||||||
@@ -1573,14 +1570,21 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
|||||||
Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
|
Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (modemInfo != null) {
|
if (wifiInfo != null) {
|
||||||
if (modemInfo.isValid()) {
|
if (wifiInfo.isValid()) {
|
||||||
mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime(),
|
mStats.updateWifiState(extractDelta(wifiInfo));
|
||||||
modemInfo);
|
} else {
|
||||||
} else {
|
Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
|
||||||
Slog.e(TAG, "modem info is invalid: " + modemInfo);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (modemInfo != null) {
|
||||||
|
if (modemInfo.isValid()) {
|
||||||
|
mStats.updateMobileRadioState(modemInfo);
|
||||||
|
} else {
|
||||||
|
Slog.e(TAG, "modem info is invalid: " + modemInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user