Watchdog 通过跟踪所有应用和服务使用内核在 `/proc/uid_io/stats` 位置公开的每 UID 磁盘 I/O 统计信息所产生的磁盘 I/O 写入总量来监控闪存使用情况。当应用或服务超出磁盘 I/O 过度使用阈值时,Watchdog 会对该应用或服务采取措施。磁盘 I/O 过度使用阈值以及对过度使用采取的措施在磁盘 I/O 过度使用配置中预定义。
过度使用阈值
- 磁盘 I/O 过度使用阈值按天强制执行,即,自当前 UTC 日历日开始以来,应用/服务的所有写入都会被汇总,并对照过度使用配置中定义的阈值进行检查。
- 当车辆在给定的一天内多次启动时,Watchdog 模块会将磁盘 I/O 使用情况统计信息存储在闪存中,并自当前 UTC 日历日开始对其进行汇总。
过度使用措施
当应用反复超出定义的磁盘 I/O 过度使用阈值时,Watchdog 会采取过度使用配置中定义的措施。
- 所有供应商应用和服务都被视为对整体系统稳定性至关重要,因此它们不会因磁盘 I/O 过度使用而终止。但是,过度使用配置可以定义一个安全终止的供应商应用和服务列表。
- 所有第三方应用都是安全终止的。
当应用或服务可以安全终止时,Watchdog 会使用应用组件状态 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
停用该应用或服务。
过度使用配置
过度使用配置包含磁盘 I/O 过度使用阈值和措施。默认过度使用配置在系统和供应商映像中定义,并随 build 一起提供。供应商可以选择在供应商映像中包含供应商配置。当未提供供应商配置时,系统配置也用于供应商应用和服务。
Watchdog 通过 CarWatchdogManager
公开系统 API,这使供应商应用或服务可以随时更新供应商配置。
过度使用配置定义
过度使用配置按组件类型拆分,例如,系统、供应商和第三方。OEM 必须仅更新供应商组件配置。
供应商配置
供应商配置定义了所有供应商应用和服务以及所有地图和媒体应用的磁盘 I/O 过度使用阈值和措施。该配置包含以下配置字段。
- 供应商软件包前缀。安装在供应商分区中的所有软件包都被视为供应商软件包。除了这些软件包之外,供应商还可以通过将软件包前缀添加到供应商软件包前缀配置,将预安装的软件包归类为供应商软件包。此配置不接受正则表达式。
- 安全终止软件包。供应商可以指定哪些供应商软件包可以安全终止,方法是将完整的软件包名称添加到安全终止软件包配置。
- 应用类别映射。供应商可以将任何软件包(包括第三方软件包)映射到两个受支持的应用类别之一 - 地图和媒体应用。此映射的目的是为地图和媒体应用提供更高的磁盘 I/O 过度使用阈值,因为这些应用往往比其他应用类型下载和写入更多数据到磁盘。
- 组件级别阈值。定义所有供应商软件包的通用阈值(即,软件包特定阈值或应用类别特定阈值未涵盖的软件包将获得这些阈值)。在定义磁盘 I/O 过度使用配置时,供应商必须定义非零组件级别阈值。
- 软件包特定阈值。供应商可以为特定的供应商软件包定义特殊的阈值。映射应包含完整的软件包名称。在此配置中定义的阈值优先于给定软件包的其他配置中定义的阈值。
- 应用类别特定阈值。供应商可以为特定的应用类别指定特殊的阈值。应用类别必须是受支持的类别之一 - 地图和媒体应用。在此配置中定义的阈值使用应用类别映射映射到特定的软件包。
- 系统范围阈值。供应商不得指定此配置。
供应商软件包前缀、安全终止软件包、组件级别阈值和软件包特定阈值配置只能由供应商配置针对供应商应用和服务进行更新。应用类别特定阈值配置只能由供应商配置针对所有地图和媒体应用进行更新。
过度使用阈值包含在以下情况下允许写入的字节数:
- 应用或服务前台模式与后台模式
- 系统车库模式
此分类允许面向用户的前台应用和服务写入比后台应用和服务更多的数据。在车库模式下,应用和服务倾向于下载更新,因此每个应用和服务都需要比在其他模式下运行的应用和服务更高的阈值。
系统和第三方配置
OEM 厂商不应更新系统和第三方配置。
- 系统配置定义了系统应用和服务的 I/O 过度使用阈值和措施。
- 此配置还可以更新应用类别映射。因此,此配置字段在系统配置和供应商配置之间共享。
- 第三方配置定义了所有第三方应用的阈值。所有未预安装在系统中的应用都是第三方应用。
- 除了地图和媒体应用(其阈值由供应商配置定义)之外,所有第三方应用都接收相同的阈值(例如,没有第三方应用接收特殊阈值)。
- 以下磁盘 I/O 过度使用阈值是第三方应用的默认阈值。这些阈值随系统映像一起提供。
- 应用前台模式下写入 3 GiB。
- 应用后台模式下写入 2 GiB。
- 系统车库模式下写入 4 GiB。
- 这些是基本阈值。随着对磁盘 I/O 使用情况的了解越来越多,这些阈值会进行更新。
过度使用配置 XML 格式
默认供应商配置可以放置在 build 映像中的 /vendor/etc/automotive/watchdog/resource_overuse_configuration.xml
位置(这是可选的)。当未指定此配置时,系统定义的配置也适用于供应商应用和服务。
XML 文件对于每个配置字段应仅包含一个标记。I/O 过度使用配置必须在 XML 文件中定义。所有阈值都应以 MiB 为单位指定。
下面提供了 XML 配置示例
<resourceOveruseConfiguration version="1.0"> <componentType> VENDOR </componentType> <!-- List of safe to kill vendor packages. --> <safeToKillPackages> <package> com.vendor.package.A </package> <package> com.vendor.package.B </package> </safeToKillPackages> <!-- List of vendor package prefixes. --> <vendorPackagePrefixes> <packagePrefix> com.vendor.package </packagePrefix> </vendorPackagePrefixes> <!-- List of unique package names to app category mappings. --> <packagesToAppCategoryTypes> <packageAppCategory type="MEDIA"> com.vendor.package.A </packageAppCategory> <packageAppCategory type="MAPS"> com.google.package.B </packageAppCategory> <packageAppCategory type="MEDIA"> com.third.party.package.C </packageAppCategory> </packagesToAppCategoryTypes> <ioOveruseConfiguration> <!-- Thresholds in MiB for all vendor packages that don't have package specific thresholds. --> <componentLevelThresholds> <state id="foreground_mode"> 1024 </state> <state id="background_mode"> 512 </state> <state id="garage_mode"> 3072 </state> </componentLevelThresholds> <packageSpecificThresholds> <!-- IDs must be unique --> <perStateThreshold id="com.vendor.package.C"> <state id="foreground_mode"> 400 </state> <state id="background_mode"> 100 </state> <state id="garage_mode"> 200 </state> </perStateThreshold> <perStateThreshold id="com.vendor.package.D"> <state id="foreground_mode"> 1024 </state> <state id="background_mode"> 500 </state> <state id="garage_mode"> 2048 </state> </perStateThreshold> </packageSpecificThresholds> <!-- Application category specific thresholds. --> <appCategorySpecificThresholds> <!-- One entry per supported application category --> <perStateThreshold id="MEDIA"> <state id="foreground_mode"> 600 </state> <state id="background_mode"> 700 </state> <state id="garage_mode"> 1024 </state> </perStateThreshold> <perStateThreshold id="MAPS"> <state id="foreground_mode"> 800 </state> <state id="background_mode"> 900 </state> <state id="garage_mode"> 2048 </state> </perStateThreshold> </appCategorySpecificThresholds> </ioOveruseConfiguration> </resourceOveruseConfiguration>
通过 CarWatchdogManager 系统 API 更新过度使用配置
以上 XML 配置只能在 build 映像中提供。如果 OEM 选择在发布 build 后更新设备上的配置,则可以使用以下 API 对设备上的配置进行更改。
- 向调用方授予
Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG
权限。 - 必须使用现有配置来更新和设置新配置。使用 API
CarWatchdogManager.getResourceOveruseConfigurations
获取现有配置。如果未使用现有配置,则所有配置(包括系统和第三方配置)都将被覆盖,这是不建议的。 - 使用增量更改更新现有配置,并设置新配置。请勿更新系统和第三方组件配置。
- 使用 API
CarWatchdogManager.setResourceOveruseConfigurations
设置新配置。 - 要获取和设置磁盘 I/O 过度使用配置,请使用标志
CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO
。
以下是一个更新资源过度使用配置的示例实现
void updateResourceOveruseConfigurations() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); List<ResourceOveruseConfiguration> resourceOveruseConfigurations = manager.getResourceOveruseConfigurations( CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO); List<ResourceOveruseConfiguration> newResourceOveruseConfigurations = new List<>(); ResourceOveruseConfiguration vendorConfiguration; for(ResourceOveruseConfiguration config : resourceOveruseConfigurations) { // Do not update the configurations of the system and third-party component types. if (config.getComponentType() != ResourceOveruseConfiguration.COMPONENT_TYPE_VENDOR) { newResourceOveruseConfigurations.add(config); continue; } vendorConfiguration = config; } if (vendorConfiguration == null) { ResourceOveruseConfiguration.Builder vendorConfigBuilder = new ResourceOveruseConfiguration.Builder(); initializeConfig(vendorConfigBuilder); newResourceOveruseConfigurations.add(vendorConfigBuilder.build()); } else { ResourceOveruseConfiguration newVendorConfig = updateConfig(vendorConfiguration); newResourceOveruseConfigurations.add(newVendorConfig); } int result = manager.setResourceOveruseConfigurations( newResourceOveruseConfigurations, if (result != CarWatchdogManager.RETURN_CODE_SUCCESS) { // Failed to set the resource overuse configurations. } } /** Sets the delta between the old configuration and the new configuration. */ ResourceOveruseConfiguration updateConfig( ResourceOveruseConfiguration oldConfiguration) { // Replace com.vendor.package.A with com.vendor.package.B in the safe-to-kill list. List<String> safeToKillPackages = oldConfiguration.getSafeToKillPackages(); safeToKillPackages.remove("com.vendor.package.A"); safeToKillPackages.add("com.vendor.package.B"); ResourceOveruseConfiguration.Builder configBuilder = new ResourceOveruseConfiguration.Builder( oldConfiguration.getComponentType(), safeToKillPackages, oldConfiguration.getVendorPackagePrefixes(), oldConfiguration.getPackagesToAppCategoryTypes()); configBuilder.addVendorPackagePrefixes("com.vendor."); configBuilder.addPackagesToAppCategoryTypes("com.vendor.package.B", ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS); IoOveruseConfiguration oldIoConfiguration = oldConfiguration.getIoOveruseConfiguration(); IoOveruseConfiguration.Builder ioConfigBuilder = new IoOveruseConfiguration.Builder( oldIoConfiguration.getComponentLevelThresholds(), oldIoConfiguration.getPackageSpecificThresholds(), oldIoConfiguration.getAppCategorySpecificThresholds(), oldIoConfiguration.getSystemWideThresholds()); // Define the amount of bytes based on the flash memory specification, expected lifetime, // and estimated average amount of bytes written by a package during different modes. ioConfigBuilder.addPackageSpecificThresholds("com.vendor.package.B", new PerStateBytes(/* foregroundModeBytes= */ 2 * 1024 * 1024 * 1024, /* backgroundModeBytes= */ 500 * 1024 * 1024, /* garageModeBytes= */ 3 * 1024 * 1024 * 1024)); return configBuilder.setIoOveruseConfiguration(ioConfigBuilder.build()).build(); }
应用监控其资源过度使用情况
供应商和第三方应用可以侦听来自 Watchdog 的应用特定资源过度使用通知,或者轮询 CarWatchdogManager
以获取应用特定资源过度使用统计信息,最多可获取过去 30 天的数据。
侦听资源过度使用通知
应用可以实现资源过度使用监听器,并在 CarWatchdogManager
中注册该监听器,以便在应用超出其磁盘 I/O 过度使用阈值的 80% 或 100% 时接收应用特定通知。应用可以使用这些通知来:
- 记录磁盘 I/O 过度使用统计信息以进行离线分析。应用开发者可以使用此日志记录来调试磁盘 I/O 过度使用问题。
- 减少磁盘 I/O 写入,直到过度使用计数器重置。
Java 客户端
- 通过继承
CarWatchdogManager.ResourceOveruseListener
来实现监听器class ResourceOveruseListenerImpl implements CarWatchdogManager.ResourceOveruseListener { @Override public void onOveruse( @NonNull ResourceOveruseStats resourceOveruseStats) { // 1. Log/Upload resource overuse metrics. // 2. Reduce writes until the counters reset. IoOveruseStats ioOveruseStats = resourceOveruseStats.getIoOveruseStats(); // Stats period - [ioOveruseStats.getStartTime(), ioOveruseStats.getStartTime() // + ioOveruseStats.getDurationInSeconds()] // Total I/O overuses - ioOveruseStats.getTotalOveruses() // Total bytes written - ioOveruseStats.getTotalBytesWritten() // Remaining write bytes for the current UTC calendar day - // ioOveruseStats.getRemainingWriteBytes() } } }
- 通过调用
CarWatchdogManager.addResourceOveruseListener
来注册监听器实例private void addResourceOveruseListener() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); // Choose a proper executor to handle resource overuse notifications. Executor executor = mContext.getMainExecutor(); manager.addResourceOveruseListener( executor, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, mListenerImpl); }
- 当应用完成侦听时,取消注册监听器实例
private void removeResourceOveruseListener() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); mCarWatchdogManager.removeResourceOveruseListener( mListenerImpl); }
原生客户端
- 在 build 规则的
shared_libs
依赖项中包含carwatchdog_aidl_interface-ndk_platform
。Android.bp
cc_binary { name: "sample_native_client", srcs: [ "src/*.cpp" ], shared_libs: [ "carwatchdog_aidl_interface-ndk_platform", "libbinder_ndk", ], vendor: true, }
- 添加 SELinux 策略以允许供应商服务域使用 binder (
binder_user
宏),并将供应商服务域添加到carwatchdog
客户端域 (carwatchdog_client_domain 宏
)。有关sample_client.te
和file_contexts
,请参见以下代码。sample_client.te
type sample_client, domain; type sample_client_exec, exec_type, file_type, vendor_file_type; carwatchdog_client_domain(sample_client) init_daemon_domain(sample_client) binder_use(sample_client)
file_contexts
/vendor/bin/sample_native_client u:object_r:sample_client_exec:s0
- 通过继承
BnResourceOveruseListener
来实现资源过度使用监听器。替换BnResourceOveruseListener::onOveruse
以处理资源过度使用通知。ResourceOveruseListenerImpl.h
class ResourceOveruseListenerImpl : public BnResourceOveruseListener { public: ndk::ScopedAStatus onOveruse( ResourceOveruseStats resourceOveruseStats) override; private: void initialize(); void terminate(); std::shared_ptr<ICarWatchdog> mWatchdogServer; std::shared_ptr<IResourceOveruseListener> mListener; }
ResourceOveruseListenerImpl.cpp
ndk::ScopedAStatus ResourceOveruseListenerImpl::onOveruse( ResourceOveruseStats resourceOveruseStats) { // 1. Log/Upload resource overuse metrics. // 2. Reduce writes until the counters reset. if (stats.getTag() != ResourceOveruseStats::ioOveruseStats) { // Received resourceOveruseStats doesn't contain I/O overuse stats. } const IoOveruseStats& ioOveruseStats = stats.get(); // Stats period - [ioOveruseStats.startTime, // ioOveruseStats.startTime + ioOveruseStats.durationInSeconds] // Total I/O overuses - ioOveruseStats.totalOveruses // Total bytes written - ioOveruseStats.writtenBytes // Remaining write bytes for the current UTC calendar day - // ioOveruseStats.remainingWriteBytes return ndk::ScopedAStatus::ok(); }
- 启动 binder 线程池,并在 watchdog 服务器中注册资源过度使用监听器。Watchdog 服务器在服务名称
android.automotive.watchdog.ICarWatchdog/default
下注册。main.cpp
int main(int argc, char** argv) { ABinderProcess_setThreadPoolMaxThreadCount(1); ABinderProcess_startThreadPool(); std::shared_ptr<ResourceOveruseListenerImpl> listener = ndk::SharedRefBase::make<ResourceOveruseListenerImpl>(); // The listener is added in initialize(). listener->initialize(); ... Run service ... // The listener is removed in terminate(). listener->terminate(); }
ResourceOveruseListenerImpl.cpp
void ResourceOveruseListener::initialize() { ndk::SpAIBinder binder(AServiceManager_getService( "android.automotive.watchdog.ICarWatchdog/default")); std::shared_ptr<ICarWatchdog> server = ICarWatchdog::fromBinder(binder); mWatchdogServer = server; std::shared_ptr<IResourceOveruseListener> listener = IResourceOveruseListener::fromBinder(this->asBinder()); mWatchdogServer->addResourceOveruseListener( std::vector<int>{ResourceType.IO}, listener); mListener = listener; } void ResourceOveruseListener::terminate() { mWatchdogServer->removeResourceOveruseListener(mListener); }
轮询资源过度使用统计信息
应用可以轮询 CarWatchdogManager 以获取最近 30 天的应用特定 I/O 过度使用统计信息 ATS。
Java 客户端
使用 CarWatchdogManager.getResourceOveruseStats
获取资源过度使用统计信息。传递 CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO
标志以获取磁盘 I/O 过度使用统计信息。
private void getResourceOveruseStats() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); // Returns resource overuse stats with I/O overuse stats for the past // 7 days. Stats are available for up to the past 30 days. ResourceOveruseStats resourceOveruseStats = mCarWatchdogManager.getResourceOveruseStats( CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, CarWatchdogManager.STATS_PERIOD_PAST_7_DAYS); IoOveruseStats ioOveruseStats = resourceOveruseStats.getIoOveruseStats(); // Stats period - [ioOveruseStats.getStartTime(), ioOveruseStats.getStartTime() // + ioOveruseStats.getDurationInSeconds()] // Total I/O overuses - ioOveruseStats.getTotalOveruses() // Total bytes written - ioOveruseStats.getTotalBytesWritten() // Remaining write bytes for the UTC calendar day - // ioOveruseStats.getRemainingWriteBytes() }
原生客户端
使用 CarWatchdogServer.getResourceOveruseStats
获取资源过度使用统计信息。传递 ResourceType.IO
枚举以获取磁盘 I/O 过度使用统计信息。
void getResourceOveruseStats() { ndk::SpAIBinder binder(AServiceManager_getService( "android.automotive.watchdog.ICarWatchdog/default")); std::shared_ptr<ICarWatchdog> server = ICarWatchdog::fromBinder(binder); // Returns the stats only for the current UTC calendar day. const std::vector<ResourceOveruseStats> resourceOveruseStats; ndk::ScopedAStatus status = server.getResourceOveruseStats( std::vector<int>{ResourceType.IO}, &resourceOveruseStats); if (!status.isOk()) { // Failed to get the resource overuse stats. return; } for (const auto& stats : resourceOveruseStats) { if (stats.getTag() != ResourceOveruseStats::ioOveruseStats) { continue; } const IoOveruseStats& ioOveruseStats = stats.get(); // Stats period - [ioOveruseStats.startTime, // ioOveruseStats.startTime + ioOveruseStats.durationInSeconds] // Total I/O overuses - ioOveruseStats.totalOveruses // Total bytes written - ioOveruseStats.writtenBytes // Remaining write bytes for the current UTC calendar day - // ioOveruseStats.remainingWriteBytes } }
资源过度使用用户体验
以下部分介绍了发生资源过度使用时的用户体验。
优先考虑应用性能设置
应用的设置页面包含 Prioritize app performance
(请参见下图)的设置,用户可以通过该设置优先考虑应用的性能,而不是系统和长期硬件性能。此设置仅适用于可以安全终止资源过度使用的应用。否则,此设置将被停用。当应用的此设置切换为关闭(默认设置)时,应用可能会因资源过度使用而被终止。否则,应用不会因资源过度使用而被终止。
当用户切换开启此设置时,以下确认对话框描述了切换开启此设置的含义
90 天后,此设置将自动重置为默认值。天数限制可以使用 RRO 叠加应用通过 watchdogUserPackageSettingsResetDays
进行修改,最多可修改为 180 天。要了解更多信息,请参阅在运行时更改应用资源的值。以下示例叠加标记可以包含在 AndroidManifest.xml
中
<overlay android:priority="<insert-value>" android:targetPackage="com.android.car.updatable" android:targetName="CarServiceCustomization" android:resourcesMap="@xml/overlays" />
在 res/values/config.xml
中
<resources> <integer name="watchdogUserPackageSettingsResetDays">value</integer> </resources>
在 res/xml/overlays.xml
中
<overlay> <item target="integer/watchdogUserPackageSettingsResetDays" value="@integer/watchdogUserPackageSettingsResetDays" /> </overlay>
影响性能的应用设置
设置应用包含影响性能的应用部分(请参见图 1)。点击后,将显示由于闪存过度使用而受到限制并对系统性能产生负面影响的应用列表。这遵循CDD 3.5.1 要求 [C-1-1]。
图 1. 影响性能的应用。
此处列出了因资源过度使用而终止的应用(请参见图 2)。可以优先考虑列出的应用。要了解更多信息,请参阅优先考虑应用性能设置。
图 2. 因资源过度使用而终止的应用列表。
用户通知
当应用或服务在一定时期内反复过度使用磁盘 I/O(例如,写入磁盘的数据超出定义的阈值)并且可以安全终止资源过度使用时,在车辆进入允许驾驶员分心状态后,用户会收到通知。
第一个用户通知(在驾驶过程中)作为平视通知发布,其他通知在通知中心发布。
例如,当应用反复过度使用磁盘 I/O 时,用户会收到以下通知:
- 当用户点击优先考虑应用按钮时,将启动应用的设置页面,用户可以在该页面上切换开启或关闭优先考虑应用性能设置。
- 当用户点击停用应用按钮时,应用将被停用,直到用户启动该应用或在应用的设置页面上启用该应用。
- 对于不可卸载的应用,停用应用按钮将替换为卸载应用按钮。当用户点击卸载应用按钮时,将启动应用的“设置”页面,用户可以从中卸载该应用。
启动器实现建议
当应用因资源过度使用而被停用时,这些应用会从默认启动器应用中消失,因为 CarService 会将应用的启用状态更新为 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
。OEM 厂商必须更新内置启动器实现以不寻常的方式显示这些应用,以便用户可以在需要时使用它们。请参阅以下基于 build 版本的建议。
Android SC V2 版本
- 启动器实现应在检索要在启动器上显示的软件包列表时使用
MATCH_DISABLED_UNTIL_USED_COMPONENTS
标志。 - 当用户点击处于
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
状态的应用时,启动器应用必须通过将启用状态设置为以下值来启用该应用: