Compare commits

...

3 Commits

Author SHA1 Message Date
Niklas Wenzel
789ae68cc4 fix: disable --memlog tests on ASAN builds where --memlog is disabled 2026-04-10 02:58:57 +02:00
Niklas Wenzel
a1638fd996 fix: make heap dump symbolication work 2026-04-10 02:58:55 +02:00
Niklas Wenzel
f4f30bfa86 feat: support heap profiling via --memlog=all
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2026-04-10 02:58:42 +02:00
19 changed files with 625 additions and 2 deletions

View File

@@ -476,8 +476,12 @@ source_set("electron_lib") {
"//components/certificate_transparency",
"//components/compose:buildflags",
"//components/embedder_support:user_agent",
"//components/heap_profiling/in_process",
"//components/heap_profiling/in_process:mojom",
"//components/heap_profiling/multi_process",
"//components/input",
"//components/language/core/browser",
"//components/memory_system",
"//components/net_log",
"//components/network_hints/browser",
"//components/network_hints/common:mojo_bindings",
@@ -490,6 +494,7 @@ source_set("electron_lib") {
"//components/pref_registry",
"//components/prefs",
"//components/security_state/content",
"//components/tracing:tracing_metrics",
"//components/upload_list",
"//components/user_prefs",
"//components/viz/host",

View File

@@ -432,6 +432,8 @@ filenames = {
"shell/browser/media/media_capture_devices_dispatcher.h",
"shell/browser/media/media_device_id_salt.cc",
"shell/browser/media/media_device_id_salt.h",
"shell/browser/metrics/electron_metrics_service_client.cc",
"shell/browser/metrics/electron_metrics_service_client.h",
"shell/browser/microtasks_runner.cc",
"shell/browser/microtasks_runner.h",
"shell/browser/native_window.cc",
@@ -508,6 +510,10 @@ filenames = {
"shell/browser/session_preferences.h",
"shell/browser/special_storage_policy.cc",
"shell/browser/special_storage_policy.h",
"shell/browser/tracing/electron_background_tracing_metrics_provider.cc",
"shell/browser/tracing/electron_background_tracing_metrics_provider.h",
"shell/browser/tracing/electron_tracing_delegate.cc",
"shell/browser/tracing/electron_tracing_delegate.h",
"shell/browser/ui/accelerator_util.cc",
"shell/browser/ui/accelerator_util.h",
"shell/browser/ui/autofill_popup.cc",

View File

@@ -11,11 +11,17 @@
#include "base/command_line.h"
#include "base/containers/extend.h"
#include "base/files/file_util.h"
#include "base/no_destructor.h"
#include "base/strings/string_split.h"
#include "components/heap_profiling/in_process/child_process_snapshot_controller.h"
#include "components/heap_profiling/in_process/heap_profiler_controller.h"
#include "components/heap_profiling/in_process/mojom/snapshot_controller.mojom.h"
#include "components/services/heap_profiling/public/cpp/profiling_client.h"
#include "content/public/common/buildflags.h"
#include "electron/buildflags/buildflags.h"
#include "electron/fuses.h"
#include "extensions/common/constants.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "pdf/buildflags.h"
#include "shell/common/options_switches.h"
#include "shell/common/process_util.h"
@@ -217,4 +223,31 @@ void ElectronContentClient::AddContentDecryptionModules(
}
}
// Mirrors |ChromeContentClient::ExposeInterfacesToBrowser|.
void ElectronContentClient::ExposeInterfacesToBrowser(
scoped_refptr<base::SequencedTaskRunner> io_task_runner,
mojo::BinderMap* binders) {
// Sets up the client side of the multi-process heap profiler service.
binders->Add<heap_profiling::mojom::ProfilingClient>(
[](mojo::PendingReceiver<heap_profiling::mojom::ProfilingClient>
receiver) {
static base::NoDestructor<heap_profiling::ProfilingClient>
profiling_client;
profiling_client->BindToInterface(std::move(receiver));
},
io_task_runner);
// Sets up the simplified in-process heap profiler, if it's enabled.
const auto* heap_profiler_controller =
heap_profiling::HeapProfilerController::GetInstance();
if (heap_profiler_controller && heap_profiler_controller->IsEnabled()) {
binders->Add<heap_profiling::mojom::SnapshotController>(
&heap_profiling::ChildProcessSnapshotController::
CreateSelfOwnedReceiver,
// ChildProcessSnapshotController calls into HeapProfilerController,
// which can only be accessed on this sequence.
base::SequencedTaskRunner::GetCurrentDefault());
}
}
} // namespace electron

View File

@@ -33,6 +33,9 @@ class ElectronContentClient : public content::ContentClient {
void AddContentDecryptionModules(
std::vector<content::CdmInfo>* cdms,
std::vector<media::CdmHostFilePath>* cdm_host_file_paths) override;
void ExposeInterfacesToBrowser(
scoped_refptr<base::SequencedTaskRunner> io_task_runner,
mojo::BinderMap* binders) override;
};
} // namespace electron

View File

@@ -23,9 +23,13 @@
#include "base/strings/cstring_view.h"
#include "base/strings/string_number_conversions.cc"
#include "base/strings/string_util_internal.h"
#include "base/version_info/channel.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/profiler/process_type.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/memory_system/initializer.h"
#include "components/memory_system/parameters.h"
#include "content/public/app/initialize_mojo_core.h"
#include "content/public/common/content_switches.h"
#include "crypto/hash.h"
@@ -343,6 +347,39 @@ std::optional<int> ElectronMainDelegate::PreBrowserMain() {
return std::nullopt;
}
std::optional<int> ElectronMainDelegate::PostEarlyInitialization(
InvokedIn invoked_in) {
// Start memory observation as early as possible so it can start recording
// memory allocations. This includes heap profiling.
InitializeMemorySystem();
return std::nullopt;
}
// Mirrors |ChromeMainDelegate::InitializeMemorySystem|.
void ElectronMainDelegate::InitializeMemorySystem() {
const base::CommandLine* const command_line =
base::CommandLine::ForCurrentProcess();
const std::string process_type =
command_line->GetSwitchValueASCII(::switches::kProcessType);
const bool is_browser_process = process_type.empty();
const memory_system::DispatcherParameters::AllocationTraceRecorderInclusion
allocation_recorder_inclusion =
is_browser_process ? memory_system::DispatcherParameters::
AllocationTraceRecorderInclusion::kDynamic
: memory_system::DispatcherParameters::
AllocationTraceRecorderInclusion::kIgnore;
memory_system::Initializer()
.SetGwpAsanParameters(is_browser_process, process_type)
.SetProfilingClientParameters(version_info::Channel::UNKNOWN,
GetProfilerProcessType(*command_line))
.SetDispatcherParameters(memory_system::DispatcherParameters::
PoissonAllocationSamplerInclusion::kEnforce,
allocation_recorder_inclusion, process_type)
.Initialize(memory_system_);
}
std::string_view ElectronMainDelegate::GetBrowserV8SnapshotFilename() {
bool load_browser_process_specific_v8_snapshot =
IsBrowserProcess() &&

View File

@@ -9,6 +9,7 @@
#include <string>
#include <string_view>
#include "components/memory_system/memory_system.h"
#include "content/public/app/content_main_delegate.h"
namespace content {
@@ -47,6 +48,7 @@ class ElectronMainDelegate : public content::ContentMainDelegate {
void PreSandboxStartup() override;
void SandboxInitialized(const std::string& process_type) override;
std::optional<int> PreBrowserMain() override;
std::optional<int> PostEarlyInitialization(InvokedIn invoked_in) override;
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentGpuClient* CreateContentGpuClient() override;
@@ -63,6 +65,8 @@ class ElectronMainDelegate : public content::ContentMainDelegate {
void ZygoteForked() override;
#endif
void InitializeMemorySystem();
private:
std::unique_ptr<content::ContentBrowserClient> browser_client_;
std::unique_ptr<content::ContentClient> content_client_;
@@ -70,6 +74,8 @@ class ElectronMainDelegate : public content::ContentMainDelegate {
std::unique_ptr<content::ContentRendererClient> renderer_client_;
std::unique_ptr<content::ContentUtilityClient> utility_client_;
std::unique_ptr<tracing::TracingSamplerProfiler> tracing_sampler_profiler_;
memory_system::MemorySystem memory_system_;
};
} // namespace electron

View File

@@ -39,6 +39,7 @@
#include "net/proxy_resolution/proxy_config_with_annotation.h"
#include "services/device/public/cpp/geolocation/geolocation_system_permission_manager.h"
#include "services/network/public/cpp/network_switches.h"
#include "shell/browser/metrics/electron_metrics_service_client.h"
#include "shell/browser/net/resolve_proxy_helper.h"
#include "shell/common/electron_paths.h"
#include "shell/common/thread_restrictions.h"
@@ -139,6 +140,8 @@ void BrowserProcessImpl::PostEarlyInitialization() {
PrefServiceFactory prefs_factory;
auto pref_registry = base::MakeRefCounted<PrefRegistrySimple>();
PrefProxyConfigTrackerImpl::RegisterPrefs(pref_registry.get());
electron::ElectronMetricsServiceClient::RegisterMetricsPrefs(
pref_registry.get());
#if BUILDFLAG(IS_WIN)
OSCrypt::RegisterLocalPrefs(pref_registry.get());
@@ -176,6 +179,10 @@ void BrowserProcessImpl::PreCreateThreads() {
// this can be created on first use.
if (!SystemNetworkContextManager::GetInstance())
SystemNetworkContextManager::CreateInstance(local_state_.get());
// Needs to be called here as per
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/chrome_browser_main.cc;l=1385-1389;drc=c3bda003554dad21313fb24b7a4f3e1aae6102d9.
CreateMetricsServiceClient();
}
void BrowserProcessImpl::PreMainMessageLoopRun() {
@@ -201,7 +208,10 @@ BrowserProcessImpl::GetMetricsServicesManager() {
}
metrics::MetricsService* BrowserProcessImpl::metrics_service() {
return nullptr;
if (!metrics_service_client_) {
return nullptr;
}
return metrics_service_client_->GetMetricsService();
}
ProfileManager* BrowserProcessImpl::profile_manager() {
@@ -508,3 +518,8 @@ void BrowserProcessImpl::CreateOSCryptAsync() {
os_crypt_async_ =
std::make_unique<os_crypt_async::OSCryptAsync>(std::move(providers));
}
void BrowserProcessImpl::CreateMetricsServiceClient() {
metrics_service_client_ =
std::make_unique<electron::ElectronMetricsServiceClient>();
}

View File

@@ -32,6 +32,10 @@ namespace printing {
class PrintJobManager;
}
namespace metrics {
class MetricsServiceClient;
}
namespace electron {
class ResolveProxyHelper;
}
@@ -145,6 +149,7 @@ class BrowserProcessImpl : public BrowserProcess {
private:
void CreateNetworkQualityObserver();
void CreateOSCryptAsync();
void CreateMetricsServiceClient();
network::NetworkQualityTracker* GetNetworkQualityTracker();
#if BUILDFLAG(ENABLE_PRINTING)
@@ -166,6 +171,7 @@ class BrowserProcessImpl : public BrowserProcess {
network_quality_observer_;
std::unique_ptr<supervised_user::DeviceParentalControls>
device_parental_controls_;
std::unique_ptr<metrics::MetricsServiceClient> metrics_service_client_;
std::unique_ptr<os_crypt_async::OSCryptAsync> os_crypt_async_;
};

View File

@@ -110,6 +110,7 @@
#include "shell/browser/protocol_registry.h"
#include "shell/browser/serial/electron_serial_delegate.h"
#include "shell/browser/session_preferences.h"
#include "shell/browser/tracing/electron_tracing_delegate.h"
#include "shell/browser/ui/devtools_manager_delegate.h"
#include "shell/browser/usb/electron_usb_delegate.h"
#include "shell/browser/web_contents_permission_helper.h"
@@ -1056,6 +1057,11 @@ std::string ElectronBrowserClient::GetProduct() {
return "Chrome/" CHROME_VERSION_STRING;
}
std::unique_ptr<content::TracingDelegate>
ElectronBrowserClient::CreateTracingDelegate() {
return std::make_unique<ElectronTracingDelegate>();
}
std::string ElectronBrowserClient::GetUserAgent() {
if (user_agent_override_.empty())
return GetApplicationUserAgent();

View File

@@ -218,6 +218,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
network::mojom::NetworkService* network_service) override;
std::vector<base::FilePath> GetNetworkContextsParentDirectory() override;
std::string GetProduct() override;
std::unique_ptr<content::TracingDelegate> CreateTracingDelegate() override;
mojo::PendingRemote<network::mojom::URLLoaderFactory>
CreateNonNetworkNavigationURLLoaderFactory(
const std::string& scheme,

View File

@@ -22,10 +22,13 @@
#include "chrome/browser/icon_manager.h"
#include "chrome/browser/ui/color/chrome_color_mixers.h"
#include "chrome/common/chrome_switches.h"
#include "components/heap_profiling/multi_process/client_connection_manager.h"
#include "components/heap_profiling/multi_process/supervisor.h"
#include "components/os_crypt/sync/key_storage_config_linux.h"
#include "components/os_crypt/sync/key_storage_util_linux.h"
#include "components/os_crypt/sync/os_crypt.h"
#include "components/password_manager/core/browser/password_manager_switches.h" // nogncheck
#include "components/services/heap_profiling/public/cpp/settings.h"
#include "content/browser/browser_main_loop.h" // nogncheck
#include "content/public/browser/browser_child_process_host_delegate.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
@@ -173,6 +176,31 @@ std::u16string MediaStringProvider(media::MessageId id) {
}
}
std::unique_ptr<heap_profiling::ClientConnectionManager>
CreateClientConnectionManager(
base::WeakPtr<heap_profiling::Controller> controller_weak_ptr,
heap_profiling::Mode mode) {
return std::make_unique<heap_profiling::ClientConnectionManager>(
controller_weak_ptr, mode);
}
// Mirrors |ChromeBrowserMainExtraPartsProfiling::PostCreateThreads|.
void InitializeHeapProfiling() {
heap_profiling::Supervisor::GetInstance()
->SetClientConnectionManagerConstructor(&CreateClientConnectionManager);
#if !defined(ADDRESS_SANITIZER)
// Memory sanitizers are using large memory shadow to keep track of memory
// state. Using memlog and memory sanitizers at the same time is slowing down
// user experience, causing the browser to be barely responsive. In theory,
// memlog and memory sanitizers are compatible and can run at the same time.
heap_profiling::Mode mode = heap_profiling::GetModeForStartup();
if (mode != heap_profiling::Mode::kNone) {
heap_profiling::Supervisor::GetInstance()->Start(base::OnceClosure());
}
#endif
}
} // namespace
// static
@@ -376,6 +404,8 @@ int ElectronBrowserMainParts::PreCreateThreads() {
}
void ElectronBrowserMainParts::PostCreateThreads() {
InitializeHeapProfiling();
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&tracing::TracingSamplerProfiler::CreateOnChildThread));

View File

@@ -0,0 +1,131 @@
// Copyright (c) 2026 Niklas Wenzel
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/metrics/electron_metrics_service_client.h"
#include <memory>
#include <string>
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/version_info/channel.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_paths.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/metrics/net/cellular_logic_helper.h"
#include "components/metrics/net/net_metrics_log_uploader.h"
#include "components/metrics/version_utils.h"
#include "components/prefs/pref_service.h"
#include "components/variations/synthetic_trial_registry.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "shell/browser/tracing/electron_background_tracing_metrics_provider.h"
#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
namespace electron {
ElectronMetricsServiceClient::ElectronMetricsServiceClient() {
PrefService* local_state = g_browser_process->local_state();
base::FilePath user_data_dir;
base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
synthetic_trial_registry_ =
std::make_unique<variations::SyntheticTrialRegistry>();
metrics_state_manager_ = metrics::MetricsStateManager::Create(
local_state, this, std::wstring(), user_data_dir,
metrics::StartupVisibility::kUnknown);
metrics_service_ = std::make_unique<metrics::MetricsService>(
metrics_state_manager_.get(), this, local_state);
RegisterMetricsServiceProviders();
}
ElectronMetricsServiceClient::~ElectronMetricsServiceClient() = default;
// static
void ElectronMetricsServiceClient::RegisterMetricsPrefs(
PrefRegistrySimple* registry) {
metrics::MetricsService::RegisterPrefs(registry);
}
variations::SyntheticTrialRegistry*
ElectronMetricsServiceClient::GetSyntheticTrialRegistry() {
return synthetic_trial_registry_.get();
}
metrics::MetricsService* ElectronMetricsServiceClient::GetMetricsService() {
return metrics_service_.get();
}
void ElectronMetricsServiceClient::SetMetricsClientId(
const std::string& client_id) {}
int32_t ElectronMetricsServiceClient::GetProduct() {
return metrics::ChromeUserMetricsExtension::CHROME;
}
std::string ElectronMetricsServiceClient::GetApplicationLocale() {
return g_browser_process ? g_browser_process->GetApplicationLocale()
: std::string();
}
const network_time::NetworkTimeTracker*
ElectronMetricsServiceClient::GetNetworkTimeTracker() {
return g_browser_process ? g_browser_process->network_time_tracker()
: nullptr;
}
bool ElectronMetricsServiceClient::GetBrand(std::string* brand_code) {
return false;
}
metrics::SystemProfileProto::Channel
ElectronMetricsServiceClient::GetChannel() {
return metrics::AsProtobufChannel(version_info::Channel::UNKNOWN);
}
bool ElectronMetricsServiceClient::IsExtendedStableChannel() {
return false;
}
std::string ElectronMetricsServiceClient::GetVersionString() {
return metrics::GetVersionString();
}
void ElectronMetricsServiceClient::CollectFinalMetricsForLog(
base::OnceClosure done_callback) {
if (done_callback) {
std::move(done_callback).Run();
}
}
std::unique_ptr<metrics::MetricsLogUploader>
ElectronMetricsServiceClient::CreateUploader(
const GURL& server_url,
const GURL& insecure_server_url,
std::string_view mime_type,
metrics::MetricsLogUploader::MetricServiceType service_type,
const metrics::MetricsLogUploader::UploadCallback& on_upload_complete) {
return std::make_unique<metrics::NetMetricsLogUploader>(
g_browser_process->shared_url_loader_factory(), server_url,
insecure_server_url, mime_type, service_type, on_upload_complete);
}
base::TimeDelta ElectronMetricsServiceClient::GetStandardUploadInterval() {
return metrics::GetUploadInterval(metrics::ShouldUseCellularUploadInterval());
}
bool ElectronMetricsServiceClient::IsConsentGiven() const {
return false;
}
void ElectronMetricsServiceClient::RegisterMetricsServiceProviders() {
metrics_service_->RegisterMetricsProvider(
std::make_unique<ElectronBackgroundTracingMetricsProvider>());
}
} // namespace electron

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2026 Niklas Wenzel
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_METRICS_ELECTRON_METRICS_SERVICE_CLIENT_H_
#define ELECTRON_SHELL_BROWSER_METRICS_ELECTRON_METRICS_SERVICE_CLIENT_H_
#include <memory>
#include "components/metrics/enabled_state_provider.h"
#include "components/metrics/metrics_service_client.h"
namespace metrics {
class MetricsService;
class MetricsStateManager;
} // namespace metrics
namespace variations {
class SyntheticTrialRegistry;
}
namespace electron {
class ElectronMetricsServiceClient : public metrics::MetricsServiceClient,
public metrics::EnabledStateProvider {
public:
ElectronMetricsServiceClient();
ElectronMetricsServiceClient(const ElectronMetricsServiceClient&) = delete;
ElectronMetricsServiceClient& operator=(const ElectronMetricsServiceClient&) =
delete;
~ElectronMetricsServiceClient() override;
static void RegisterMetricsPrefs(PrefRegistrySimple* registry);
// metrics::MetricsServiceClient:
variations::SyntheticTrialRegistry* GetSyntheticTrialRegistry() override;
metrics::MetricsService* GetMetricsService() override;
void SetMetricsClientId(const std::string& client_id) override;
int32_t GetProduct() override;
std::string GetApplicationLocale() override;
const network_time::NetworkTimeTracker* GetNetworkTimeTracker() override;
bool GetBrand(std::string* brand_code) override;
metrics::SystemProfileProto::Channel GetChannel() override;
bool IsExtendedStableChannel() override;
std::string GetVersionString() override;
void CollectFinalMetricsForLog(base::OnceClosure done_callback) override;
std::unique_ptr<metrics::MetricsLogUploader> CreateUploader(
const GURL& server_url,
const GURL& insecure_server_url,
std::string_view mime_type,
metrics::MetricsLogUploader::MetricServiceType service_type,
const metrics::MetricsLogUploader::UploadCallback& on_upload_complete)
override;
base::TimeDelta GetStandardUploadInterval() override;
// metrics::EnabledStateProvider:
bool IsConsentGiven() const override;
private:
void RegisterMetricsServiceProviders();
std::unique_ptr<variations::SyntheticTrialRegistry> synthetic_trial_registry_;
std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager_;
std::unique_ptr<metrics::MetricsService> metrics_service_;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_METRICS_ELECTRON_METRICS_SERVICE_CLIENT_H_

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2026 Niklas Wenzel
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/tracing/electron_background_tracing_metrics_provider.h"
#include <memory>
#include <string>
#include "base/version_info/channel.h"
#include "components/metrics/metrics_log.h"
#include "components/metrics/version_utils.h"
#include "shell/browser/electron_browser_client.h"
namespace electron {
ElectronBackgroundTracingMetricsProvider::
ElectronBackgroundTracingMetricsProvider() = default;
ElectronBackgroundTracingMetricsProvider::
~ElectronBackgroundTracingMetricsProvider() = default;
void ElectronBackgroundTracingMetricsProvider::RecordCoreSystemProfileMetrics(
metrics::SystemProfileProto& system_profile_proto) {
auto* browser_client = ElectronBrowserClient::Get();
const std::string application_locale =
browser_client ? browser_client->GetApplicationLocale() : std::string();
metrics::MetricsLog::RecordCoreSystemProfile(
metrics::GetVersionString(),
metrics::AsProtobufChannel(version_info::Channel::UNKNOWN), false,
application_locale, std::string(), &system_profile_proto);
}
} // namespace electron

View File

@@ -0,0 +1,31 @@
// Copyright (c) 2026 Niklas Wenzel
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_TRACING_ELECTRON_BACKGROUND_TRACING_METRICS_PROVIDER_H_
#define ELECTRON_SHELL_BROWSER_TRACING_ELECTRON_BACKGROUND_TRACING_METRICS_PROVIDER_H_
#include "components/tracing/common/background_tracing_metrics_provider.h"
namespace electron {
class ElectronBackgroundTracingMetricsProvider
: public tracing::BackgroundTracingMetricsProvider {
public:
ElectronBackgroundTracingMetricsProvider();
ElectronBackgroundTracingMetricsProvider(
const ElectronBackgroundTracingMetricsProvider&) = delete;
ElectronBackgroundTracingMetricsProvider& operator=(
const ElectronBackgroundTracingMetricsProvider&) = delete;
~ElectronBackgroundTracingMetricsProvider() override;
// metrics::MetricsProvider:
void RecordCoreSystemProfileMetrics(
metrics::SystemProfileProto& system_profile_proto) override;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_TRACING_ELECTRON_BACKGROUND_TRACING_METRICS_PROVIDER_H_

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2026 Niklas Wenzel
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/tracing/electron_tracing_delegate.h"
#include <string>
#include "base/functional/bind.h"
#include "components/tracing/common/background_tracing_metrics_provider.h"
#include "components/tracing/common/system_profile_metadata_recorder.h"
#include "third_party/metrics_proto/system_profile.pb.h"
namespace electron {
ElectronTracingDelegate::ElectronTracingDelegate() = default;
ElectronTracingDelegate::~ElectronTracingDelegate() = default;
// Mirrors |ChromeTracingDelegate::RecordSerializedSystemProfileMetrics|.
std::string ElectronTracingDelegate::RecordSerializedSystemProfileMetrics()
const {
metrics::SystemProfileProto system_profile_proto;
auto recorder = tracing::BackgroundTracingMetricsProvider::
GetSystemProfileMetricsRecorder();
if (!recorder) {
return std::string();
}
recorder.Run(system_profile_proto);
std::string serialized_system_profile;
system_profile_proto.SerializeToString(&serialized_system_profile);
return serialized_system_profile;
}
tracing::MetadataDataSource::BundleRecorder
ElectronTracingDelegate::CreateSystemProfileMetadataRecorder() const {
return base::BindRepeating(&tracing::RecordSystemProfileMetadata);
}
} // namespace electron

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2026 Niklas Wenzel
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_TRACING_ELECTRON_TRACING_DELEGATE_H_
#define ELECTRON_SHELL_BROWSER_TRACING_ELECTRON_TRACING_DELEGATE_H_
#include "content/public/browser/tracing_delegate.h"
#include "services/tracing/public/cpp/perfetto/metadata_data_source.h"
namespace electron {
class ElectronTracingDelegate : public content::TracingDelegate {
public:
ElectronTracingDelegate();
ElectronTracingDelegate(const ElectronTracingDelegate&) = delete;
ElectronTracingDelegate& operator=(const ElectronTracingDelegate&) = delete;
~ElectronTracingDelegate() override;
// content::TracingDelegate:
std::string RecordSerializedSystemProfileMetrics() const override;
tracing::MetadataDataSource::BundleRecorder
CreateSystemProfileMetadataRecorder() const override;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_TRACING_ELECTRON_TRACING_DELEGATE_H_

View File

@@ -2,11 +2,14 @@ import { app, contentTracing, TraceConfig, TraceCategoriesAndOptions } from 'ele
import { expect } from 'chai';
import { once } from 'node:events';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { setTimeout } from 'node:timers/promises';
import { ifdescribe } from './lib/spec-helpers';
import { ifdescribe, ifit, startRemoteControlApp } from './lib/spec-helpers';
const fixturesPath = path.resolve(__dirname, 'fixtures');
// FIXME: The tests are skipped on linux arm/arm64
ifdescribe(!(['arm', 'arm64'].includes(process.arch)) || (process.platform !== 'linux'))('contentTracing', () => {
@@ -184,5 +187,137 @@ ifdescribe(!(['arm', 'arm64'].includes(process.arch)) || (process.platform !== '
const parsed = JSON.parse(data);
expect(parsed.traceEvents.some((x: any) => x.cat === 'disabled-by-default-v8.cpu_profiler' && x.name === 'ProfileChunk')).to.be.true();
});
describe('memory-infra heap dumps', () => {
const checkForHeapDumps = async (launchArgs: string[]) => {
const rc = await startRemoteControlApp(launchArgs);
const { hasBrowserProcessHeapDump, hasRendererProcessHeapDump, hasUtilityProcessHeapDump } = await rc.remotely(async (utilityProcessPath: string) => {
const { contentTracing, BrowserWindow, utilityProcess } = require('electron');
const { once } = require('node:events');
const fs = require('node:fs');
const process = require('node:process');
const { setTimeout } = require('node:timers/promises');
const isEventWithNonEmptyHeapDumpForProcess = (event: any, pid: number) =>
event.cat === 'disabled-by-default-memory-infra' &&
event.name === 'periodic_interval' &&
event.pid === pid &&
event.args.dumps.level_of_detail === 'detailed' &&
event.args.dumps.process_mmaps?.vm_regions.length > 0 &&
Object.values(event.args.dumps.allocators).some((allocator: any) => allocator.attrs.size?.value !== "0") &&
Object.values(event.args.dumps.heaps_v2.allocators).some((allocator: any) => allocator.counts.length > 0 && allocator.nodes.length > 0 && allocator.sizes.length > 0);
const hasNonEmptyHeapDumpForProcess = (parsedTrace: any, pid: number) =>
parsedTrace.traceEvents.some((event: any) => isEventWithNonEmptyHeapDumpForProcess(event, pid));
const interval = 1000;
await contentTracing.startRecording({
included_categories: ["disabled-by-default-memory-infra"],
excluded_categories: ["*"],
memory_dump_config: {
triggers: [
{ mode: "detailed", periodic_interval_ms: interval },
],
},
});
// Launch a renderer process
const window = new BrowserWindow({ show: false });
await window.webContents.loadURL('about:blank');
// Launch a utility process
const utility = utilityProcess.fork(utilityProcessPath);
await once(utility, 'spawn');
// Collect heap dumps
await setTimeout(2 * interval);
const path = await contentTracing.stopRecording();
const data = fs.readFileSync(path, 'utf8');
const parsed = JSON.parse(data);
const hasBrowserProcessHeapDump = hasNonEmptyHeapDumpForProcess(parsed, process.pid);
const hasRendererProcessHeapDump = hasNonEmptyHeapDumpForProcess(parsed, window.webContents.getOSProcessId());
const hasUtilityProcessHeapDump = hasNonEmptyHeapDumpForProcess(parsed, utility.pid);
global.setTimeout(() => require('electron').app.quit());
return {
hasBrowserProcessHeapDump,
hasRendererProcessHeapDump,
hasUtilityProcessHeapDump
};
}, path.join(fixturesPath, 'api', 'content-tracing', 'utility.js'));
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(0);
return {
hasBrowserProcessHeapDump,
hasRendererProcessHeapDump,
hasUtilityProcessHeapDump
};
};
it('are not included when launched without --memlog', async function () {
const { hasBrowserProcessHeapDump, hasRendererProcessHeapDump, hasUtilityProcessHeapDump } = await checkForHeapDumps([]);
expect(hasBrowserProcessHeapDump).to.be.false();
expect(hasRendererProcessHeapDump).to.be.false();
expect(hasUtilityProcessHeapDump).to.be.false();
});
// We disable --memlog for ASAN builds
ifit(!process.env.IS_ASAN)('are included for browser process when launched with --memlog=browser', async function () {
const { hasBrowserProcessHeapDump, hasRendererProcessHeapDump, hasUtilityProcessHeapDump } = await checkForHeapDumps(['--memlog=browser']);
expect(hasBrowserProcessHeapDump).to.be.true();
expect(hasRendererProcessHeapDump).to.be.false();
expect(hasUtilityProcessHeapDump).to.be.false();
});
ifit(!process.env.IS_ASAN)('are included for renderer processes when launched with --memlog=all-renderers', async function () {
const { hasBrowserProcessHeapDump, hasRendererProcessHeapDump, hasUtilityProcessHeapDump } = await checkForHeapDumps(['--memlog=all-renderers']);
expect(hasBrowserProcessHeapDump).to.be.false();
expect(hasRendererProcessHeapDump).to.be.true();
expect(hasUtilityProcessHeapDump).to.be.false();
});
ifit(!process.env.IS_ASAN)('are included for utility processes when launched with --memlog=all-utilities', async function () {
const { hasBrowserProcessHeapDump, hasRendererProcessHeapDump, hasUtilityProcessHeapDump } = await checkForHeapDumps(['--memlog=all-utilities']);
expect(hasBrowserProcessHeapDump).to.be.false();
expect(hasRendererProcessHeapDump).to.be.false();
expect(hasUtilityProcessHeapDump).to.be.true();
});
ifit(!process.env.IS_ASAN)('are included for browser, renderer, and utility processes when launched with --memlog=all', async function () {
const { hasBrowserProcessHeapDump, hasRendererProcessHeapDump, hasUtilityProcessHeapDump } = await checkForHeapDumps(['--memlog=all']);
expect(hasBrowserProcessHeapDump).to.be.true();
expect(hasRendererProcessHeapDump).to.be.true();
expect(hasUtilityProcessHeapDump).to.be.true();
});
});
});
describe('trace metadata', () => {
// These are necessary to be able to symbolicate heap dumps using third_party/catapult/tracing/bin/symbolize_trace (see https://github.com/electron/electron/pull/50826).
it('includes product version and OS arch metadata in JSON output', async () => {
const config = {
excluded_categories: ['*']
};
await record(config, outputFilePath);
const content = fs.readFileSync(outputFilePath).toString();
const parsed = JSON.parse(content);
expect(parsed.metadata).to.be.an('object');
expect(parsed.metadata['product-version']).to.be.a('string');
expect(parsed.metadata['product-version'].startsWith(process.versions.chrome)).to.be.true();
expect(parsed.metadata['os-arch']).to.equal(process.arch);
});
});
});

View File

@@ -0,0 +1,3 @@
setInterval(() => {
new Array(1000000).fill(0);
}, 100);