File: uma_session_stats.cc

package info (click to toggle)
chromium 135.0.7049.95-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 5,959,392 kB
  • sloc: cpp: 34,198,526; ansic: 7,100,035; javascript: 3,985,800; python: 1,395,489; asm: 896,754; xml: 722,891; pascal: 180,504; sh: 94,909; perl: 88,388; objc: 79,739; sql: 53,020; cs: 41,358; fortran: 24,137; makefile: 22,501; php: 13,699; tcl: 10,142; yacc: 8,822; ruby: 7,350; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; awk: 197; sed: 36
file content (395 lines) | stat: -rw-r--r-- 15,022 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/android/metrics/uma_session_stats.h"

#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/no_destructor.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/android/metrics/android_session_durations_service.h"
#include "chrome/browser/android/metrics/android_session_durations_service_factory.h"
#include "chrome/browser/android/preferences/shared_preferences_migrator_android.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/installer/util/google_update_settings.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics_services_manager/metrics_services_manager.h"
#include "components/prefs/pref_service.h"
#include "components/ukm/ukm_service.h"
#include "components/variations/synthetic_trial_registry.h"
#include "content/public/browser/browser_thread.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/android/chrome_jni_headers/UmaSessionStats_jni.h"

using base::android::ConvertJavaStringToUTF8;
using base::android::JavaParamRef;
using base::UserMetricsAction;

namespace {
// Used to keep the state of whether we should consider metric consent enabled.
// This is used/read only within the ChromeMetricsServiceAccessor methods.
bool g_metrics_consent_for_testing = false;
}  // namespace

namespace {
// Counter for the number of times onPreCreate and onResume were called between
// foreground sessions that reach native code. The code PXRY means:
// * onPreCreate was called X times
// * onResume was called Y times
// * the counters are capped at 3, so that value means "3 or more".
enum class ChromeActivityCounter : int32_t {
  P0R0 = 0,
  P0R1 = 1,
  P0R2 = 2,
  P0R3 = 3,
  P1R0 = 4,
  P1R1 = 5,
  P1R2 = 6,
  P1R3 = 7,
  P2R0 = 8,
  P2R1 = 9,
  P2R2 = 10,
  P2R3 = 11,
  P3R0 = 12,
  P3R1 = 13,
  P3R2 = 14,
  P3R3 = 15,
  kMaxValue = 15,
};
}  // namespace

void UmaSessionStats::UmaResumeSession(JNIEnv* env,
                                       const JavaParamRef<jobject>& obj) {
  DCHECK(g_browser_process);
  if (++active_session_count_ == 1) {
    const bool had_background_session =
        session_time_tracker_.BeginForegroundSession();

    // Tell the metrics services that the application resumes.
    metrics::MetricsService* metrics = g_browser_process->metrics_service();
    if (metrics) {
      // Forcing a new log allows foreground and background metrics can be
      // separated in analysis.
      const bool force_new_log = base::FeatureList::IsEnabled(
                                     chrome::android::kUmaBackgroundSessions) &&
                                 had_background_session;

      metrics->OnAppEnterForeground(force_new_log);
    }
    // Report background session time if it wasn't already reported by
    // OnAppEnterForeground() -> ProvideCurrentSessionData().
    session_time_tracker_.ReportBackgroundSessionTime();

    ukm::UkmService* ukm_service =
        g_browser_process->GetMetricsServicesManager()->GetUkmService();
    if (ukm_service)
      ukm_service->OnAppEnterForeground();

    AndroidSessionDurationsServiceFactory::OnAppEnterForeground(
        session_time_tracker_.session_start_time());
  }
}

void UmaSessionStats::UmaEndSession(JNIEnv* env,
                                    const JavaParamRef<jobject>& obj) {
  --active_session_count_;
  DCHECK_GE(active_session_count_, 0);

  if (active_session_count_ == 0) {
    const base::TimeDelta duration =
        session_time_tracker_.EndForegroundSession();

    DCHECK(g_browser_process);
    // Tell the metrics services they were cleanly shutdown.
    metrics::MetricsService* metrics = g_browser_process->metrics_service();
    if (metrics) {
      const bool keep_reporting =
          base::FeatureList::IsEnabled(chrome::android::kUmaBackgroundSessions);
      metrics->OnAppEnterBackground(keep_reporting);
    }
    ukm::UkmService* ukm_service =
        g_browser_process->GetMetricsServicesManager()->GetUkmService();
    if (ukm_service)
      ukm_service->OnAppEnterBackground();

    AndroidSessionDurationsServiceFactory::OnAppEnterBackground(duration);

    // Note: Keep the line below after |metrics->OnAppEnterBackground()|.
    // Otherwise, |ProvideCurrentSessionData()| may report a small timeslice of
    // background session time toward the previous log.
    session_time_tracker_.BeginBackgroundSession();
  }
}

void UmaSessionStats::ProvideCurrentSessionData() {
  base::UmaHistogramBoolean("Session.IsActive", active_session_count_ != 0);

  // We record Session.Background.TotalDuration here to ensure each UMA log
  // containing a background session contains this histogram.
  session_time_tracker_.AccumulateBackgroundSessionTime();
  session_time_tracker_.ReportBackgroundSessionTime();
}

// static
UmaSessionStats* UmaSessionStats::GetInstance() {
  static base::NoDestructor<UmaSessionStats> instance;
  return instance.get();
}

// static
bool UmaSessionStats::HasVisibleActivity() {
  return Java_UmaSessionStats_hasVisibleActivity(
      base::android::AttachCurrentThread());
}

// Called on startup. If there is an activity, do nothing because a foreground
// session will be created naturally. Otherwise, begin recording a background
// session.
// static
void UmaSessionStats::OnStartup() {
  if (!UmaSessionStats::HasVisibleActivity()) {
    GetInstance()->session_time_tracker_.BeginBackgroundSession();
  }
}

// static
void UmaSessionStats::RegisterSyntheticFieldTrial(
    const std::string& trial_name,
    const std::string& group_name,
    variations::SyntheticTrialAnnotationMode annotation_mode) {
  ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
      trial_name, group_name, annotation_mode);
}

// static
bool UmaSessionStats::IsBackgroundSessionStartForTesting() {
  return !GetInstance()
              ->session_time_tracker_.background_session_start_time()
              .is_null();
}

void UmaSessionStats::EmitAndResetCounters() {
  std::optional<int> on_postcreate_counter =
      android::shared_preferences::GetAndClearInt(
          "Chrome.UMA.OnPostCreateCounter2");
  std::optional<int> on_resume_counter =
      android::shared_preferences::GetAndClearInt(
          "Chrome.UMA.OnResumeCounter2");
  int on_create_count = std::min(on_postcreate_counter.value_or(0), 3);
  int on_resume_count = std::min(on_resume_counter.value_or(0), 3);
  ChromeActivityCounter count_code =
      static_cast<ChromeActivityCounter>(4 * on_create_count + on_resume_count);
  UMA_HISTOGRAM_ENUMERATION("UMA.AndroidPreNative.ChromeActivityCounter2",
                            count_code);
}

void UmaSessionStats::SessionTimeTracker::AccumulateBackgroundSessionTime() {
  // No time spent in background since the last call to
  // |AccumulateBackgroundSessionTime()|.
  if (background_session_start_time_.is_null())
    return;

  base::TimeTicks now = base::TimeTicks::Now();
  base::TimeDelta duration = now - background_session_start_time_;
  background_session_accumulated_time_ += duration;

  background_session_start_time_ = now;
}

void UmaSessionStats::SessionTimeTracker::ReportBackgroundSessionTime() {
  if (background_session_accumulated_time_.is_zero())
    return;

  // This histogram is used in analysis to determine if an uploaded log
  // represents background activity. For this reason, this histogram may be
  // recorded more than once per 'background session'.
  UMA_HISTOGRAM_CUSTOM_TIMES("Session.Background.TotalDuration",
                             background_session_accumulated_time_,
                             base::Milliseconds(1), base::Hours(24), 50);
  background_session_accumulated_time_ = base::TimeDelta();
}

bool UmaSessionStats::SessionTimeTracker::BeginForegroundSession() {
  // Emit onPostCreate & onResume counters. This is done early in the session
  // to ensure that these are captured even if the session is not ended
  // cleanly.
  UmaSessionStats::EmitAndResetCounters();
  AccumulateBackgroundSessionTime();
  background_session_start_time_ = {};
  session_start_time_ = base::TimeTicks::Now();
  return !background_session_accumulated_time_.is_zero();
}

base::TimeDelta UmaSessionStats::SessionTimeTracker::EndForegroundSession() {
  base::TimeDelta duration = base::TimeTicks::Now() - session_start_time_;

  // Note: This metric is recorded separately on desktop in
  // DesktopSessionDurationTracker::EndSession.
  UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration", duration);
  UMA_HISTOGRAM_CUSTOM_TIMES("Session.TotalDurationMax1Day", duration,
                             base::Milliseconds(1), base::Hours(24), 50);
  return duration;
}

void UmaSessionStats::SessionTimeTracker::BeginBackgroundSession() {
  background_session_start_time_ = base::TimeTicks::Now();
}

// Updates metrics reporting state managed by native code. This should only be
// called when consent is changing, and UpdateMetricsServiceState() should be
// called immediately after for metrics services to be started or stopped as
// needed. This is enforced by UmaSessionStats.changeMetricsReportingConsent on
// the Java side.
static void JNI_UmaSessionStats_ChangeMetricsReportingConsent(
    JNIEnv*,
    jboolean consent,
    jint called_from) {
  UpdateMetricsPrefsOnPermissionChange(
      consent, static_cast<ChangeMetricsReportingStateCalledFrom>(called_from));

  // This function ensures a consent file in the data directory is either
  // created, or deleted, depending on consent. Starting up metrics services
  // will ensure that the consent file contains the ClientID. The ID is passed
  // to the renderer for crash reporting when things go wrong.
  GoogleUpdateSettings::CollectStatsConsentTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(GoogleUpdateSettings::SetCollectStatsConsent),
          consent));
}

// Initialize the local consent bool variable to false. Used only for testing.
static void JNI_UmaSessionStats_InitMetricsAndCrashReportingForTesting(
    JNIEnv*) {
  DCHECK(g_browser_process);

  g_metrics_consent_for_testing = false;
  ChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
      &g_metrics_consent_for_testing);
}

// Clears the boolean consent pointer for ChromeMetricsServiceAccessor to
// original setting. Used only for testing.
static void JNI_UmaSessionStats_UnsetMetricsAndCrashReportingForTesting(
    JNIEnv*) {
  DCHECK(g_browser_process);

  g_metrics_consent_for_testing = false;
  ChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(nullptr);
}

// Updates the metrics consent bit to |consent|. This is separate from
// InitMetricsAndCrashReportingForTesting as the Set isn't meant to be used
// repeatedly. Used only for testing.
static void JNI_UmaSessionStats_UpdateMetricsAndCrashReportingForTesting(
    JNIEnv*,
    jboolean consent) {
  DCHECK(g_browser_process);

  g_metrics_consent_for_testing = consent;
  g_browser_process->GetMetricsServicesManager()->UpdateUploadPermissions(true);
}

// Starts/stops the MetricsService based on existing consent and upload
// preferences.
// There are three possible states:
// * Logs are being recorded and being uploaded to the server.
// * Logs are being recorded, but not being uploaded to the server.
//   This happens when we've got permission to upload on Wi-Fi but we're on a
//   mobile connection (for example).
// * Logs are neither being recorded or uploaded.
// If logs aren't being recorded, then |may_upload| is ignored.
//
// This can be called at any time when consent hasn't changed, such as
// connection type change, or start up. If consent has changed, then
// ChangeMetricsReportingConsent() should be called first.
static void JNI_UmaSessionStats_UpdateMetricsServiceState(
    JNIEnv*,
    jboolean may_upload) {
  // This will also apply the consent state, taken from Chrome Local State
  // prefs.
  g_browser_process->GetMetricsServicesManager()->UpdateUploadPermissions(
      may_upload);
}

static void JNI_UmaSessionStats_RegisterExternalExperiment(
    JNIEnv* env,
    const JavaParamRef<jintArray>& jexperiment_ids,
    jboolean override_existing_ids) {
  std::vector<int> experiment_ids;
  // A null |jexperiment_ids| is the same as an empty list.
  if (jexperiment_ids) {
    base::android::JavaIntArrayToIntVector(env, jexperiment_ids,
                                           &experiment_ids);
  }

  auto override_mode =
      override_existing_ids
          ? variations::SyntheticTrialRegistry::kOverrideExistingIds
          : variations::SyntheticTrialRegistry::kDoNotOverrideExistingIds;

  g_browser_process->metrics_service()
      ->GetSyntheticTrialRegistry()
      ->RegisterExternalExperiments(experiment_ids, override_mode);
}

static void JNI_UmaSessionStats_RegisterSyntheticFieldTrial(
    JNIEnv* env,
    std::string& trial_name,
    std::string& group_name,
    int annotation_mode) {
  UmaSessionStats::RegisterSyntheticFieldTrial(
      trial_name, group_name,
      static_cast<variations::SyntheticTrialAnnotationMode>(annotation_mode));
}

static void JNI_UmaSessionStats_RecordTabCountPerLoad(
    JNIEnv*,
    jint num_tabs) {
  // Record how many tabs total are open.
  UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountPerLoad", num_tabs, 1, 200, 50);
}

static void JNI_UmaSessionStats_RecordPageLoaded(
    JNIEnv*,
    jboolean is_desktop_user_agent) {
  // Should be called whenever a page has been loaded.
  base::RecordAction(UserMetricsAction("MobilePageLoaded"));
  if (is_desktop_user_agent) {
    base::RecordAction(UserMetricsAction("MobilePageLoadedDesktopUserAgent"));
  }
}

static void JNI_UmaSessionStats_RecordPageLoadedWithAccessory(JNIEnv*) {
  base::RecordAction(UserMetricsAction("MobilePageLoadedWithAccessory"));
}

static void JNI_UmaSessionStats_RecordPageLoadedWithKeyboard(JNIEnv*) {
  base::RecordAction(UserMetricsAction("MobilePageLoadedWithKeyboard"));
}

static void JNI_UmaSessionStats_RecordPageLoadedWithMouse(JNIEnv*) {
  base::RecordAction(UserMetricsAction("MobilePageLoadedWithMouse"));
}

static void JNI_UmaSessionStats_RecordPageLoadedWithToEdge(JNIEnv*) {
  base::RecordAction(UserMetricsAction("MobilePageLoadedWithToEdge"));
}

static jlong JNI_UmaSessionStats_Init(JNIEnv* env) {
  // We should have only one UmaSessionStats instance.
  return reinterpret_cast<intptr_t>(UmaSessionStats::GetInstance());
}