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 過度使用閾值和操作。預設過度使用配置在系統和供應商映像中定義,並隨建置一起提供。供應商可以選擇在供應商映像中包含供應商配置。如果未提供供應商配置,系統配置也將用於供應商應用程式和服務。
Watchdog 透過CarWatchdogManager
公開系統 API,讓供應商應用程式或服務隨時更新供應商配置。
過度使用配置定義
過度使用配置按元件類型劃分,例如係統、供應商和第三方。 OEM 必須僅更新供應商組件配置。
供應商配置
供應商配置定義所有供應商應用程式和服務以及所有地圖和媒體應用程式的磁碟 I/O 過度使用閾值和操作。此配置包含以下配置欄位。
-
Vendor package prefixes
。所有安裝在供應商分區中的軟體包都被視為供應商軟體包。除了這些軟體包之外,供應商還可以透過將軟體包前綴添加到供應商軟體包前綴配置中,將預先安裝的軟體包分類為vendor package prefixes
。此配置不接受正規表示式。 -
Safe-to-terminate packages
。供應商可以透過將完整的包名稱新增至safe-to-terminate packages
配置來指定哪些供應商包可以安全終止。 -
Application category mappings
。供應商可以將任何套件(包括第三方包)映射到兩個受支援的應用程式類別之一 - 地圖和媒體應用程式。完成此映射的目的是為地圖和媒體應用程式提供更高的磁碟 I/O 過度使用閾值,因為這些應用程式往往比其他應用程式類型下載和寫入更多資料到磁碟。 -
Component level thresholds
。定義所有供應商軟體包的通用閾值(即,Package specific thresholds
或Application category specific thresholds
未涵蓋的軟體包將獲得這些閾值)。供應商在定義磁碟 I/O 過度使用配置時必須定義非零組件級閾值。 -
Package specific thresholds
。供應商可以為特定供應商包定義特殊閾值。映射應包含完整的套件名稱。此配置中定義的閾值優先於給定套件的其他配置中定義的閾值。 -
Application category specific thresholds
。供應商可以為特定應用類別指定特殊閾值。應用程式類別必須是受支援的類別之一 - 地圖和媒體應用程式。此配置中定義的閾值使用Application category mappings
映射到特定套件。 -
System-wide thresholds
。供應商不得指定此配置。
Vendor package prefixes
、 Safe-to-terminate packages
、 Component level thresholds
和Package specific thresholds
配置只能由供應商應用程式和服務的供應商配置進行更新。 Application category specific thresholds
配置只能由所有地圖和媒體應用程式的供應商配置進行更新。
過度使用閾值包含在期間允許寫入的位元組數
- 應用程式/服務前台模式與背景模式
- 和系統車庫模式。
此分類允許面向前台應用程式/服務的使用者比後台應用程式/服務寫入更多資料。在車庫模式下,應用程式和服務傾向於下載更新,因此每個應用程式和服務都需要比在其他模式下運行的應用程式和服務更高的閾值。
系統和第三方配置
OEM不應更新系統和第三方配置。
- 系統配置定義系統應用程式和服務的 I/O 過度使用閾值和操作。
- 此配置還可以更新
Application category mappings
。因此,此配置欄位在系統和供應商配置之間共用。
- 此配置還可以更新
- 第三方配置定義所有第三方應用程式的閾值。所有系統中未預先安裝的應用程式都是第三方應用程式。
- 所有第三方應用程式都會收到相同的閾值(例如,沒有第三方應用程式會收到特殊閾值),但地圖和媒體應用程式除外,其閾值由供應商配置定義。
- 以下磁碟 I/O 過度使用閾值是第三方應用程式的預設閾值。這些閾值隨系統映像一起提供。
- 在應用程式前台模式下寫入 3 GiB。
- 在應用程式背景模式下寫入 2 GiB。
- 以系統車庫模式寫入 4 GiB。
- 這些是基本閾值。當我們更了解磁碟 I/O 使用情況時,這些閾值就會更新。
過度使用配置 XML 格式
預設供應商配置可以放置在建置映像中的/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 配置只能在建置映像中提供。如果 OEM 選擇在版本發布後更新設備上配置,他們可以使用以下 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); }
原生客戶端
- 將
carwatchdog_aidl_interface-ndk_platform
包含在建置規則的shared_libs
相依性中。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 macro)
。請參閱下面的程式碼以了解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 伺服器以服務名稱
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>
用戶通知
當應用程式或服務在一定時間內反覆過度使用磁碟I/O(例如,將資料寫入磁碟超出定義的閾值)並且可以安全地因資源過度使用而終止時,車輛進入允許驅動程式後,使用者會收到通知-分心狀態。
第一個使用者通知(在駕駛期間)作為提醒通知發布,其他通知發佈在通知中心。
例如,當應用程式重複過度使用磁碟 I/O 時,使用者會收到以下通知:
- 當使用者點擊優先應用程式按鈕時,將啟動應用程式的設定頁面,使用者可以在其中開啟或關閉優先應用程式效能設定。
- 當使用者點擊停用應用程式按鈕時,應用程式將被停用,直到使用者啟動應用程式或在應用程式的設定頁面上啟用它。
- 對於可卸載的應用程序, “禁用應用程式”按鈕將替換為“卸載應用程式”按鈕。當使用者點擊「卸載應用程式」按鈕時,將啟動應用程式的「設定」頁面,使用者可以從中卸載應用程式。
啟動器實施建議
當應用程式因資源過度使用而停用時,應用程式會從預設啟動器應用程式中消失,因為 CarService 將應用程式的啟用狀態更新為PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
。 OEM 必須更新內建啟動器實現,以將這些應用程式顯示為異常,以便使用者可以在需要時使用它們。請參閱以下基於建置版本的建議。
Android SC V2 發布
- 在擷取要在啟動器上顯示的套件清單時,啟動器實作應使用
MATCH_DISABLED_UNTIL_USED_COMPONENTS
標誌。 - 當使用者點擊處於
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
狀態的應用程式時,啟動器應用程式必須透過將啟用狀態設為啟用應用程式: