Add EvsStats to evs_app to compute camera latency
EvsStats computes e2e camera->display latency of the
first frame.
Test: mmm -j packages/services/Car/cpp/evs/apps/default
Test: Tested on seahawk by installing evs_app and enabling EVS
and running the app. Verified that ICarTelemetry receives
the data.
Bug: 173158650
Change-Id: Iec4eee04193a379e77deee7167de73d71b0c3d85
diff --git a/cpp/evs/apps/default/Android.bp b/cpp/evs/apps/default/Android.bp
index 3b393e1..4b1fadf 100644
--- a/cpp/evs/apps/default/Android.bp
+++ b/cpp/evs/apps/default/Android.bp
@@ -25,6 +25,7 @@
srcs: [
"evs_app.cpp",
"EvsStateControl.cpp",
+ "EvsStats.cpp",
"RenderBase.cpp",
"RenderDirectView.cpp",
"RenderTopView.cpp",
@@ -50,10 +51,13 @@
"libhardware",
"libpng",
"libcamera_metadata",
+ "android.frameworks.automotive.telemetry-V1-ndk_platform",
"android.hardware.camera.device@3.2",
"android.hardware.automotive.evs@1.0",
"android.hardware.automotive.evs@1.1",
"android.hardware.automotive.vehicle@2.0",
+ "libcartelemetry-evs-proto",
+ "libprotobuf-cpp-lite",
],
static_libs: [
@@ -81,6 +85,20 @@
}
+cc_library {
+ name: "libcartelemetry-evs-proto",
+ srcs: [
+ ":cartelemetry-evs-proto-srcs",
+ ],
+ proto: {
+ export_proto_headers: true,
+ type: "lite",
+ },
+ shared_libs: [
+ "libprotobuf-cpp-lite",
+ ],
+}
+
prebuilt_etc {
name: "config.json",
diff --git a/cpp/evs/apps/default/EvsStateControl.cpp b/cpp/evs/apps/default/EvsStateControl.cpp
index e3d9757..cb5faf1 100644
--- a/cpp/evs/apps/default/EvsStateControl.cpp
+++ b/cpp/evs/apps/default/EvsStateControl.cpp
@@ -44,17 +44,14 @@
& static_cast<int32_t>(VehiclePropertyType::MASK));
}
-
-EvsStateControl::EvsStateControl(android::sp <IVehicle> pVnet,
- android::sp <IEvsEnumerator> pEvs,
- android::sp <IEvsDisplay> pDisplay,
- const ConfigManager& config) :
- mVehicle(pVnet),
- mEvs(pEvs),
- mDisplay(pDisplay),
- mConfig(config),
- mCurrentState(OFF) {
-
+EvsStateControl::EvsStateControl(android::sp<IVehicle> pVnet, android::sp<IEvsEnumerator> pEvs,
+ android::sp<IEvsDisplay> pDisplay, const ConfigManager& config) :
+ mVehicle(pVnet),
+ mEvs(pEvs),
+ mDisplay(pDisplay),
+ mConfig(config),
+ mCurrentState(OFF),
+ mEvsStats(EvsStats::build()) {
// Initialize the property value containers we'll be updating (they'll be zeroed by default)
static_assert(getPropType(VehicleProperty::GEAR_SELECTION) == VehiclePropertyType::INT32,
"Unexpected type for GEAR_SELECTION property");
@@ -114,7 +111,6 @@
LOG(DEBUG) << "State controller ready";
}
-
bool EvsStateControl::startUpdateLoop() {
// Create the thread and report success if it gets started
mRenderThread = std::thread([this](){ updateLoop(); });
@@ -198,6 +194,12 @@
// Send the finished image back for display
mDisplay->returnTargetBufferForDisplay(tgtBuffer);
+
+ if (!mFirstFrameIsDisplayed) {
+ mFirstFrameIsDisplayed = true;
+ // returnTargetBufferForDisplay() is finished, the frame should be displayed
+ mEvsStats.finishComputingFirstFrameLatency(android::uptimeMillis());
+ }
}
} else if (run) {
// No active renderer, so sleep until somebody wakes us with another command
@@ -214,6 +216,9 @@
mCurrentRenderer->deactivate();
}
+ // If `ICarTelemetry` service was not ready before, we need to try sending data again.
+ mEvsStats.sendCollectedDataBlocking();
+
printf("Shutting down app due to state control loop ending\n");
LOG(ERROR) << "Shutting down app due to state control loop ending";
}
@@ -295,6 +300,9 @@
return true;
}
+ // Used by CarStats to accurately compute stats, it needs to be close to the beginning.
+ auto desiredStateTimeMillis = android::uptimeMillis();
+
LOG(DEBUG) << "Switching to state " << desiredState;
LOG(DEBUG) << " Current state " << mCurrentState
<< " has " << mCameraList[mCurrentState].size() << " cameras";
@@ -382,5 +390,12 @@
LOG(INFO) << "Activated state " << desiredState;
mCurrentState = desiredState;
+ mFirstFrameIsDisplayed = false; // Got a new renderer, mark first frame is not displayed.
+
+ if (mCurrentRenderer != nullptr && desiredState == State::REVERSE) {
+ // Start computing the latency when the evs state changes.
+ mEvsStats.startComputingFirstFrameLatency(desiredStateTimeMillis);
+ }
+
return true;
}
diff --git a/cpp/evs/apps/default/EvsStateControl.h b/cpp/evs/apps/default/EvsStateControl.h
index 1953906..d847195 100644
--- a/cpp/evs/apps/default/EvsStateControl.h
+++ b/cpp/evs/apps/default/EvsStateControl.h
@@ -17,18 +17,18 @@
#ifndef CAR_EVS_APP_EVSSTATECONTROL_H
#define CAR_EVS_APP_EVSSTATECONTROL_H
-#include "StreamHandler.h"
#include "ConfigManager.h"
+#include "EvsStats.h"
#include "RenderBase.h"
+#include "StreamHandler.h"
-#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
-#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
-#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
#include <thread>
-
using namespace ::android::hardware::automotive::evs::V1_1;
using namespace ::android::hardware::automotive::vehicle::V2_0;
using ::android::hardware::Return;
@@ -112,6 +112,12 @@
std::mutex mLock;
std::condition_variable mWakeSignal;
std::queue<Command> mCommandQueue;
+
+ EvsStats mEvsStats; // Not thread-safe
+
+ // True if the first frame displayed on the mCurrentRenderer. Resets to false when
+ // mCurrentRenderer changes.
+ bool mFirstFrameIsDisplayed;
};
diff --git a/cpp/evs/apps/default/EvsStats.cpp b/cpp/evs/apps/default/EvsStats.cpp
new file mode 100644
index 0000000..f43ae75
--- /dev/null
+++ b/cpp/evs/apps/default/EvsStats.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "EvsStats.h"
+
+#include "packages/services/Car/cpp/telemetry/proto/evs.pb.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <utils/SystemClock.h>
+
+#include <vector>
+
+namespace {
+
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::ICarTelemetry;
+using ::android::automotive::telemetry::EvsFirstFrameLatency;
+
+// Name of ICarTelemetry service that consumes RVC latency metrics.
+constexpr const char kCarTelemetryServiceName[] =
+ "android.frameworks.automotive.telemetry.ICarTelemetry/default";
+
+const int kCollectedDataSizeLimit = 200; // arbitrary chosen
+// 10kb limit is imposed by ICarTelemetry, the limit if only for
+// CarData.content vector.
+const int kCarTelemetryMaxDataSizePerWrite = 10 * 1024;
+
+// Defined in packages/services/Car/cpp/telemetry/proto/CarData.proto
+const int kEvsFirstFrameLatencyId = 1;
+} // namespace
+
+// static
+EvsStats EvsStats::build() {
+ // No need to enable stats if ICarTelemetry is not available.
+ bool enabled = ::AServiceManager_isDeclared(kCarTelemetryServiceName);
+ return EvsStats(enabled);
+}
+
+void EvsStats::startComputingFirstFrameLatency(int64_t startTimeMillis) {
+ mFirstFrameLatencyStartTimeMillis = startTimeMillis;
+}
+
+void EvsStats::finishComputingFirstFrameLatency(int64_t finishTimeMillis) {
+ if (!mEnabled) {
+ return;
+ }
+ if (mFirstFrameLatencyStartTimeMillis == EvsStatsState::NOT_STARTED) {
+ LOG(WARNING) << __func__ << "EvsStats received finishComputingFirstFrameLatency, but "
+ << "startComputingFirstFrameLatency was not called before.";
+ return;
+ }
+ auto firstFrameLatencyMillis = finishTimeMillis - mFirstFrameLatencyStartTimeMillis;
+ mFirstFrameLatencyStartTimeMillis = EvsStatsState::NOT_STARTED;
+
+ LOG(DEBUG) << __func__ << ": firstFrameLatencyMillis = " << firstFrameLatencyMillis;
+
+ EvsFirstFrameLatency latency;
+ latency.set_start_timestamp_millis(mFirstFrameLatencyStartTimeMillis);
+ latency.set_latency_millis(firstFrameLatencyMillis);
+ std::vector<uint8_t> bytes(latency.ByteSize());
+ latency.SerializeToArray(&bytes[0], latency.ByteSize());
+ CarData msg;
+ msg.id = kEvsFirstFrameLatencyId;
+ msg.content = std::move(bytes);
+
+ if (msg.content.size() > kCarTelemetryMaxDataSizePerWrite) {
+ LOG(WARNING) << __func__ << "finishComputingFirstFrameLatency is trying to store data "
+ << "with size " << msg.content.size() << ", which is larger than allowed "
+ << kCarTelemetryMaxDataSizePerWrite;
+ return;
+ }
+
+ mCollectedData.push_back(msg);
+
+ while (mCollectedData.size() > kCollectedDataSizeLimit) {
+ mCollectedData.pop_front();
+ }
+
+ sendCollectedDataUnsafe(/* waitIfNotReady= */ false);
+}
+
+void EvsStats::sendCollectedDataBlocking() {
+ if (!mEnabled || mCollectedData.empty()) {
+ return;
+ }
+ sendCollectedDataUnsafe(/* waitIfNotReady= */ true);
+}
+
+std::shared_ptr<ICarTelemetry> EvsStats::getCarTelemetry(bool waitIfNotReady) {
+ {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ if (mCarTelemetry != nullptr) {
+ return mCarTelemetry;
+ }
+ }
+
+ AIBinder* binder;
+ if (waitIfNotReady) {
+ binder = ::AServiceManager_getService(kCarTelemetryServiceName);
+ } else {
+ binder = ::AServiceManager_checkService(kCarTelemetryServiceName);
+ }
+
+ if (binder == nullptr) {
+ LOG(WARNING) << __func__ << ": ICarTelemetry is not ready";
+ return nullptr;
+ }
+
+ const std::scoped_lock<std::mutex> lock(mMutex); // locks until the end of the method
+ mCarTelemetry = ICarTelemetry::fromBinder(ndk::SpAIBinder(binder));
+ auto status = ndk::ScopedAStatus::fromStatus(
+ ::AIBinder_linkToDeath(mCarTelemetry->asBinder().get(), mBinderDeathRecipient.get(),
+ this));
+ if (!status.isOk()) {
+ LOG(WARNING) << __func__
+ << ": Failed to linkToDeath, continuing anyway: " << status.getMessage();
+ }
+ return mCarTelemetry;
+}
+
+void EvsStats::sendCollectedDataUnsafe(bool waitIfNotReady) {
+ std::shared_ptr<ICarTelemetry> telemetry = getCarTelemetry(waitIfNotReady);
+ if (telemetry == nullptr) {
+ LOG(INFO) << __func__ << ": CarTelemetry is not ready, ignoring";
+ return;
+ }
+
+ // Send data chunk by chnk, because Binder has transfer data size limit.
+ // Adds the oldest elements to `sendingData` and tries to push ICarTelemetryService.
+ // If successful, erases then from `mCollectedData`, otherwise leaves them there to try again
+ // later.
+ while (!mCollectedData.empty()) {
+ int sendingDataSizeBytes = 0;
+ std::vector<CarData> sendingData;
+ auto it = mCollectedData.begin();
+ while (it != mCollectedData.end()) {
+ sendingDataSizeBytes += it->content.size();
+ if (sendingDataSizeBytes > kCarTelemetryMaxDataSizePerWrite) {
+ break;
+ }
+ sendingData.push_back(*it);
+ it++;
+ }
+ ndk::ScopedAStatus status = telemetry->write(sendingData);
+ if (!status.isOk()) {
+ LOG(WARNING) << __func__
+ << "Failed to write data to ICarTelemetry: " << status.getMessage();
+ return;
+ }
+ mCollectedData.erase(mCollectedData.begin(), it);
+ }
+}
+
+// Removes the listener if its binder dies.
+void EvsStats::telemetryBinderDiedImpl() {
+ LOG(WARNING) << __func__ << "ICarTelemetry service died, resetting the state";
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ mCarTelemetry = nullptr;
+}
+
+// static
+void EvsStats::telemetryBinderDied(void* cookie) {
+ // We expect the pointer to be alive as there is only a single instance of
+ // EvsStats and if EvsStats is destructed, then the whole evs_app should be dead too.
+ auto thiz = static_cast<EvsStats*>(cookie);
+ thiz->telemetryBinderDiedImpl();
+}
diff --git a/cpp/evs/apps/default/EvsStats.h b/cpp/evs/apps/default/EvsStats.h
new file mode 100644
index 0000000..0d9d47f
--- /dev/null
+++ b/cpp/evs/apps/default/EvsStats.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAR_EVS_APP_EVSSTATS_H
+#define CAR_EVS_APP_EVSSTATS_H
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <android/binder_status.h>
+#include <utils/Mutex.h>
+
+#include <deque>
+#include <memory>
+
+// Performs metric computations, sends to `ICarTelemetry`.
+//
+// Not thread-safe. Methods `startComputingFirstFrameLatency`, `finishComputingFirstFrameLatency`
+// and `sendCollectedDataBlocking` must be called from the same thread.
+class EvsStats {
+public:
+ // Instantiates EvsStats.
+ static EvsStats build();
+
+ // Starts computing end-2-end first frame latency: from the time of the event that starts the
+ // camera to the time of the display of the very first frame.
+ // Call this method when an event that enables the camera occurred, e.g. gear shift to REAR.
+ // Param `startTimeMillis` should be `android::uptimeMillis()`.
+ void startComputingFirstFrameLatency(int64_t startTimeMillis);
+
+ // Computes the latency and sends the data to `ICarTelemetry` if the receiving service is up.
+ // Call this method when the first camera frame is displayed on the screen and don't call
+ // after that unless a new computation is started.
+ // Param `finishTimeMillis` should be `android::uptimeMillis()`.
+ void finishComputingFirstFrameLatency(int64_t finishTimeMillis);
+
+ // Sends the collected data to `ICarTelemetry`. Blocks for short amount of time if the service
+ // is unavailable.
+ void sendCollectedDataBlocking();
+
+private:
+ enum EvsStatsState {
+ NOT_STARTED = -1,
+ };
+
+private:
+ EvsStats(bool enabled) :
+ mEnabled(enabled),
+ mBinderDeathRecipient(::AIBinder_DeathRecipient_new(EvsStats::telemetryBinderDied)) {}
+
+ // Death recipient callback that is called when ICarTelemetry dies.
+ // The cookie is a pointer to a EvsStats object.
+ static void telemetryBinderDied(void* cookie);
+
+ void telemetryBinderDiedImpl();
+
+ // Tries sending data if the receiving service is up.
+ // Must be called when both `mEnabled` is true and `mCollectedData` is not empty.
+ //
+ // \param waitIfNotReady - if true, it can block briefly until ICarTelemetry is ready.
+ void sendCollectedDataUnsafe(bool waitIfNotReady);
+
+ // Returns the instance of ICarTelemetry.
+ //
+ // \param waitIfNotReady - if true, it can block briefly until ICarTelemetry is ready.
+ std::shared_ptr<aidl::android::frameworks::automotive::telemetry::ICarTelemetry>
+ getCarTelemetry(bool waitIfNotReady);
+
+ std::mutex mMutex;
+ bool mEnabled;
+ int64_t mFirstFrameLatencyStartTimeMillis = EvsStatsState::NOT_STARTED;
+ // This is a ring buffer
+ std::deque<aidl::android::frameworks::automotive::telemetry::CarData> mCollectedData;
+ std::shared_ptr<aidl::android::frameworks::automotive::telemetry::ICarTelemetry> mCarTelemetry;
+ ndk::ScopedAIBinder_DeathRecipient mBinderDeathRecipient;
+};
+
+#endif // CAR_EVS_APP_EVSSTATS_H
diff --git a/cpp/telemetry/proto/Android.bp b/cpp/telemetry/proto/Android.bp
new file mode 100644
index 0000000..862a537
--- /dev/null
+++ b/cpp/telemetry/proto/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "cartelemetry-evs-proto-srcs",
+ srcs: [
+ "evs.proto",
+ ],
+}
diff --git a/cpp/telemetry/proto/CarData.proto b/cpp/telemetry/proto/CarData.proto
new file mode 100644
index 0000000..96e8c2e
--- /dev/null
+++ b/cpp/telemetry/proto/CarData.proto
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file contains all the known CarData messages used in AAOS. Some messages
+ * might be defined in OEM's private repo, the owners of those messages must
+ * make sure the CarData IDs are not colliding.
+ *
+ * All the messages must be defined in the CarData message and given a unique
+ * ID.
+ */
+
+syntax = "proto2";
+
+package android.automotive.telemetry;
+option java_package = "com.android.automotive.telemetry";
+option java_outer_classname = "CarDataProto";
+
+import "packages/services/Car/cpp/telemetry/proto/evs.proto";
+
+message CarData {
+ oneof pushed {
+ EvsFirstFrameLatency evs_first_frame_latency = 1;
+ }
+
+ // DO NOT USE field numbers above 100,000 in AOSP.
+ // Field numbers 100,000 - 199,999 are reserved for non-AOSP (e.g. OEMs) to
+ // use. Field numbers 200,000 and above are reserved for future use; do not
+ // use them at all.
+}
diff --git a/cpp/telemetry/proto/evs.proto b/cpp/telemetry/proto/evs.proto
new file mode 100644
index 0000000..c39434f
--- /dev/null
+++ b/cpp/telemetry/proto/evs.proto
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file contains all the CarData messages used in EVS related processes.
+ * It's preferred to group CarData messages used in a single process or service
+ * to improve performance and memory usage.
+ *
+ * All the messages must be defined in the CarData message and given a unique
+ * ID.
+ */
+
+syntax = "proto2";
+
+package android.automotive.telemetry;
+option java_package = "com.android.automotive.telemetry";
+option java_outer_classname = "EvsProto";
+
+message EvsFirstFrameLatency {
+ // Latency measurement start time since boot.
+ optional int64 start_timestamp_millis = 1;
+
+ // end-2-end latency since gear shifted to reverse and the
+ // first frame from camera is displayed on the screen.
+ optional int64 latency_millis = 2;
+}