Commit 7f117b99 authored by Sebastien Marchand's avatar Sebastien Marchand Committed by Chromium LUCI CQ

[battery]: Track the ukm::SourceID that has been visible the longest

Bug: 1153193
Change-Id: I09fcbbbe6335dfbc420d551957399a2999e3b97a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2624768
Commit-Queue: Sébastien Marchand <sebmarchand@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#844785}
parent 46e54fb3
......@@ -6,13 +6,18 @@
#include <algorithm>
#include "base/containers/contains.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
UsageScenarioDataStore::UsageScenarioDataStore() = default;
UsageScenarioDataStore::~UsageScenarioDataStore() = default;
UsageScenarioDataStore::IntervalData::IntervalData() = default;
UsageScenarioDataStore::IntervalData::IntervalData(const IntervalData&) =
default;
UsageScenarioDataStoreImpl::UsageScenarioDataStoreImpl()
: start_time_(base::TimeTicks::Now()) {}
......@@ -131,6 +136,30 @@ void UsageScenarioDataStoreImpl::OnWebRTCConnectionClosed() {
}
}
void UsageScenarioDataStoreImpl::OnUkmSourceBecameVisible(
const ukm::SourceId& source,
const url::Origin& origin) {
DCHECK_NE(ukm::kInvalidSourceId, source);
auto& origin_map_iter = origin_info_map_[origin];
auto& source_id_iter = origin_map_iter[source];
DCHECK(source_id_iter.visible_timestamp.is_null());
source_id_iter.visible_timestamp = base::TimeTicks::Now();
}
void UsageScenarioDataStoreImpl::OnUkmSourceBecameHidden(
const ukm::SourceId& source,
const url::Origin& origin) {
DCHECK_NE(ukm::kInvalidSourceId, source);
auto& origin_map_iter = origin_info_map_[origin];
auto& source_id_iter = origin_map_iter[source];
DCHECK(!source_id_iter.visible_timestamp.is_null());
source_id_iter.cumulative_visible_time +=
base::TimeTicks::Now() - source_id_iter.visible_timestamp;
source_id_iter.visible_timestamp = base::TimeTicks();
}
void UsageScenarioDataStoreImpl::FinalizeIntervalData(base::TimeTicks now) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Update feature usage durations in |interval_data_|.
......@@ -144,4 +173,74 @@ void UsageScenarioDataStoreImpl::FinalizeIntervalData(base::TimeTicks now) {
interval_data_.time_with_open_webrtc_connection +=
now - has_opened_webrtc_connection_since_;
}
base::TimeDelta origin_visible_for_longest_time_duration;
// Finalize the interval data and find the origin that has been visible for
// the longest period of time.
for (auto origin_iter = origin_info_map_.begin();
origin_iter != origin_info_map_.end();) {
// Compute the total visible time for this origin. This can exceed the
// interval length if multiple tabs with the same origin are visible at the
// same time.
base::TimeDelta origin_visible_duration;
base::TimeDelta longest_visible_sourceid_duration;
ukm::SourceId longest_visible_sourceid = ukm::kInvalidSourceId;
for (auto iter = origin_iter->second.begin();
iter != origin_iter->second.end();) {
// If this SourceID is still visible then its cumulative time has to be
// updated.
if (!iter->second.visible_timestamp.is_null()) {
DCHECK(!iter->second.visible_timestamp.is_null());
iter->second.cumulative_visible_time +=
now - iter->second.visible_timestamp;
iter->second.visible_timestamp = now;
}
// Track the SourceID that has been visible the longest.
if (iter->second.cumulative_visible_time >
longest_visible_sourceid_duration) {
longest_visible_sourceid = iter->first;
longest_visible_sourceid_duration =
iter->second.cumulative_visible_time;
}
origin_visible_duration += iter->second.cumulative_visible_time;
// Remove the non visible source IDs from the map, they're not needed
// anymore
if (!iter->second.visible_timestamp.is_null()) {
// Reset the cumulative timestamp counter as the data has been consumed.
iter->second.cumulative_visible_time = base::TimeDelta();
++iter;
} else {
iter = origin_iter->second.erase(iter);
}
}
bool update_interval_data =
origin_visible_duration > origin_visible_for_longest_time_duration;
// In case of equality check if there's one sourceID that has been visible
// for a longer time than the currently recorded one.
if (origin_visible_duration == origin_visible_for_longest_time_duration) {
if (longest_visible_sourceid_duration >
interval_data_.source_id_for_longest_visible_origin_duration) {
update_interval_data = true;
}
}
if (update_interval_data) {
origin_visible_for_longest_time_duration = origin_visible_duration;
interval_data_.source_id_for_longest_visible_origin_duration =
longest_visible_sourceid_duration;
interval_data_.source_id_for_longest_visible_origin =
longest_visible_sourceid;
}
// Remove the origins that contain no visible SourceIDs.
if (origin_iter->second.empty()) {
origin_iter = origin_info_map_.erase(origin_iter);
} else {
++origin_iter;
}
}
}
......@@ -5,9 +5,12 @@
#ifndef CHROME_BROWSER_METRICS_USAGE_SCENARIO_USAGE_SCENARIO_DATA_STORE_H_
#define CHROME_BROWSER_METRICS_USAGE_SCENARIO_USAGE_SCENARIO_DATA_STORE_H_
#include "base/containers/flat_map.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "url/origin.h"
// Stores the data necessary to analyze the usage pattern during a given
// interval of time. There are 2 types of data tracked by this class:
......@@ -31,6 +34,7 @@ class UsageScenarioDataStore {
// Used to store data between 2 calls to ResetIntervalData.
struct IntervalData {
IntervalData();
IntervalData(const IntervalData& rhs);
// The uptime at the end of the interval.
base::TimeDelta uptime_at_interval_end;
......@@ -48,6 +52,23 @@ class UsageScenarioDataStore {
base::TimeDelta time_playing_video_full_screen_single_monitor;
// The time spent with at least one opened WebRTC connection.
base::TimeDelta time_with_open_webrtc_connection;
// The SourceID that has been visible for the longest period of time for the
// origin that has been visible for the longest period of time during the
// interval. E.g.:
// - SourceID 1 and 2 are for the same origin and are visible respectively
// for 2 and 3 seconds each during the interval.
// - SourceID 3 is for a different origin and is visible for 4 second.
// - This will report Source ID 2 with a duration of 3 seconds.
//
// In case of equality for an interval a SourceID will be randomly picked.
// In case of equality between origins this will report the data for the
// origin that contains the sourceID that has been visible the longest (or
// a random one in case of equality).
ukm::SourceId source_id_for_longest_visible_origin = ukm::kInvalidSourceId;
// The visibility time for |source_id_for_longest_visible_origin|.
base::TimeDelta source_id_for_longest_visible_origin_duration;
};
// Reset the interval data with the current state information and returns the
......@@ -90,11 +111,29 @@ class UsageScenarioDataStoreImpl : public UsageScenarioDataStore {
void OnWebRTCConnectionOpened();
void OnWebRTCConnectionClosed();
void OnUkmSourceBecameVisible(const ukm::SourceId& source,
const url::Origin& origin);
void OnUkmSourceBecameHidden(const ukm::SourceId& source,
const url::Origin& origin);
const IntervalData& GetIntervalDataForTesting() { return interval_data_; }
private:
// Information about a ukm::SourceId that has been visible during an interval
// of time.
struct SourceIdData {
// The timestamp when the SourceID became visible, null if the sourceID
// isn't visible.
base::TimeTicks visible_timestamp;
// The total visible time during the interval.
base::TimeDelta cumulative_visible_time;
};
using OriginData = base::flat_map<ukm::SourceId, SourceIdData>;
using OriginInfoMap = base::flat_map<url::Origin, OriginData>;
// Finalize the interval data based on the data contained in
// |interval_details_|.
// |interval_details_| and |origin_info_map_| and remove the SourceIdData that
// don't need to be tracked anymore.
void FinalizeIntervalData(base::TimeTicks now);
// The current tab count.
......@@ -119,6 +158,9 @@ class UsageScenarioDataStoreImpl : public UsageScenarioDataStore {
// The application start time.
const base::TimeTicks start_time_;
// Information about the origins that have been visible during the interval.
OriginInfoMap origin_info_map_;
IntervalData interval_data_;
SEQUENCE_CHECKER(sequence_checker_);
......
......@@ -9,7 +9,10 @@
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
constexpr base::TimeDelta kShortDelay = base::TimeDelta::FromSeconds(1);
......@@ -271,3 +274,143 @@ TEST_F(UsageScenarioDataStoreTest, WebRTCUsageInMultipleTabsMultipleInterval) {
data = ResetIntervalData();
EXPECT_EQ(base::TimeDelta(), data.time_with_open_webrtc_connection);
}
TEST_F(UsageScenarioDataStoreTest, VisibleSourceIDsDuringIntervalSingleURL) {
data_store()->OnTabAdded();
data_store()->OnTabAdded();
data_store()->OnTabAdded();
const url::Origin kOrigin = url::Origin::Create(GURL("https://foo.com"));
// Interval with no visible SourceID.
task_environment_.FastForwardBy(kShortDelay);
auto data = ResetIntervalData();
EXPECT_EQ(ukm::kInvalidSourceId, data.source_id_for_longest_visible_origin);
EXPECT_TRUE(data.source_id_for_longest_visible_origin_duration.is_zero());
// Interval with one SourceID visible the entire time.
const ukm::SourceId kSource1 = 42;
data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
task_environment_.FastForwardBy(kShortDelay);
data = ResetIntervalData();
EXPECT_EQ(kSource1, data.source_id_for_longest_visible_origin);
EXPECT_EQ(kShortDelay, data.source_id_for_longest_visible_origin_duration);
// Interval with 2 different visible SourceID, |kSource1| is visible the
// longest.
const ukm::SourceId kSource2 = 43;
task_environment_.FastForwardBy(2 * kShortDelay);
data_store()->OnUkmSourceBecameHidden(kSource1, kOrigin);
data_store()->OnUkmSourceBecameVisible(kSource2, kOrigin);
task_environment_.FastForwardBy(kShortDelay);
data = ResetIntervalData();
EXPECT_EQ(kSource1, data.source_id_for_longest_visible_origin);
EXPECT_EQ(2 * kShortDelay,
data.source_id_for_longest_visible_origin_duration);
// Interval with 3 different visible SourceID, |kSource1| and |kSource2| are
// visible for the same amount of time.
data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
const ukm::SourceId kSource3 = 44;
task_environment_.FastForwardBy(kShortDelay);
data_store()->OnUkmSourceBecameVisible(kSource3, kOrigin);
task_environment_.FastForwardBy(kShortDelay);
data = ResetIntervalData();
EXPECT_TRUE(data.source_id_for_longest_visible_origin == kSource1 ||
data.source_id_for_longest_visible_origin == kSource2);
EXPECT_EQ(2 * kShortDelay,
data.source_id_for_longest_visible_origin_duration);
// Interval with only |kSource3| being visible.
data_store()->OnUkmSourceBecameHidden(kSource1, kOrigin);
data_store()->OnUkmSourceBecameHidden(kSource2, kOrigin);
task_environment_.FastForwardBy(kShortDelay);
data = ResetIntervalData();
EXPECT_EQ(kSource3, data.source_id_for_longest_visible_origin);
EXPECT_EQ(kShortDelay, data.source_id_for_longest_visible_origin_duration);
// Back to no visible SourceID.
data_store()->OnUkmSourceBecameHidden(kSource3, kOrigin);
task_environment_.FastForwardBy(kShortDelay);
data = ResetIntervalData();
EXPECT_EQ(ukm::kInvalidSourceId, data.source_id_for_longest_visible_origin);
EXPECT_TRUE(data.source_id_for_longest_visible_origin_duration.is_zero());
}
TEST_F(UsageScenarioDataStoreTest, SourceIDVisibleMultipleTimesDuringInterval) {
data_store()->OnTabAdded();
const url::Origin kOrigin = url::Origin::Create(GURL("https://foo.com"));
const ukm::SourceId kSource1 = 42;
data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
task_environment_.FastForwardBy(kShortDelay);
data_store()->OnUkmSourceBecameHidden(kSource1, kOrigin);
task_environment_.FastForwardBy(kShortDelay);
data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
task_environment_.FastForwardBy(kShortDelay);
data_store()->OnUkmSourceBecameHidden(kSource1, kOrigin);
task_environment_.FastForwardBy(kShortDelay);
data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
auto data = ResetIntervalData();
EXPECT_EQ(kSource1, data.source_id_for_longest_visible_origin);
EXPECT_EQ(2 * kShortDelay,
data.source_id_for_longest_visible_origin_duration);
}
TEST_F(UsageScenarioDataStoreTest, VisibleSourceIDsMultipleOrigins) {
data_store()->OnTabAdded();
data_store()->OnTabAdded();
data_store()->OnTabAdded();
const url::Origin kOrigin1 = url::Origin::Create(GURL("https://foo.com"));
const url::Origin kOrigin2 = url::Origin::Create(GURL("https://bar.com"));
const url::Origin kOrigin3 = url::Origin::Create(GURL("https://baz.com"));
const ukm::SourceId kOrigin1SourceId1 = 42;
const ukm::SourceId kOrigin1SourceId2 = 43;
const ukm::SourceId kOrigin2SourceId = 44;
const ukm::SourceId kOrigin3SourceId = 45;
// |kOrigin1SourceId1| and |kOrigin1SourceId2| visible for 2 time units each,
// |kOrigin2SourceId| visible for 3 time units. The sourceID reported should
// be one associated with |kOrigin1| as the cumulative visibility time for its
// sourceIDs is the greatest.
data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId1, kOrigin1);
data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId2, kOrigin1);
data_store()->OnUkmSourceBecameVisible(kOrigin2SourceId, kOrigin2);
task_environment_.FastForwardBy(kShortDelay);
auto data = ResetIntervalData();
EXPECT_TRUE(data.source_id_for_longest_visible_origin == kOrigin1SourceId1 ||
data.source_id_for_longest_visible_origin == kOrigin1SourceId2);
EXPECT_EQ(kShortDelay, data.source_id_for_longest_visible_origin_duration);
// All the sourceIDs associated with |kOrigin2| and |kOrigin3| visible for the
// same time, which is greater than the cumulative visibility time for the
// sourceIDs associated with |kOrigin1|.
data_store()->OnUkmSourceBecameHidden(kOrigin1SourceId1, kOrigin1);
data_store()->OnUkmSourceBecameVisible(kOrigin3SourceId, kOrigin3);
task_environment_.FastForwardBy(kShortDelay);
data_store()->OnUkmSourceBecameHidden(kOrigin1SourceId2, kOrigin1);
task_environment_.FastForwardBy(kShortDelay);
data = ResetIntervalData();
EXPECT_TRUE(data.source_id_for_longest_visible_origin == kOrigin2SourceId ||
data.source_id_for_longest_visible_origin == kOrigin3SourceId);
EXPECT_EQ(2 * kShortDelay,
data.source_id_for_longest_visible_origin_duration);
// The sourceID associated with |kOrigin2| is visible for 5 time units, the
// cumulative time for the source ID associated with |kOrigin1| is also equal
// to 5 time units.
data_store()->OnUkmSourceBecameHidden(kOrigin3SourceId, kOrigin3);
data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId1, kOrigin1);
task_environment_.FastForwardBy(3 * kShortDelay);
data_store()->OnUkmSourceBecameHidden(kOrigin1SourceId1, kOrigin1);
data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId2, kOrigin1);
task_environment_.FastForwardBy(2 * kShortDelay);
data = ResetIntervalData();
EXPECT_TRUE(data.source_id_for_longest_visible_origin == kOrigin2SourceId);
EXPECT_EQ(5 * kShortDelay,
data.source_id_for_longest_visible_origin_duration);
}
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