| // Copyright 2023 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/app/application_storage_metrics.h" |
| |
| #import <Foundation/Foundation.h> |
| |
| #import "base/apple/foundation_util.h" |
| #import "base/base_paths.h" |
| #import "base/files/file_enumerator.h" |
| #import "base/files/file_path.h" |
| #import "base/files/file_util.h" |
| #import "base/metrics/histogram_macros.h" |
| #import "base/path_service.h" |
| #import "base/task/sequenced_task_runner.h" |
| #import "base/task/thread_pool.h" |
| #import "components/history/core/browser/history_constants.h" |
| #import "components/optimization_guide/core/optimization_guide_constants.h" |
| #import "ios/chrome/browser/shared/model/paths/paths.h" |
| |
| // The etension used for all snapshot images. |
| constexpr std::string_view kSnapshotImageExtension = ".jpg"; |
| // The label appended to the snapshot filename for grey snapshot images. |
| constexpr std::string_view kGreySnapshotImageIdentifier = "Grey"; |
| |
| // The path, relative to the profile directory, where tab state is stored. |
| const base::FilePath::CharType kSessionsPath[] = FILE_PATH_LITERAL("Sessions"); |
| |
| // The path, relative to the profile directory, where snapshots are stored. |
| const base::FilePath::CharType kSnapshotsPath[] = |
| FILE_PATH_LITERAL("Snapshots"); |
| |
| // The path, relative to the application's Library directory, to WebKit's |
| // storage location for website local data . |
| const base::FilePath::CharType kWebsiteLocalDataPath[] = |
| FILE_PATH_LITERAL("WebKit/WebsiteData/Default"); |
| |
| // The path, relative to the tmp directory, used for WebKit tmp data. |
| const base::FilePath::CharType kWebKitTmpPath[] = FILE_PATH_LITERAL("WebKit"); |
| |
| // The path, relative to the Caches directory, used for WebKit cache. |
| const base::FilePath::CharType kWebKitCachePath[] = FILE_PATH_LITERAL("WebKit"); |
| |
| struct DirectorySnapshotDetails { |
| DirectorySnapshotDetails() : snapshot_count(0), total_size_bytes(0) {} |
| |
| int snapshot_count; |
| int64_t total_size_bytes; |
| }; |
| |
| DirectorySnapshotDetails CalculateImageMetricsInRoot(bool grey_only, |
| base::FilePath root) { |
| DirectorySnapshotDetails details; |
| |
| if (!base::PathExists(root)) { |
| return details; |
| } |
| |
| base::File file(root, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| base::File::Info info; |
| if (!file.IsValid() || !file.GetInfo(&info)) { |
| return details; |
| } |
| |
| if (!info.is_directory) { |
| if (root.MatchesExtension(kSnapshotImageExtension)) { |
| // Add this snapshot to the total if search for all snapshots or searching |
| // for grey snapshots only and the filename contains |
| // `kGreySnapshotImageIdentifier`. |
| if (!grey_only || |
| root.BaseName().MaybeAsASCII().find(kGreySnapshotImageIdentifier) != |
| std::string::npos) { |
| details.snapshot_count += 1; |
| details.total_size_bytes += info.size; |
| } |
| } |
| return details; |
| } |
| |
| base::FileEnumerator enumerator( |
| root, /*recursive=*/false, |
| base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); |
| for (base::FilePath path = enumerator.Next(); !path.empty(); |
| path = enumerator.Next()) { |
| DirectorySnapshotDetails child_details = |
| CalculateImageMetricsInRoot(grey_only, root.Append(path.BaseName())); |
| details.snapshot_count += child_details.snapshot_count; |
| details.total_size_bytes += child_details.total_size_bytes; |
| } |
| |
| return details; |
| } |
| // Calculates and returns the total size used by `root`. |
| int64_t CalculateTotalSize(base::FilePath root) { |
| if (!base::PathExists(root)) { |
| return 0; |
| } |
| |
| base::File file(root, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| base::File::Info info; |
| if (!file.IsValid() || !file.GetInfo(&info)) { |
| return 0; |
| } |
| |
| if (!info.is_directory) { |
| return info.size; |
| } |
| |
| int64_t total_directory_size = 0; |
| |
| base::FileEnumerator enumerator( |
| root, /*recursive=*/false, |
| base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); |
| for (base::FilePath path = enumerator.Next(); !path.empty(); |
| path = enumerator.Next()) { |
| int64_t dir_item_size = CalculateTotalSize(root.Append(path.BaseName())); |
| total_directory_size += dir_item_size; |
| } |
| return total_directory_size; |
| } |
| |
| // Returns the path to the sandbox "Application Support" directory. |
| base::FilePath GetApplicationSupportDirectory() { |
| base::FilePath application_support_path; |
| base::PathService::Get(base::DIR_APP_DATA, &application_support_path); |
| return application_support_path; |
| } |
| |
| // Returns the path to the sandbox "Caches" directory. |
| base::FilePath GetCachesDirectory() { |
| NSArray* cachesDirectories = NSSearchPathForDirectoriesInDomains( |
| NSCachesDirectory, NSUserDomainMask, YES); |
| NSString* cachePath = [cachesDirectories objectAtIndex:0]; |
| return base::apple::NSStringToFilePath(cachePath); |
| } |
| |
| // Logs the WebKit and Chrome Cache directory sizes. Accepts a task runner as a |
| // parameter in order to keep it in scope throughout the execution. |
| void LogCacheDirectorySizes(scoped_refptr<base::SequencedTaskRunner>) { |
| base::FilePath caches_path = GetCachesDirectory(); |
| |
| base::FilePath webkit_caches_path = caches_path.Append(kWebKitCachePath); |
| int64_t webkit_cache_size_bytes = CalculateTotalSize(webkit_caches_path); |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.WebKitCacheSize", |
| webkit_cache_size_bytes / 1024 / 1024); |
| |
| int64_t total_cache_size_bytes = CalculateTotalSize(caches_path); |
| int64_t chrome_cache_size_bytes = |
| total_cache_size_bytes - webkit_cache_size_bytes; |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.ChromeCacheSize", |
| chrome_cache_size_bytes / 1024 / 1024); |
| } |
| |
| // Logs the "Documents" directory size. Accepts a task runner as a parameter in |
| // order to keep it in scope throughout the execution. |
| void LogDocumentsDirectorySize(scoped_refptr<base::SequencedTaskRunner>) { |
| base::FilePath documents_path = base::apple::GetUserDocumentPath(); |
| int total_size_bytes = CalculateTotalSize(documents_path); |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.DocumentsSize2", |
| total_size_bytes / 1024 / 1024); |
| } |
| |
| // Logs the Favicons storage size. Accepts a task runner as a parameter in |
| // order to keep it in scope throughout the execution. |
| void LogFaviconsStorageSize(base::FilePath profile_path, |
| scoped_refptr<base::SequencedTaskRunner>) { |
| base::FilePath favicons_path = |
| profile_path.Append(history::kFaviconsFilename); |
| int64_t total_size_bytes = CalculateTotalSize(favicons_path); |
| UMA_HISTOGRAM_MEMORY_KB("IOS.SandboxMetrics.FaviconsSize", |
| total_size_bytes / 1024); |
| } |
| |
| // Logs the "Library" directory size. Accepts a task runner as a parameter in |
| // order to keep it in scope throughout the execution. |
| void LogLibraryDirectorySize(scoped_refptr<base::SequencedTaskRunner>) { |
| base::FilePath library_path = base::apple::GetUserLibraryPath(); |
| int total_size_bytes = CalculateTotalSize(library_path); |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.LibrarySize", |
| total_size_bytes / 1024 / 1024); |
| } |
| |
| // Logs the optimization guide model downloads directory size. Accepts a task |
| // runner as a parameter in order to keep it in scope throughout the execution. |
| void LogOptimizationGuideModelDownloadsMetrics( |
| scoped_refptr<base::SequencedTaskRunner>) { |
| int items = 0; |
| |
| base::FilePath models_dir = |
| base::PathService::CheckedGet(ios::DIR_USER_DATA) |
| .Append(optimization_guide::kOptimizationGuideModelStoreDirPrefix); |
| if (base::PathExists(models_dir)) { |
| int total_size_bytes = CalculateTotalSize(models_dir); |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB( |
| "IOS.SandboxMetrics.OptimizationGuideModelDownloadsSize", |
| total_size_bytes / 1024 / 1024); |
| |
| base::FileEnumerator enumerator(models_dir, /*recursive=*/false, |
| base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath path = enumerator.Next(); !path.empty(); |
| path = enumerator.Next()) { |
| items++; |
| } |
| } |
| |
| UMA_HISTOGRAM_COUNTS_1000( |
| "IOS.SandboxMetrics.OptimizationGuideModelDownloadedItems", items); |
| } |
| |
| // Logs the total amount of storage used by the regular and OTR tabs for both |
| // tab state and snapshots and the total size of the Application Support |
| // directory excluding the storage used by tabs. |
| void LogApplicationSupportDirectorySize( |
| base::FilePath profile_path, |
| base::FilePath otr_profile_path, |
| scoped_refptr<base::SequencedTaskRunner>) { |
| base::FilePath session_storage_dir = profile_path.Append(kSessionsPath); |
| int64_t regular_tabs_size_bytes = CalculateTotalSize(session_storage_dir); |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.TotalRegularSessionSize", |
| regular_tabs_size_bytes / 1024 / 1024); |
| |
| base::FilePath otr_storage_dir = otr_profile_path.Append(kSessionsPath); |
| int64_t otr_tabs_size_bytes = CalculateTotalSize(otr_storage_dir); |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.TotalOTRSessionSize", |
| otr_tabs_size_bytes / 1024 / 1024); |
| |
| int64_t tab_storage_size = regular_tabs_size_bytes + otr_tabs_size_bytes; |
| int64_t application_support_size = |
| CalculateTotalSize(GetApplicationSupportDirectory()); |
| int64_t size_without_tabs_data = application_support_size - tab_storage_size; |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.ApplicationSupportSize", |
| size_without_tabs_data / 1024 / 1024); |
| } |
| |
| void LogAverageSnapshotSizes(base::FilePath profile_path, |
| scoped_refptr<base::SequencedTaskRunner>) { |
| base::FilePath snapshots_storage_dir = profile_path.Append(kSnapshotsPath); |
| |
| DirectorySnapshotDetails grey_snapshot_details = |
| CalculateImageMetricsInRoot(/*grey_only=*/true, snapshots_storage_dir); |
| int64_t grey_average_bytes_size = 0; |
| if (grey_snapshot_details.snapshot_count > 0) { |
| grey_average_bytes_size = grey_snapshot_details.total_size_bytes / |
| grey_snapshot_details.snapshot_count; |
| } |
| UMA_HISTOGRAM_MEMORY_KB("IOS.SandboxMetrics.AverageGreySnapshotSize", |
| grey_average_bytes_size / 1024); |
| |
| DirectorySnapshotDetails all_snapshot_details = |
| CalculateImageMetricsInRoot(/*grey_only=*/false, snapshots_storage_dir); |
| int64_t color_total_size_bytes = all_snapshot_details.total_size_bytes - |
| grey_snapshot_details.total_size_bytes; |
| int color_snapshot_count = all_snapshot_details.snapshot_count - |
| grey_snapshot_details.snapshot_count; |
| int64_t color_average_bytes_size = 0; |
| if (color_snapshot_count > 0) { |
| color_average_bytes_size = color_total_size_bytes / color_snapshot_count; |
| } |
| UMA_HISTOGRAM_MEMORY_KB("IOS.SandboxMetrics.AverageColorSnapshotSize", |
| color_average_bytes_size / 1024); |
| } |
| |
| // Logs the WebKit tmp directory size and the size of the tmp directory |
| // excluding the WebKit directory. Accepts a task runner as a parameter in order |
| // to keep it in scope throughout the execution. |
| void LogTmpDirectorySizes(base::FilePath profile_path, |
| scoped_refptr<base::SequencedTaskRunner>) { |
| base::FilePath tmp_dir; |
| if (GetTempDir(&tmp_dir)) { |
| base::FilePath tmp_webkit_path = profile_path.Append(kWebKitTmpPath); |
| int webkit_tmp_size_bytes = CalculateTotalSize(tmp_webkit_path); |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.WebKitTempSize", |
| webkit_tmp_size_bytes / 1024 / 1024); |
| |
| int total_tmp_size_bytes = CalculateTotalSize(tmp_dir); |
| int tmp_without_webkit_data = total_tmp_size_bytes - webkit_tmp_size_bytes; |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.ChromeTempSize", |
| tmp_without_webkit_data / 1024 / 1024); |
| } |
| } |
| |
| // Logs the `kWebsiteLocalDataPath` directory size. Accepts a task runner as a |
| // parameter in order to keep it in scope throughout the execution. |
| void LogWebsiteLocalDataSize(base::FilePath profile_path, |
| scoped_refptr<base::SequencedTaskRunner>) { |
| base::FilePath library_path = base::apple::GetUserLibraryPath(); |
| base::FilePath website_data_dir = profile_path.Append(kWebsiteLocalDataPath); |
| int total_size_bytes = CalculateTotalSize(website_data_dir); |
| UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.WebsiteLocalData", |
| total_size_bytes / 1024 / 1024); |
| } |
| |
| void LogApplicationStorageMetrics(base::FilePath profile_path, |
| base::FilePath off_the_record_state_path) { |
| scoped_refptr<base::SequencedTaskRunner> task_runner = |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}); |
| |
| task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(&LogApplicationSupportDirectorySize, profile_path, |
| off_the_record_state_path, task_runner)); |
| task_runner->PostTask(FROM_HERE, base::BindOnce(&LogAverageSnapshotSizes, |
| profile_path, task_runner)); |
| task_runner->PostTask(FROM_HERE, |
| base::BindOnce(&LogCacheDirectorySizes, task_runner)); |
| task_runner->PostTask( |
| FROM_HERE, base::BindOnce(&LogDocumentsDirectorySize, task_runner)); |
| task_runner->PostTask(FROM_HERE, base::BindOnce(&LogFaviconsStorageSize, |
| profile_path, task_runner)); |
| task_runner->PostTask(FROM_HERE, |
| base::BindOnce(&LogLibraryDirectorySize, task_runner)); |
| task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(&LogOptimizationGuideModelDownloadsMetrics, task_runner)); |
| task_runner->PostTask(FROM_HERE, base::BindOnce(&LogTmpDirectorySizes, |
| profile_path, task_runner)); |
| task_runner->PostTask(FROM_HERE, base::BindOnce(&LogWebsiteLocalDataSize, |
| profile_path, task_runner)); |
| } |