From 4895b4df794060b04ce590cb58fd493ec9ea4b03 Mon Sep 17 00:00:00 2001 From: Michael Butler Date: Thu, 18 Mar 2021 21:15:09 -0700 Subject: [PATCH] Change NNAPI time from steady_clock to boot_clock -- hal Previously, the NNAPI used std::chrono::steady_clock to represent and measure timings. However, steady_clock does not count while the system is suspended. Instead, boot_clock is monotonic like steady_clock but does include the time when the system is suspended. This change also indicates that services may convert from std::chrono::steady_clock::time_point to android::base::boot_clock::time_point in the HIDL 1.3 NN HAL. Bug: 183118340 Test: mma Test: VtsHalNeuralnetworksV1_3TargetTest Test: VtsHalNeuralnetworksTargetTest Test: presubmit Change-Id: I5a7d039a31d9ce98602a301387ec99635f279f42 Merged-In: I5a7d039a31d9ce98602a301387ec99635f279f42 --- current.txt | 2 + neuralnetworks/1.3/IDevice.hal | 18 ++++- neuralnetworks/1.3/IPreparedModel.hal | 24 +++++++ neuralnetworks/1.3/utils/src/Conversions.cpp | 71 ++++++++++++++++++- .../hardware/neuralnetworks/IDevice.aidl | 16 ++--- .../neuralnetworks/IPreparedModel.aidl | 8 +-- neuralnetworks/aidl/utils/src/Conversions.cpp | 9 +-- .../vts/functional/QualityOfServiceTests.cpp | 10 +-- 8 files changed, 133 insertions(+), 25 deletions(-) diff --git a/current.txt b/current.txt index 6c576cab7a..270880fbf1 100644 --- a/current.txt +++ b/current.txt @@ -782,6 +782,8 @@ cd84ab19c590e0e73dd2307b591a3093ee18147ef95e6d5418644463a6620076 android.hardwar f729ee6a5f136b25d79ea6895d24700fce413df555baaecf2c39e4440d15d043 android.hardware.neuralnetworks@1.0::types a84f8dac7a9b75de1cc2936a9b429b9b62b32a31ea88ca52c29f98f5ddc0fa95 android.hardware.neuralnetworks@1.2::types cd331b92312d16ab89f475c39296abbf539efc4114a8c5c2b136ad99b904ef33 android.hardware.neuralnetworks@1.3::types +c3fec5bd470984402997f78a74b6511efc4063b270f2bd9ee7b78f48b683a1bb android.hardware.neuralnetworks@1.3::IDevice +0fdfad62c2ec33b52e6687004e5a1971c02d10b93ee4d26df5ccff7ce032494a android.hardware.neuralnetworks@1.3::IPreparedModel e8c86c69c438da8d1549856c1bb3e2d1b8da52722f8235ff49a30f2cce91742c android.hardware.soundtrigger@2.1::ISoundTriggerHwCallback b9fbb6e2e061ed0960939d48b785e9700210add1f13ed32ecd688d0f1ca20ef7 android.hardware.renderscript@1.0::types 0f53d70e1eadf8d987766db4bf6ae2048004682168f4cab118da576787def3fa android.hardware.radio@1.0::types diff --git a/neuralnetworks/1.3/IDevice.hal b/neuralnetworks/1.3/IDevice.hal index e0b04a8b62..de889e4b25 100644 --- a/neuralnetworks/1.3/IDevice.hal +++ b/neuralnetworks/1.3/IDevice.hal @@ -131,6 +131,14 @@ interface IDevice extends @1.2::IDevice { * ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due * to an abort must be sent the same way as other errors, described above. + * The deadline is represented as nanoseconds since the epoch of the steady + * clock (as if from std::chrono::steady_clock::time_point), but the service + * may convert it to the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * Optionally, the driver may save the prepared model to cache during the * asynchronous preparation. Any error that occurs when saving to cache must @@ -249,7 +257,15 @@ interface IDevice extends @1.2::IDevice { * ErrorStatus::MISSED_DEADLINE_TRANSIENT} * or {@link ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The * error due to an abort must be sent the same way as other errors, - * described above. + * described above. The deadline is represented as nanoseconds since the + * epoch of the steady clock (as if from + * std::chrono::steady_clock::time_point), but the service may convert it to + * the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * The only information that may be unknown to the model at this stage is * the shape of the tensors, which may only be known at execution time. As diff --git a/neuralnetworks/1.3/IPreparedModel.hal b/neuralnetworks/1.3/IPreparedModel.hal index e7d63f4851..8b86a1a20e 100644 --- a/neuralnetworks/1.3/IPreparedModel.hal +++ b/neuralnetworks/1.3/IPreparedModel.hal @@ -74,6 +74,14 @@ interface IPreparedModel extends @1.2::IPreparedModel { * ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due * to an abort must be sent the same way as other errors, described above. + * The deadline is represented as nanoseconds since the epoch of the steady + * clock (as if from std::chrono::steady_clock::time_point), but the service + * may convert it to the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * Any number of calls to the execute* and executeSynchronously* functions, * in any combination, may be made concurrently, even on the same @@ -150,6 +158,14 @@ interface IPreparedModel extends @1.2::IPreparedModel { * ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due * to an abort must be sent the same way as other errors, described above. + * The deadline is represented as nanoseconds since the epoch of the steady + * clock (as if from std::chrono::steady_clock::time_point), but the service + * may convert it to the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * Any number of calls to the execute* and executeSynchronously* functions, * in any combination, may be made concurrently, even on the same @@ -231,6 +247,14 @@ interface IPreparedModel extends @1.2::IPreparedModel { * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due * to an abort must be sent the same way as other errors, described above. + * The deadline is represented as nanoseconds since the epoch of the steady + * clock (as if from std::chrono::steady_clock::time_point), but the service + * may convert it to the nanoseconds since boot time (as if from + * clock_gettime(CLOCK_BOOTTIME, &ts) or + * android::base::boot_clock::time_point) to account for time when the + * system is suspended. This conversion can by done by finding the timeout + * duration remaining compared to the steady_clock and adding it to the + * current boot_clock time. * * If any of the sync fences in waitFor changes to error status after the executeFenced * call succeeds, or the execution is aborted because it cannot finish before the deadline diff --git a/neuralnetworks/1.3/utils/src/Conversions.cpp b/neuralnetworks/1.3/utils/src/Conversions.cpp index 8083ae4368..e8a4f55afd 100644 --- a/neuralnetworks/1.3/utils/src/Conversions.cpp +++ b/neuralnetworks/1.3/utils/src/Conversions.cpp @@ -42,6 +42,23 @@ namespace { +std::chrono::nanoseconds makeNanosFromUint64(uint64_t nanoseconds) { + constexpr auto kMaxCount = std::chrono::nanoseconds::max().count(); + using CommonType = std::common_type_t; + const auto count = std::min(kMaxCount, nanoseconds); + return std::chrono::nanoseconds{static_cast(count)}; +} + +uint64_t makeUint64FromNanos(std::chrono::nanoseconds nanoseconds) { + if (nanoseconds < std::chrono::nanoseconds::zero()) { + return 0; + } + constexpr auto kMaxCount = std::numeric_limits::max(); + using CommonType = std::common_type_t; + const auto count = std::min(kMaxCount, nanoseconds.count()); + return static_cast(count); +} + template constexpr std::underlying_type_t underlyingType(Type value) { return static_cast>(value); @@ -237,8 +254,32 @@ GeneralResult unvalidatedConvert( switch (optionalTimePoint.getDiscriminator()) { case Discriminator::none: return {}; - case Discriminator::nanosecondsSinceEpoch: - return TimePoint{Duration{optionalTimePoint.nanosecondsSinceEpoch()}}; + case Discriminator::nanosecondsSinceEpoch: { + const auto currentSteadyTime = std::chrono::steady_clock::now(); + const auto currentBootTime = Clock::now(); + + const auto timeSinceEpoch = + makeNanosFromUint64(optionalTimePoint.nanosecondsSinceEpoch()); + const auto steadyTimePoint = std::chrono::steady_clock::time_point{timeSinceEpoch}; + + // Both steadyTimePoint and currentSteadyTime are guaranteed to be non-negative, so this + // subtraction will never overflow or underflow. + const auto timeRemaining = steadyTimePoint - currentSteadyTime; + + // currentBootTime is guaranteed to be non-negative, so this code only protects against + // an overflow. + nn::TimePoint bootTimePoint; + constexpr auto kZeroNano = std::chrono::nanoseconds::zero(); + constexpr auto kMaxTime = nn::TimePoint::max(); + if (timeRemaining > kZeroNano && currentBootTime > kMaxTime - timeRemaining) { + bootTimePoint = kMaxTime; + } else { + bootTimePoint = currentBootTime + timeRemaining; + } + + constexpr auto kZeroTime = nn::TimePoint{}; + return std::max(bootTimePoint, kZeroTime); + } } return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Invalid OptionalTimePoint discriminator " @@ -549,9 +590,33 @@ nn::GeneralResult unvalidatedConvert( nn::GeneralResult unvalidatedConvert( const nn::OptionalTimePoint& optionalTimePoint) { + const auto currentSteadyTime = std::chrono::steady_clock::now(); + const auto currentBootTime = nn::Clock::now(); + OptionalTimePoint ret; if (optionalTimePoint.has_value()) { - const auto count = optionalTimePoint.value().time_since_epoch().count(); + const auto bootTimePoint = optionalTimePoint.value(); + + if (bootTimePoint < nn::TimePoint{}) { + return NN_ERROR() << "Trying to cast invalid time point"; + } + + // Both bootTimePoint and currentBootTime are guaranteed to be non-negative, so this + // subtraction will never overflow or underflow. + const auto timeRemaining = bootTimePoint - currentBootTime; + + // currentSteadyTime is guaranteed to be non-negative, so this code only protects against an + // overflow. + std::chrono::steady_clock::time_point steadyTimePoint; + constexpr auto kZeroNano = std::chrono::nanoseconds::zero(); + constexpr auto kMaxTime = std::chrono::steady_clock::time_point::max(); + if (timeRemaining > kZeroNano && currentSteadyTime > kMaxTime - timeRemaining) { + steadyTimePoint = kMaxTime; + } else { + steadyTimePoint = currentSteadyTime + timeRemaining; + } + + const uint64_t count = makeUint64FromNanos(steadyTimePoint.time_since_epoch()); ret.nanosecondsSinceEpoch(count); } return ret; diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl index e17e0cd765..c5b4ab1b8f 100644 --- a/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl @@ -307,10 +307,10 @@ interface IDevice { * @param priority The priority of the prepared model relative to other prepared models owned by * the client. * @param deadline The time by which the model is expected to be prepared. The time is measured - * in nanoseconds since epoch of the steady clock (as from - * std::chrono::steady_clock). If the model cannot be prepared by the deadline, - * the preparation may be aborted. Passing -1 means the deadline is omitted. - * Other negative values are invalid. + * in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME, &ts) + * or ::android::base::boot_clock). If the model cannot be prepared by the + * deadline, the preparation may be aborted. Passing -1 means the deadline is + * omitted. Other negative values are invalid. * @param modelCache A vector of file descriptors for the security-sensitive cache. The length * of the vector must either be 0 indicating that caching information is not * provided, or match the numModelCache returned from @@ -396,10 +396,10 @@ interface IDevice { * different shapes of inputs on different (possibly concurrent) executions. * * @param deadline The time by which the model is expected to be prepared. The time is measured - * in nanoseconds since epoch of the steady clock (as from - * std::chrono::steady_clock). If the model cannot be prepared by the deadline, - * the preparation may be aborted. Passing -1 means the deadline is omitted. - * Other negative values are invalid. + * in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME, &ts) or + * ::android::base::boot_clock). If the model cannot be prepared by the + * deadline, the preparation may be aborted. Passing -1 means the deadline is + * omitted. Other negative values are invalid. * @param modelCache A vector of file descriptors for the security-sensitive cache. The length * of the vector must match the numModelCache returned from * getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl index 2a9757b323..bfab9067d1 100644 --- a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl +++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl @@ -73,8 +73,8 @@ interface IPreparedModel { * runs from the time the driver sees the call to the executeSynchronously * function to the time the driver returns from the function. * @param deadline The time by which the execution is expected to complete. The time is measured - * in nanoseconds since epoch of the steady clock (as from - * std::chrono::steady_clock). If the execution cannot be finished by the + * in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME, &ts) or + * ::android::base::boot_clock). If the execution cannot be finished by the * deadline, the execution may be aborted. Passing -1 means the deadline is * omitted. Other negative values are invalid. * @param loopTimeoutDuration The maximum amount of time in nanoseconds that should be spent @@ -138,8 +138,8 @@ interface IPreparedModel { * sync fences have been signaled. * @param measure Specifies whether or not to measure duration of the execution. * @param deadline The time by which the execution is expected to complete. The time is measured - * in nanoseconds since epoch of the steady clock (as from - * std::chrono::steady_clock).If the execution cannot be finished by the + * in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME, &ts) or + * ::android::base::boot_clock). If the execution cannot be finished by the * deadline, the execution may be aborted. Passing -1 means the deadline is * omitted. Other negative values are invalid. * @param loopTimeoutDuration The maximum amount of time in nanoseconds that should be spent diff --git a/neuralnetworks/aidl/utils/src/Conversions.cpp b/neuralnetworks/aidl/utils/src/Conversions.cpp index c74c509a8d..d5f7f81663 100644 --- a/neuralnetworks/aidl/utils/src/Conversions.cpp +++ b/neuralnetworks/aidl/utils/src/Conversions.cpp @@ -931,11 +931,12 @@ nn::GeneralResult unvalidatedConvert(const nn::Timing& timing) { } nn::GeneralResult unvalidatedConvert(const nn::Duration& duration) { - const uint64_t nanoseconds = duration.count(); - if (nanoseconds > std::numeric_limits::max()) { - return std::numeric_limits::max(); + if (duration < nn::Duration::zero()) { + return NN_ERROR() << "Unable to convert invalid (negative) duration"; } - return static_cast(nanoseconds); + constexpr std::chrono::nanoseconds::rep kIntMax = std::numeric_limits::max(); + const auto count = duration.count(); + return static_cast(std::min(count, kIntMax)); } nn::GeneralResult unvalidatedConvert(const nn::OptionalDuration& optionalDuration) { diff --git a/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp index 9ace1a9591..e803e38092 100644 --- a/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp +++ b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ +#include #include #include #include - #include #include "Callbacks.h" @@ -61,16 +61,16 @@ static int64_t makeDeadline(DeadlineBoundType deadlineBoundType) { return std::chrono::duration_cast(timeSinceEpoch).count(); }; - std::chrono::steady_clock::time_point timePoint; + ::android::base::boot_clock::time_point timePoint; switch (deadlineBoundType) { case DeadlineBoundType::NOW: - timePoint = std::chrono::steady_clock::now(); + timePoint = ::android::base::boot_clock::now(); break; case DeadlineBoundType::UNLIMITED: - timePoint = std::chrono::steady_clock::time_point::max(); + timePoint = ::android::base::boot_clock::time_point::max(); break; case DeadlineBoundType::SHORT: - timePoint = std::chrono::steady_clock::now() + kShortDuration; + timePoint = ::android::base::boot_clock::now() + kShortDuration; break; }