[go: nahoru, domu]

blob: afbadf21634d289f66075e338b2ead330c74169a [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/ntp/metrics/feed_session_recorder.h"
#import "base/apple/foundation_util.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/time/time.h"
#import "ios/chrome/browser/ui/ntp/metrics/feed_session_recorder+testing.h"
namespace {
// Histogram name to measure the duration of a feed session. Note that
// `kSessionTimeout` directly affects how this is measured.
const char kDiscoverFeedSessionDuration[] =
"ContentSuggestions.Feed.SessionDuration";
// Histogram name to measure the time between user interactions (including
// scrolling) in the feed. This can inform how we set `kSessionTimeout`.
const char kDiscoverFeedTimeBetweenInteractions[] =
"ContentSuggestions.Feed.TimeBetweenInteractions";
// Histogram name to measure the time between user sessions in the feed. Note
// that `kSessionTimeout` directly affects how this is measured.
const char kDiscoverFeedTimeBetweenSessions[] =
"ContentSuggestions.Feed.TimeBetweenSessions";
// Key used to store the date (and time) of the user's previous interaction
// (including scrolling) with the feed. Can be nil if this is a fresh install or
// if there was an issue writing to NSUserDefaults.
NSString* const kFeedPreviousInteractionDateKey =
@"DiscoverFeedPreviousInteractionDate";
// Two interactions (including scrolling) are considered to be within the same
// session if they are at most `kSessionTimeout` apart from each other.
// Otherwise, the later interaction (including scrolling) is considered a new
// session.
constexpr base::TimeDelta kSessionTimeout = base::Minutes(5);
} // namespace
@interface FeedSessionRecorder ()
// The date (and time) when the first interaction (including scrolling) was
// recorded for the current session.
@property(nonatomic, assign) base::Time sessionStartDate;
// The date (and time) of the previous interaction (including scrolling).
@property(nonatomic, assign) base::Time previousInteractionDate;
// Session duration.
@property(nonatomic, assign) base::TimeDelta sessionDuration;
// Time between sessions.
@property(nonatomic, assign) base::TimeDelta timeBetweenSessions;
// Time between interactions.
@property(nonatomic, assign) base::TimeDelta timeBetweenInteractions;
@end
@implementation FeedSessionRecorder
@synthesize previousInteractionDate = _previousInteractionDate;
#pragma mark - Properties
- (base::Time)previousInteractionDate {
if (_previousInteractionDate.is_null()) {
// Reads from disk if there was a cold start and the property is null.
// Disk value can be nil if this is a fresh install or if there was an issue
// writing to NSUserDefaults.
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSDate* previousInteractionDate = base::apple::ObjCCast<NSDate>(
[defaults objectForKey:kFeedPreviousInteractionDateKey]);
if (previousInteractionDate) {
_previousInteractionDate =
base::Time::FromNSDate(previousInteractionDate);
}
}
return _previousInteractionDate;
}
- (void)setPreviousInteractionDate:(base::Time)previousInteractionDate {
// Sets both in-memory and disk memory at the same time.
_previousInteractionDate = previousInteractionDate;
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:_previousInteractionDate.ToNSDate()
forKey:kFeedPreviousInteractionDateKey];
[defaults synchronize];
}
#pragma mark - Public
- (void)recordUserInteractionOrScrolling {
[self recordUserInteractionOrScrollingAtDate:base::Time::Now()];
}
#pragma mark - Helpers
- (void)recordUserInteractionOrScrollingAtDate:(base::Time)interactionDate {
if (self.previousInteractionDate.is_null()) {
// This probably means this is the first feed interaction since a new
// install. We cannot record session time metrics without this value so
// start a new session.
self.sessionStartDate = interactionDate;
} else {
[self
recordTimeBetweenInteractionsWithStartDate:self.previousInteractionDate
endDate:interactionDate];
// Determine if this interaction should start a new session.
const base::TimeDelta previousInteractionAge =
interactionDate - self.previousInteractionDate;
if (previousInteractionAge > kSessionTimeout) {
// Close out current session.
[self recordSessionDurationWithStartDate:self.sessionStartDate
endDate:self.previousInteractionDate];
[self recordTimeBetweenSessionsWithStartDate:self.previousInteractionDate
endDate:interactionDate];
// Start a new session.
self.sessionStartDate = interactionDate;
}
}
// Reset the previous active time for session measurement.
self.previousInteractionDate = interactionDate;
}
// Records the session duration histogram. This tests our assumption that
// session durations are only a few seconds long. Our goal is to increase this
// over time. The histogram max value is 5 minutes. Session durations of 0
// indicate that there was only a single interaction or scrolling.
- (void)recordSessionDurationWithStartDate:(base::Time)startDate
endDate:(base::Time)endDate {
self.sessionDuration = endDate - startDate;
UMA_HISTOGRAM_CUSTOM_TIMES(kDiscoverFeedSessionDuration, self.sessionDuration,
/*min=*/base::Milliseconds(0),
/*max=*/base::Minutes(5), /*bucket_count=*/100);
}
// Records the time between sessions histogram (i.e., how long it takes for
// users to return to a feed session). This can help us gauge how useful
// prefetching can be. The time between sessions is by definition longer than 5
// minutes. This histogram only captures those users who return to the feed
// within a day. The histogram max is 24 hours.
- (void)recordTimeBetweenSessionsWithStartDate:(base::Time)startDate
endDate:(base::Time)endDate {
self.timeBetweenSessions = endDate - startDate;
UMA_HISTOGRAM_CUSTOM_TIMES(kDiscoverFeedTimeBetweenSessions,
self.timeBetweenSessions,
/*min=*/base::Minutes(5),
/*max=*/base::Hours(24), /*bucket_count=*/50);
}
// Records the time between interactions (including scrolling) histogram. We
// expect interactions within a single session to be within a few seconds to a
// minute or two of each other. We want to test if 5 minutes is the right
// session timeout value. The histogram max value is 10 minutes.
- (void)recordTimeBetweenInteractionsWithStartDate:(base::Time)startDate
endDate:(base::Time)endDate {
self.timeBetweenInteractions = endDate - startDate;
UMA_HISTOGRAM_CUSTOM_TIMES(kDiscoverFeedTimeBetweenInteractions,
self.timeBetweenInteractions,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*bucket_count=*/50);
}
@end