Commit c590aaeb authored by Mikhail Khokhlov's avatar Mikhail Khokhlov Committed by Commit Bot

chrome/android: Record radio signal strength and traffic into UMA

Adds a UMA metric to chrome/android that reports signal strength for
Wifi or cellular connection (depending on which is active at the moment)
as well as a number of outgoing and incoming bytes over the last 30 secs.
This metric will be used in conjunction with battery discharge metrics
to estimate the relative importance of radio usage in battery drain.

Bug: b/167544787
Change-Id: I806076896408fd4ac32ca67d21cbb281bea64453
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2450049
Commit-Queue: Mikhail Khokhlov <khokhlov@google.com>
Reviewed-by: default avatarSami Kyöstilä <skyostil@chromium.org>
Reviewed-by: default avatarWeilun Shi <sweilun@chromium.org>
Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarNico Weber <thakis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815577}
parent 0504e8be
......@@ -1426,6 +1426,8 @@ component("base") {
"android/path_service_android.cc",
"android/path_utils.cc",
"android/path_utils.h",
"android/radio_utils.cc",
"android/radio_utils.h",
"android/reached_addresses_bitset.cc",
"android/reached_addresses_bitset.h",
"android/reached_code_profiler.cc",
......@@ -3487,6 +3489,7 @@ if (is_android) {
"android/java/src/org/chromium/base/PathService.java",
"android/java/src/org/chromium/base/PathUtils.java",
"android/java/src/org/chromium/base/PowerMonitor.java",
"android/java/src/org/chromium/base/RadioUtils.java",
"android/java/src/org/chromium/base/SysUtils.java",
"android/java/src/org/chromium/base/ThreadUtils.java",
"android/java/src/org/chromium/base/TimeUtils.java",
......@@ -3595,6 +3598,7 @@ if (is_android) {
"android/java/src/org/chromium/base/PiiElider.java",
"android/java/src/org/chromium/base/PowerMonitor.java",
"android/java/src/org/chromium/base/Promise.java",
"android/java/src/org/chromium/base/RadioUtils.java",
"android/java/src/org/chromium/base/SecureRandomInitializer.java",
"android/java/src/org/chromium/base/StreamUtil.java",
"android/java/src/org/chromium/base/StrictModeContext.java",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Build;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
/**
* Exposes radio related information about the current device.
*/
@JNINamespace("base::android")
public class RadioUtils {
private RadioUtils() {}
/**
* Return whether the current SDK supports necessary functions.
* @return True or false.
*/
@CalledByNative
private static boolean isSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
}
/**
* Return whether the device is currently connected to a wifi network.
* @return True or false.
*/
@CalledByNative
@TargetApi(Build.VERSION_CODES.P)
private static boolean isWifiConnected() {
assert isSupported();
ConnectivityManager connectivityManager =
(ConnectivityManager) ContextUtils.getApplicationContext().getSystemService(
Context.CONNECTIVITY_SERVICE);
Network network = connectivityManager.getActiveNetwork();
if (network == null) return false;
NetworkCapabilities networkCapabilities =
connectivityManager.getNetworkCapabilities(network);
if (networkCapabilities == null) return false;
return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
}
/**
* Return current cell signal level.
* @return Signal level from 0 (no signal) to 4 (good signal).
*/
@CalledByNative
@TargetApi(Build.VERSION_CODES.P)
private static int getCellSignalLevel() {
assert isSupported();
TelephonyManager telephonyManager =
(TelephonyManager) ContextUtils.getApplicationContext().getSystemService(
Context.TELEPHONY_SERVICE);
SignalStrength signalStrength = telephonyManager.getSignalStrength();
if (signalStrength == null) return -1;
return signalStrength.getLevel();
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/android/radio_utils.h"
#include "base/base_jni_headers/RadioUtils_jni.h"
namespace base {
namespace android {
bool RadioUtils::IsSupported() {
JNIEnv* env = AttachCurrentThread();
return Java_RadioUtils_isSupported(env);
}
bool RadioUtils::IsWifiConnected() {
JNIEnv* env = AttachCurrentThread();
return Java_RadioUtils_isWifiConnected(env);
}
Optional<RadioSignalLevel> RadioUtils::GetCellSignalLevel() {
JNIEnv* env = AttachCurrentThread();
int signal_level = Java_RadioUtils_getCellSignalLevel(env);
if (signal_level < 0) {
return nullopt;
} else {
return static_cast<RadioSignalLevel>(signal_level);
}
}
} // namespace android
} // namespace base
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_ANDROID_RADIO_UTILS_H_
#define BASE_ANDROID_RADIO_UTILS_H_
#include "base/android/jni_android.h"
#include "base/optional.h"
namespace base {
namespace android {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. Keep in sync with RadioSignalLevel
// in //tools/metrics/histograms/enums.xml.
enum class RadioSignalLevel {
kNoneOrUnknown = 0,
kPoor = 1,
kModerate = 2,
kGood = 3,
kGreat = 4,
kMaxValue = kGreat,
};
class BASE_EXPORT RadioUtils {
public:
static bool IsSupported();
static bool IsWifiConnected();
static Optional<RadioSignalLevel> GetCellSignalLevel();
};
} // namespace android
} // namespace base
#endif // BASE_ANDROID_RADIO_UTILS_H_
......@@ -4,13 +4,62 @@
#include "chrome/browser/android/battery/android_battery_metrics.h"
#include "base/android/radio_utils.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/power_monitor/power_monitor.h"
#include "net/android/network_library.h"
#include "net/android/traffic_stats.h"
namespace {
void Report30SecondRadioUsage(int64_t tx_bytes, int64_t rx_bytes) {
if (!base::android::RadioUtils::IsSupported())
return;
if (base::android::RadioUtils::IsWifiConnected()) {
base::Optional<int32_t> maybe_level = net::android::GetWifiSignalLevel();
if (!maybe_level.has_value())
return;
base::android::RadioSignalLevel wifi_level =
static_cast<base::android::RadioSignalLevel>(*maybe_level);
UMA_HISTOGRAM_ENUMERATION("Power.ForegroundRadio.SignalLevel.Wifi",
wifi_level);
// Traffic sent over network during the last 30 seconds in kibibytes.
UMA_HISTOGRAM_SCALED_ENUMERATION(
"Power.ForegroundRadio.SentKiB.Wifi.30Seconds", wifi_level, tx_bytes,
1024);
// Traffic received over network during the last 30 seconds in kibibytes.
UMA_HISTOGRAM_SCALED_ENUMERATION(
"Power.ForegroundRadio.ReceivedKiB.Wifi.30Seconds", wifi_level,
rx_bytes, 1024);
} else {
base::Optional<base::android::RadioSignalLevel> maybe_level =
base::android::RadioUtils::GetCellSignalLevel();
if (!maybe_level.has_value())
return;
base::android::RadioSignalLevel cell_level = *maybe_level;
UMA_HISTOGRAM_ENUMERATION("Power.ForegroundRadio.SignalLevel.Cell",
cell_level);
// Traffic sent over network during the last 30 seconds in kibibytes.
UMA_HISTOGRAM_SCALED_ENUMERATION(
"Power.ForegroundRadio.SentKiB.Cell.30Seconds", cell_level, tx_bytes,
1024);
// Traffic received over network during the last 30 seconds in kibibytes.
UMA_HISTOGRAM_SCALED_ENUMERATION(
"Power.ForegroundRadio.ReceivedKiB.Cell.30Seconds", cell_level,
rx_bytes, 1024);
}
}
void Report30SecondDrain(int capacity_consumed, bool is_exclusive_measurement) {
// Drain over the last 30 seconds in uAh. We assume a max current of 10A which
// translates to a little under 100mAh capacity drain over 30 seconds.
......@@ -63,7 +112,7 @@ void ReportAveragedDrain(int capacity_consumed,
} // namespace
// static
constexpr base::TimeDelta AndroidBatteryMetrics::kDrainMetricsInterval;
constexpr base::TimeDelta AndroidBatteryMetrics::kMetricsInterval;
AndroidBatteryMetrics::AndroidBatteryMetrics()
: app_state_listener_(base::android::ApplicationStatusListener::New(
......@@ -72,7 +121,7 @@ AndroidBatteryMetrics::AndroidBatteryMetrics()
app_state_(base::android::ApplicationStatusListener::GetState()),
on_battery_power_(base::PowerMonitor::IsOnBatteryPower()) {
base::PowerMonitor::AddObserver(this);
UpdateDrainMetricsEnabled();
UpdateMetricsEnabled();
}
AndroidBatteryMetrics::~AndroidBatteryMetrics() {
......@@ -83,16 +132,16 @@ void AndroidBatteryMetrics::OnAppStateChanged(
base::android::ApplicationState state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
app_state_ = state;
UpdateDrainMetricsEnabled();
UpdateMetricsEnabled();
}
void AndroidBatteryMetrics::OnPowerStateChange(bool on_battery_power) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
on_battery_power_ = on_battery_power;
UpdateDrainMetricsEnabled();
UpdateMetricsEnabled();
}
void AndroidBatteryMetrics::UpdateDrainMetricsEnabled() {
void AndroidBatteryMetrics::UpdateMetricsEnabled() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We want to attribute battery drain to Chrome while it is in the foreground.
......@@ -102,23 +151,44 @@ void AndroidBatteryMetrics::UpdateDrainMetricsEnabled() {
app_state_ == base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES &&
on_battery_power_;
if (should_be_enabled && !drain_metrics_timer_.IsRunning()) {
if (should_be_enabled && !metrics_timer_.IsRunning()) {
// Capture first capacity measurement and enable the repeating timer.
last_remaining_capacity_uah_ =
base::PowerMonitor::GetRemainingBatteryCapacity();
if (!net::android::traffic_stats::GetTotalTxBytes(&last_tx_bytes_))
last_tx_bytes_ = -1;
if (!net::android::traffic_stats::GetTotalRxBytes(&last_rx_bytes_))
last_rx_bytes_ = -1;
skipped_timers_ = 0;
observed_capacity_drops_ = 0;
drain_metrics_timer_.Start(FROM_HERE, kDrainMetricsInterval, this,
&AndroidBatteryMetrics::CaptureAndReportDrain);
} else if (!should_be_enabled && drain_metrics_timer_.IsRunning()) {
metrics_timer_.Start(FROM_HERE, kMetricsInterval, this,
&AndroidBatteryMetrics::CaptureAndReportMetrics);
} else if (!should_be_enabled && metrics_timer_.IsRunning()) {
// Capture one last measurement before disabling the timer.
CaptureAndReportDrain();
drain_metrics_timer_.Stop();
CaptureAndReportMetrics();
metrics_timer_.Stop();
}
}
void AndroidBatteryMetrics::CaptureAndReportDrain() {
void AndroidBatteryMetrics::UpdateAndReportRadio() {
int64_t tx_bytes;
int64_t rx_bytes;
if (!net::android::traffic_stats::GetTotalTxBytes(&tx_bytes))
tx_bytes = -1;
if (!net::android::traffic_stats::GetTotalRxBytes(&rx_bytes))
rx_bytes = -1;
if (last_tx_bytes_ > 0 && tx_bytes > 0 && last_rx_bytes_ > 0 &&
rx_bytes > 0) {
Report30SecondRadioUsage(tx_bytes - last_tx_bytes_,
rx_bytes - last_rx_bytes_);
}
last_tx_bytes_ = tx_bytes;
last_rx_bytes_ = rx_bytes;
}
void AndroidBatteryMetrics::CaptureAndReportMetrics() {
int remaining_capacity_uah =
base::PowerMonitor::GetRemainingBatteryCapacity();
......@@ -129,6 +199,8 @@ void AndroidBatteryMetrics::CaptureAndReportDrain() {
// here to avoid overreporting in case of fluctuating values.
skipped_timers_++;
Report30SecondDrain(0, IsMeasuringDrainExclusively());
UpdateAndReportRadio();
return;
}
observed_capacity_drops_++;
......@@ -136,6 +208,7 @@ void AndroidBatteryMetrics::CaptureAndReportDrain() {
// Report the consumed capacity delta over the last 30 seconds.
int capacity_consumed = last_remaining_capacity_uah_ - remaining_capacity_uah;
Report30SecondDrain(capacity_consumed, IsMeasuringDrainExclusively());
UpdateAndReportRadio();
// Also record drain over 30 second intervals, but averaged since the last
// time we recorded an increase (or started recording samples). Because the
......
......@@ -24,8 +24,9 @@ class AndroidBatteryMetrics : public base::PowerObserver {
// Called by base::android::ApplicationStatusListener.
void OnAppStateChanged(base::android::ApplicationState);
void UpdateDrainMetricsEnabled();
void CaptureAndReportDrain();
void UpdateMetricsEnabled();
void CaptureAndReportMetrics();
void UpdateAndReportRadio();
// Whether or not we've seen at least two consecutive capacity drops while
// Chrome was the foreground app. Battery drain reported prior to this could
......@@ -34,14 +35,16 @@ class AndroidBatteryMetrics : public base::PowerObserver {
// Battery drain is captured and reported periodically in this interval while
// the device is on battery power and Chrome is the foreground activity.
static constexpr base::TimeDelta kDrainMetricsInterval =
static constexpr base::TimeDelta kMetricsInterval =
base::TimeDelta::FromSeconds(30);
std::unique_ptr<base::android::ApplicationStatusListener> app_state_listener_;
base::android::ApplicationState app_state_;
bool on_battery_power_;
int last_remaining_capacity_uah_ = 0;
base::RepeatingTimer drain_metrics_timer_;
int64_t last_tx_bytes_ = -1;
int64_t last_rx_bytes_ = -1;
base::RepeatingTimer metrics_timer_;
int skipped_timers_ = 0;
// Number of consecutive charge drops seen while the app has been in the
......
......@@ -61250,6 +61250,14 @@ https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
<int value="6" label="In use"/>
</enum>
<enum name="RadioSignalLevel">
<int value="0" label="None or Unknown"/>
<int value="1" label="Poor"/>
<int value="2" label="Moderate"/>
<int value="3" label="Good"/>
<int value="4" label="Great"/>
</enum>
<enum name="RankerModelStatus">
<int value="0" label="OK"/>
<int value="1" label="Download Throttled"/>
......@@ -498,6 +498,52 @@ reviews. Googlers can read more about this at go/gwsq-gerrit.
</summary>
</histogram>
<histogram name="Power.ForegroundRadio.SignalLevel.{NetworkType}"
enum="RadioSignalLevel" expires_after="2021-07-03">
<owner>eseckler@chromium.org</owner>
<owner>khokhlov@chromium.org</owner>
<owner>skyostil@chromium.org</owner>
<summary>
Periodically samples the radio (cellular/wifi) signal strength level while
Chrome is the foreground app and the device is on battery power. Sampled
every 30 seconds and when Chrome is backgrounded or the device connects to a
charger.
Only supported on Android.
</summary>
<token key="NetworkType">
<variant name="Cell"/>
<variant name="Wifi"/>
</token>
</histogram>
<histogram name="Power.ForegroundRadio.{Direction}KiB.{NetworkType}.30Seconds"
enum="RadioSignalLevel" expires_after="2021-07-03">
<owner>eseckler@chromium.org</owner>
<owner>khokhlov@chromium.org</owner>
<owner>skyostil@chromium.org</owner>
<summary>
Periodically samples the number of kibibytes received over network while
Chrome is the foreground app and the device is on battery power. Sampled
every 30 seconds and when Chrome is backgrounded or the device connects to a
charger. Values are distributed across buckets according to the radio signal
quality.
Caveat: signal quality is measured at the end of 30-sec period, so it might
not reflect the quality during the entire period.
Only supported on Android.
</summary>
<token key="Direction">
<variant name="Received"/>
<variant name="Sent"/>
</token>
<token key="NetworkType">
<variant name="Cell"/>
<variant name="Wifi"/>
</token>
</histogram>
<histogram name="Power.IdleScreenDimCountDaily" units="count"
expires_after="M100">
<owner>tbroch@chromium.org</owner>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment