시스템 상태 모니터링

워치독은 공급업체 서비스 및 VHAL 서비스의 상태를 모니터링하고 비정상 프로세스를 종료합니다. 비정상 프로세스가 종료되면 워치독은 다른 애플리케이션 응답 없음(ANR) 덤프에서와 마찬가지로 이 프로세스 상태를 /data/anr에 덤프합니다. 이렇게 하면 디버깅 프로세스가 용이해집니다.

공급업체 서비스 상태 모니터링

공급업체 서비스는 네이티브 측과 자바 측에서 모니터링됩니다. 공급업체 서비스를 모니터링하기 위해 서비스는 사전 정의된 제한 시간을 지정하여 워치독으로 상태 점검 프로세스를 등록해야 합니다. 워치독은 등록 중에 지정된 제한 시간을 기준한 간격으로 프로세스를 핑함으로써, 등록된 상태 점검 프로세스의 상태를 모니터링합니다. 핑된 프로세스가 제한 시간 내에 응답하지 않으면 프로세스는 비정상으로 간주됩니다.

기본 서비스 상태 모니터링

워치독 AIDL makefile 지정

  1. shared_libscarwatchdog_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 정책 추가

  1. SELinux 정책을 추가하려면 공급업체 서비스 도메인에서 바인더(binder_use 매크로)를 사용하고 공급업체 서비스 도메인을 carwatchdog 클라이언트 도메인(carwatchdog_client_domain 매크로)에 추가하도록 허용합니다. sample_client.tefile_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
    

BnCarWatchdogClient를 상속하여 클라이언트 클래스 구현

  1. checkIfAlive에서 상태 확인을 실행합니다. 한 가지 옵션은 스레드 루프 핸들러에 게시하는 것입니다. 정상인 경우 ICarWatchdog::tellClientAlive를 호출합니다. SampleNativeClient.hSampleNativeClient.cpp의 경우 아래 코드를 참고하세요.

    SampleNativeClient.h

    class SampleNativeClient : public BnCarWatchdogClient {
    public:
        ndk::ScopedAStatus checkIfAlive(int32_t sessionId, TimeoutLength
            timeout) override;
        ndk::ScopedAStatus prepareProcessTermination() override;
        void initialize();
    
    private:
        void respondToDaemon();
    private:
        ::android::sp<::android::Looper> mHandlerLooper;
        std::shared_ptr<ICarWatchdog> mWatchdogServer;
        std::shared_ptr<ICarWatchdogClient> mClient;
        int32_t mSessionId;
    };
    

    SampleNativeClient.cpp

    ndk::ScopedAStatus WatchdogClient::checkIfAlive(int32_t sessionId, TimeoutLength timeout) {
        mHandlerLooper->removeMessages(mMessageHandler,
            WHAT_CHECK_ALIVE);
        mSessionId = sessionId;
        mHandlerLooper->sendMessage(mMessageHandler,
            Message(WHAT_CHECK_ALIVE));
        return ndk::ScopedAStatus::ok();
    }
    // WHAT_CHECK_ALIVE triggers respondToDaemon from thread handler
    void WatchdogClient::respondToDaemon() {
      // your health checking method here
      ndk::ScopedAStatus status = mWatchdogServer->tellClientAlive(mClient,
            mSessionId);
    }
    

바인더 스레드 시작 및 클라이언트 등록

자동차 워치독 데몬 인터페이스 이름은 android.automotive.watchdog.ICarWatchdog/default입니다.

  1. 이름으로 데몬을 검색하고 ICarWatchdog::registerClient를 호출합니다. main.cppSampleNativeClient.cpp의 경우 아래 코드를 참고하세요.

    main.cpp

    int main(int argc, char** argv) {
        sp<Looper> looper(Looper::prepare(/*opts=*/0));
    
        ABinderProcess_setThreadPoolMaxThreadCount(1);
        ABinderProcess_startThreadPool();
        std::shared_ptr<SampleNativeClient> client =
            ndk::SharedRefBase::make<SampleNatvieClient>(looper);
    
        // The client is registered in initialize()
        client->initialize();
        ...
    }
    

    SampleNativeClient.cpp

    void SampleNativeClient::initialize() {
        ndk::SpAIBinder binder(AServiceManager_getService(
            "android.automotive.watchdog.ICarWatchdog/default"));
        std::shared_ptr<ICarWatchdog> server =
            ICarWatchdog::fromBinder(binder);
        mWatchdogServer = server;
        ndk::SpAIBinder binder = this->asBinder();
        std::shared_ptr<ICarWatchdogClient> client =
            ICarWatchdogClient::fromBinder(binder)
        mClient = client;
        server->registerClient(client, TimeoutLength::TIMEOUT_NORMAL);
    }
    

자바 서비스 상태 모니터링

CarWatchdogClientCallback을 상속하여 클라이언트 구현

  1. 다음과 같이 새 파일을 수정합니다.
    private final CarWatchdogClientCallback mClientCallback = new CarWatchdogClientCallback() {
        @Override
        public boolean onCheckHealthStatus(int sessionId, int timeout) {
            // Your health check logic here
            // Returning true implies the client is healthy
            // If false is returned, the client should call
            // CarWatchdogManager.tellClientAlive after health check is
            // completed
        }
    
        @Override
        public void onPrepareProcessTermination() {}
    };
    

클라이언트 등록

  1. CarWatchdogManager.registerClient() 호출:
    private void startClient() {
        CarWatchdogManager manager =
            (CarWatchdogManager) car.getCarManager(
            Car.CAR_WATCHDOG_SERVICE);
        // Choose a proper executor according to your health check method
        ExecutorService executor = Executors.newFixedThreadPool(1);
        manager.registerClient(executor, mClientCallback,
            CarWatchdogManager.TIMEOUT_NORMAL);
    }
    

클라이언트 등록 취소

  1. 서비스가 완료되면 CarWatchdogManager.unregisterClient()를 호출합니다.
    private void finishClient() {
        CarWatchdogManager manager =
            (CarWatchdogManager) car.getCarManager(
            Car.CAR_WATCHDOG_SERVICE);
        manager.unregisterClient(mClientCallback);
    }
    

VHAL 상태 모니터링

공급업체 서비스 상태 모니터링과 달리 워치독은 VHAL_HEARTBEAT 차량 속성을 구독하여 VHAL 서비스 상태를 모니터링합니다. 워치독은 이 속성 값이 N초마다 한 번 업데이트될 것으로 예상합니다. 이 제한 시간 내에 하트비트가 업데이트되지 않으면 워치독은 VHAL 서비스를 종료합니다.

참고: 워치독은 VHAL_HEARTBEAT 차량 속성이 VHAL 서비스에서 지원되는 경우에만 VHAL 서비스 상태를 모니터링합니다.

VHAL 내부 구현은 공급업체마다 다를 수 있습니다. 다음 코드 샘플을 참조로 사용할 수 있습니다.

  1. VHAL_HEARTBEAT 차량 속성을 등록합니다.

    VHAL 서비스를 시작할 때 VHAL_HEARTBEAT 차량 속성을 등록합니다. 아래 예에서는 속성 ID를 구성에 매핑하는 unordered_map가 모든 지원 구성을 저장하는 데 사용됩니다. VHAL_HEARTBEAT의 구성이 지도에 추가됩니다. 그래서 VHAL_HEARTBEAT가 쿼리되면 관련 구성이 반환됩니다.

    void registerVhalHeartbeatProperty() {
            const VehiclePropConfig config = {
                    .prop = toInt(VehicleProperty::VHAL_HEARTBEAT),
                    .access = VehiclePropertyAccess::READ,
                    .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
            };
           // mConfigsById is declared as std::unordered_map<int32_t, VehiclePropConfig>.
           mConfigsById[config.prop] = config;
    }
    
  2. VHAL_HEARTBEAT 차량 속성을 업데이트합니다.

    VHAL 상태 점검 빈도(VHAL 상태 점검의 빈도 정의)에 따라 VHAL_HEARTBEAT 차량 속성을 N초마다 한 번씩 업데이트합니다. 이를 위한 한 가지 방법은 RecurrentTimer를 사용하여 VHAL 상태를 확인하고 제한 시간 내에 VHAL_HEARTBEAT 차량 속성을 업데이트하는 작업을 호출하는 것입니다.

    다음은 RecurrentTimer를 사용하는 샘플 구현입니다.

    int main(int argc, char** argv) {
            RecurrentTimer recurrentTimer(updateVhalHeartbeat);
            recurrentTimer.registerRecurrentEvent(kHeartBeatIntervalNs,
                                               static_cast<int32_t>(VehicleProperty::VHAL_HEARTBEAT));
            … Run service …
            recurrentTimer.unregisterRecurrentEvent(
                    static_cast<int32_t>(VehicleProperty::VHAL_HEARTBEAT));
    }
    
    void updateVhalHeartbeat(const std::vector<int32_t>& cookies) {
           for (int32_t property : cookies) {
                  if (property != static_cast<int32_t>(VehicleProperty::VHAL_HEARTBEAT)) {
                         continue;
                  }
    
                  // Perform internal health checking such as retrieving a vehicle property to ensure
                  // the service is responsive.
                  doHealthCheck();
    
                  // Construct the VHAL_HEARTBEAT property with system uptime.
                  VehiclePropValuePool valuePool;
                  VehicleHal::VehiclePropValuePtr propValuePtr = valuePool.obtainInt64(uptimeMillis());
                  propValuePtr->prop = static_cast<int32_t>(VehicleProperty::VHAL_HEARTBEAT);
                  propValuePtr->areaId = 0;
                  propValuePtr->status = VehiclePropertyStatus::AVAILABLE;
                  propValuePtr->timestamp = elapsedRealtimeNano();
    
                  // Propagate the HAL event.
                  onHalEvent(std::move(propValuePtr));
           }
    }
    
  3. (선택사항) VHAL 상태 점검 빈도를 정의합니다.

    워치독의 ro.carwatchdog.vhal_healthcheck.interval 읽기 전용 제품 속성이 VHAL 상태 점검 빈도를 정의합니다. 기본 상태 점검 빈도(이 속성이 정의되지 않은 경우)는 3초입니다. VHAL 서비스가 VHAL_HEARTBEAT 차량 속성을 업데이트하는 데 3초가 충분하지 않으면 서비스 응답성에 따라 VHAL 상태 점검 빈도를 정의합니다.

워치독으로 종료된 비정상 프로세스 디버그

워치독은 프로세스 상태를 덤프하고 비정상 프로세스를 종료합니다. 비정상적인 프로세스를 종료할 때 워치독은 carwatchdog terminated <process name> (pid:<process id>) 텍스트를 logcat에 기록합니다. 이 로그 줄에서는 프로세스 이름 및 프로세스 ID와 같은 종료된 프로세스에 관한 정보를 제공합니다.

  1. 다음을 실행하여 logcat에서 앞서 언급한 텍스트를 검색할 수 있습니다.
    $ adb logcat -s CarServiceHelper | fgrep "carwatchdog killed"
    

    예를 들어, KitchenSink 앱이 등록된 워치독 클라이언트일 때 워치독 핑에 응답하지 않으면 워치독은 등록된 KitchenSink 프로세스를 종료할 때 아래 줄과 같은 줄을 기록합니다.

    05-01 09:50:19.683   578  5777 W CarServiceHelper: carwatchdog killed com.google.android.car.kitchensink (pid: 5574)
    
  2. 응답이 없는 근본 원인을 파악하려면 활동 ANR 사례에서와 마찬가지로 /data/anr에 저장된 프로세스 덤프를 사용합니다. 종료된 프로세스의 덤프 파일을 검색하려면 다음 명령어를 사용합니다.
    $ adb root
    $ adb shell grep -Hn "pid process_pid" /data/anr/*
    

    KitchinSink 앱에 관련된 샘플 출력은 다음과 같습니다.

    $ adb shell su root grep -Hn "pid 5574" /data/anr/*.
    
    /data/anr/anr_2020-05-01-09-50-18-290:3:----- pid 5574 at 2020-05-01 09:50:18 -----
    /data/anr/anr_2020-05-01-09-50-18-290:285:----- Waiting Channels: pid 5574 at 2020-05-01 09:50:18 -----
    

    종료된 KitchenSink 프로세스의 덤프 파일은 /data/anr/anr_2020-05-01-09-50-18-290에 있습니다. 종료된 프로세스의 ANR 덤프 파일을 사용하여 분석을 시작합니다.