Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions ui/src/core/analytics_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {VERSION} from '../gen/perfetto_version';
import {Router} from './router';
import {Analytics, TraceCategories} from '../public/analytics';

const ANALYTICS_ID = 'G-BD89KT2P3C';
const PAGE_TITLE = 'no-page-title';

function isValidUrl(s: string) {
Expand Down Expand Up @@ -69,21 +68,15 @@ export function initAnalytics(
testingMode: boolean,
embeddedMode: boolean,
enable: boolean,
analyticsId: string | undefined,
): AnalyticsInternal {
// Only initialize logging on the official site and on localhost (to catch
// analytics bugs when testing locally).
// Skip analytics is the fragment has "testing=1", this is used by UI tests.
// Skip analytics if the fragment has "testing=1", this is used by UI tests.
// Skip analytics in embeddedMode since iFrames do not have the same access to
// local storage.
// Skip analytics if the user has disabled analytics.
if (
(window.location.origin.startsWith('http://localhost:') ||
window.location.origin.endsWith('.perfetto.dev')) &&
!testingMode &&
!embeddedMode &&
enable
) {
return new AnalyticsImpl();
// Skip analytics if the embedder does not provide an analytics ID.
if (analyticsId !== undefined && !testingMode && !embeddedMode && enable) {
return new AnalyticsImpl(analyticsId);
}
return new NullAnalytics();
}
Expand All @@ -105,8 +98,10 @@ class NullAnalytics implements AnalyticsInternal {

class AnalyticsImpl implements AnalyticsInternal {
private initialized_ = false;
private readonly analyticsId: string;

constructor() {
constructor(analyticsId: string) {
this.analyticsId = analyticsId;
// The code below is taken from the official Google Analytics docs [1] and
// adapted to TypeScript. We have it here rather than as an inline script
// in index.html (as suggested by GA's docs) because inline scripts don't
Expand All @@ -133,7 +128,8 @@ class AnalyticsImpl implements AnalyticsInternal {
if (this.initialized_) return;
this.initialized_ = true;
const script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + ANALYTICS_ID;
script.src =
'https://www.googletagmanager.com/gtag/js?id=' + this.analyticsId;
script.defer = true;
document.head.appendChild(script);
const route = window.location.href;
Expand All @@ -144,7 +140,7 @@ class AnalyticsImpl implements AnalyticsInternal {
// GA's recommendation for SPAs is to disable automatic page views and
// manually send page_view events. See:
// https://developers.google.com/analytics/devguides/collection/gtagjs/pages#manual_pageviews
gtagGlobals.gtag('config', ANALYTICS_ID, {
gtagGlobals.gtag('config', this.analyticsId, {
allow_google_signals: false,
anonymize_ip: true,
page_location: route,
Expand Down
5 changes: 5 additions & 0 deletions ui/src/core/app_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {SerializedAppState} from './state_serialization_schema';
import {TraceImpl} from './trace_impl';
import {TraceArrayBufferSource, TraceSource} from './trace_source';
import {TaskTrackerImpl} from '../frontend/task_tracker/task_tracker';
import {Embedder} from './embedder/embedder';
import {createEmbedder} from './embedder/create_embedder';

export type OpenTraceArrayBufArgs = Omit<
Omit<TraceArrayBufferSource, 'type'>,
Expand Down Expand Up @@ -89,6 +91,7 @@ export class AppImpl implements App {
readonly testingMode: boolean;
readonly openTraceAsyncLimiter = new AsyncLimiter();
readonly settings: SettingsManagerImpl;
readonly embedder: Embedder;

// The current active trace (if any).
private _activeTrace: TraceImpl | undefined;
Expand Down Expand Up @@ -147,10 +150,12 @@ export class AppImpl implements App {
disabled: this.embeddedMode,
hidden: this.initialRouteArgs.hideSidebar,
});
this.embedder = createEmbedder();
this.analytics = initAnalytics(
this.testingMode,
this.embeddedMode,
initArgs.analyticsSetting.get(),
this.embedder.analyticsId,
);
this.pages = new PageManagerImpl(this.analytics);
}
Expand Down
34 changes: 34 additions & 0 deletions ui/src/core/embedder/create_embedder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {Embedder} from './embedder';
import {DefaultEmbedder} from './default_embedder';
import {PerfettoUiEmbedder} from './perfetto_ui_embedder';

/**
* Returns the appropriate Embedder based on the current origin.
* Uses PerfettoUiEmbedder when running on ui.perfetto.dev or localhost,
* and DefaultEmbedder otherwise.
*/
export function createEmbedder(): Embedder {
const origin = self.location?.origin ?? '';
if (
origin.endsWith('.perfetto.dev') ||
origin.startsWith('http://localhost:') ||
origin.startsWith('http://127.0.0.1:')
) {
return new PerfettoUiEmbedder();
}
return new DefaultEmbedder();
}
20 changes: 20 additions & 0 deletions ui/src/core/embedder/default_embedder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {Embedder} from './embedder';

/** Default embedder implementation for third-party embeddings. */
export class DefaultEmbedder implements Embedder {
readonly analyticsId = undefined;
}
24 changes: 24 additions & 0 deletions ui/src/core/embedder/embedder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* Interface for embedder-specific behavior. Different implementations allow
* the UI to adapt to the environment it's running in (e.g. ui.perfetto.dev
* vs a third-party embedding).
*/
export interface Embedder {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we like Embedder as a name or do we prefer "deployment" or something else?

// Returns the Google Analytics measurement ID, or undefined if analytics
// should not be enabled for this embedder.
readonly analyticsId: string | undefined;
}
20 changes: 20 additions & 0 deletions ui/src/core/embedder/perfetto_ui_embedder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {Embedder} from './embedder';

/** Embedder implementation for ui.perfetto.dev and localhost development. */
export class PerfettoUiEmbedder implements Embedder {
readonly analyticsId = 'G-BD89KT2P3C';
}