diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index 6858819a84..0bd297bc42 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -304,6 +304,14 @@ default + + android.hardware.power.stats + 1.0 + + IPowerStats + default + + android.hardware.radio 1.0-2 diff --git a/power/stats/1.0/Android.bp b/power/stats/1.0/Android.bp new file mode 100644 index 0000000000..9a956e4ce1 --- /dev/null +++ b/power/stats/1.0/Android.bp @@ -0,0 +1,29 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.power.stats@1.0", + root: "android.hardware", + vndk: { + enabled: true, + }, + srcs: [ + "types.hal", + "IPowerStats.hal", + ], + interfaces: [ + "android.hidl.base@1.0", + ], + types: [ + "EnergyData", + "PowerEntityInfo", + "PowerEntityStateInfo", + "PowerEntityStateResidencyData", + "PowerEntityStateResidencyResult", + "PowerEntityStateSpace", + "PowerEntityType", + "RailInfo", + "Status", + ], + gen_java: false, +} + diff --git a/power/stats/1.0/IPowerStats.hal b/power/stats/1.0/IPowerStats.hal new file mode 100644 index 0000000000..74ceb8f73c --- /dev/null +++ b/power/stats/1.0/IPowerStats.hal @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2018 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 android.hardware.power.stats@1.0; + +interface IPowerStats { + + /** + * Rail information: + * Reports information related to the rails being monitored. + * + * @return rails Information about monitored rails. + * @return status SUCCESS on success or NOT_SUPPORTED if + * feature is not enabled or FILESYSTEM_ERROR on filesystem nodes + * access error. + */ + getRailInfo() + generates(vec rails, Status status); + + /** + * Rail level energy measurements for low frequency clients: + * Reports accumulated energy since boot on each rail. + * + * @param railIndices Indices of rails for which data is required. + * To get data for all rails pass an empty vector. Rail name to + * index mapping can be queried from getRailInfo() API. + * @return data Energy values since boot for all requested rails. + * @return status SUCCESS on success or NOT_SUPPORTED if + * feature is not enabled or FILESYSTEM_ERROR on filesystem nodes + * access error. + */ + getEnergyData(vec railIndices) + generates(vec data, Status status); + + /** + * Stream rail level power measurements for high frequency clients: + * Streams accumulated energy since boot on each rail. This API is + * asynchronous. + * + * @param timeMs Time(in ms) for which energyData should be streamed + * @param samplingRate Frequency(in Hz) at which samples should be + * captured. If the requested sampling rate is not supported then + * SUCCESS is returned and numSamples are reported back according + * to the supported sampling rate. + * @return mqDesc Blocking Synchronous Fast Message Queue descriptor - One + * writer(power.stats HAL) and one reader are supported. Data is + * present in the following format in the queue: + * +-----------------------+ <-- + * | EnergyData for rail 1 | | + * +-----------------------+ | + * | EnergyData for rail 2 | | + * +-----------------------+ | + * | . | |-- 1st Sample + * | . | | + * | . | | + * +-----------------------+ | + * | EnergyData for rail n | | + * +-----------------------+ <-- + * | . | + * | . | + * | . | + * +-----------------------+ <-- + * | EnergyData for rail 1 | | + * +-----------------------+ | + * | EnergyData for rail 2 | | + * +-----------------------+ | + * | . | |-- kth Sample + * | . | | + * | . | | + * +-----------------------+ | + * | EnergyData for rail n | | + * +-----------------------+ <-- + * + * where, + * n = railsPerSample + * k = numSamples + * + * @return numSamples Number of samples which will be generated in timeMs. + * @return railsPerSample Number of rails measured per sample. + * @return status SUCCESS on success or FILESYSTEM_ERROR on filesystem + * nodes access or NOT_SUPPORTED if feature is not enabled or + * INSUFFICIENT_RESOURCES if there are not enough resources. + */ + streamEnergyData(uint32_t timeMs, uint32_t samplingRate) + generates(fmq_sync mqDesc, uint32_t numSamples, + uint32_t railsPerSample, Status status); + + /** + * PowerEntity information: + * Reports information related to all supported PowerEntity(s) for which + * data is available. A PowerEntity is defined as a platform subsystem, + * peripheral, or power domain that impacts the total device power + * consumption. + * + * @return powerEntityInfos List of information on each PowerEntity + * @return status SUCCESS on success, NOT_SUPPORTED if feature is not + * enabled, FILESYSTEM_ERROR if there was an error accessing the + * filesystem. + */ + getPowerEntityInfo() + generates(vec powerEntityInfos, Status status); + + /** + * PowerEntity state information: + * Reports the set of power states for which the specified + * PowerEntity(s) provide residency data. + * + * @param powerEntityIds collection of IDs of PowerEntity(s) for which + * state information is requested. PowerEntity name to ID mapping may + * be queried from getPowerEntityInfo(). To get state space + * information for all PowerEntity(s) pass an empty vector. + * + * @return powerEntityStateSpaces PowerEntity state space information for + * each specified PowerEntity that provides state space information. + * @return status SUCCESS on success, NOT_SUPPORTED if feature is not + * enabled, FILESYSTEM_ERROR if there was an error accessing the + * filesystem, INVALID_INPUT if any requested PowerEntity(s) do not + * provide state space information and there was not a filesystem error. + */ + getPowerEntityStateInfo(vec powerEntityIds) + generates(vec powerEntityStateSpaces, + Status status); + + /** + * PowerEntity residencies for low frequency clients: + * Reports accumulated residency data for each specified PowerEntity. + * Each PowerEntity may reside in one of multiple states. It may also + * transition to another state. Residency data is an accumulation of time + * that a specified PowerEntity resided in each of its possible states, + * the number of times that each state was entered, and a timestamp + * corresponding to the last time that state was entered. Data is + * accumulated starting from the last time the PowerEntity was reset. + * + * @param powerEntityId collection of IDs of PowerEntity(s) for which + * residency data is requested. PowerEntity name to ID mapping may + * be queried from getPowerEntityInfo(). To get state residency + * data for all PowerEntity(s) pass an empty vector. + * @return stateResidencyResults state residency data for each specified + * PowerEntity that provides state residency data. + * @return status SUCCESS on success, NOT_SUPPORTED if feature is not + * enabled, FILESYSTEM_ERROR if there was an error accessing the + * filesystem, INVALID_INPUT if any requested PowerEntity(s) do not + * provide state residency data and there was not a filesystem error. + */ + getPowerEntityStateResidencyData(vec powerEntityIds) + generates(vec stateResidencyResults, + Status status); +}; diff --git a/power/stats/1.0/default/Android.bp b/power/stats/1.0/default/Android.bp new file mode 100644 index 0000000000..04270c14fe --- /dev/null +++ b/power/stats/1.0/default/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2018 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 + +cc_library_shared { + name: "android.hardware.power.stats@1.0-service", + relative_install_path: "hw", + init_rc: ["android.hardware.power.stats@1.0-service.rc"], + srcs: ["service.cpp", "PowerStats.cpp"], + cflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ + "libbase", + "libcutils", + "libfmq", + "libhidlbase", + "libhidltransport", + "liblog", + "libutils", + "android.hardware.power.stats@1.0", + ], + vendor: true, +} diff --git a/power/stats/1.0/default/PowerStats.cpp b/power/stats/1.0/default/PowerStats.cpp new file mode 100644 index 0000000000..810c575a87 --- /dev/null +++ b/power/stats/1.0/default/PowerStats.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2018 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 "PowerStats.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace power { +namespace stats { +namespace V1_0 { +namespace implementation { + +#define MAX_FILE_PATH_LEN 128 +#define MAX_DEVICE_NAME_LEN 64 +#define MAX_QUEUE_SIZE 8192 + +constexpr char kIioDirRoot[] = "/sys/bus/iio/devices/"; +constexpr char kDeviceName[] = "pm_device_name"; +constexpr char kDeviceType[] = "iio:device"; +constexpr uint32_t MAX_SAMPLING_RATE = 10; +constexpr uint64_t WRITE_TIMEOUT_NS = 1000000000; + +void PowerStats::findIioPowerMonitorNodes() { + struct dirent* ent; + int fd; + char devName[MAX_DEVICE_NAME_LEN]; + char filePath[MAX_FILE_PATH_LEN]; + DIR* iioDir = opendir(kIioDirRoot); + if (!iioDir) { + ALOGE("Error opening directory: %s", kIioDirRoot); + return; + } + while (ent = readdir(iioDir), ent) { + if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0 && + strlen(ent->d_name) > strlen(kDeviceType) && + strncmp(ent->d_name, kDeviceType, strlen(kDeviceType)) == 0) { + snprintf(filePath, MAX_FILE_PATH_LEN, "%s/%s", ent->d_name, "name"); + fd = openat(dirfd(iioDir), filePath, O_RDONLY); + if (fd < 0) { + ALOGW("Failed to open directory: %s", filePath); + continue; + } + if (read(fd, devName, MAX_DEVICE_NAME_LEN) < 0) { + ALOGW("Failed to read device name from file: %s(%d)", filePath, fd); + close(fd); + continue; + } + + if (strncmp(devName, kDeviceName, strlen(kDeviceName)) == 0) { + snprintf(filePath, MAX_FILE_PATH_LEN, "%s/%s", kIioDirRoot, ent->d_name); + mPm.devicePaths.push_back(filePath); + } + close(fd); + } + } + closedir(iioDir); + return; +} + +size_t PowerStats::parsePowerRails() { + std::string data; + std::string railFileName; + std::string spsFileName; + uint32_t index = 0; + uint32_t samplingRate; + for (const auto& path : mPm.devicePaths) { + railFileName = path + "/enabled_rails"; + spsFileName = path + "/sampling_rate"; + if (!android::base::ReadFileToString(spsFileName, &data)) { + ALOGW("Error reading file: %s", spsFileName.c_str()); + continue; + } + samplingRate = strtoul(data.c_str(), NULL, 10); + if (!samplingRate || samplingRate == ULONG_MAX) { + ALOGE("Error parsing: %s", spsFileName.c_str()); + break; + } + if (!android::base::ReadFileToString(railFileName, &data)) { + ALOGW("Error reading file: %s", railFileName.c_str()); + continue; + } + std::istringstream railNames(data); + std::string line; + while (std::getline(railNames, line)) { + std::vector words = android::base::Split(line, ":"); + if (words.size() == 2) { + mPm.railsInfo.emplace(words[0], RailData{.devicePath = path, + .index = index, + .subsysName = words[1], + .samplingRate = samplingRate}); + index++; + } else { + ALOGW("Unexpected format in file: %s", railFileName.c_str()); + } + } + } + return index; +} + +int PowerStats::parseIioEnergyNode(std::string devName) { + int ret = 0; + std::string data; + std::string fileName = devName + "/energy_value"; + if (!android::base::ReadFileToString(fileName, &data)) { + ALOGE("Error reading file: %s", fileName.c_str()); + return -1; + } + + std::istringstream energyData(data); + std::string line; + uint64_t timestamp = 0; + bool timestampRead = false; + while (std::getline(energyData, line)) { + std::vector words = android::base::Split(line, ","); + if (timestampRead == false) { + if (words.size() == 1) { + timestamp = strtoull(words[0].c_str(), NULL, 10); + if (timestamp == 0 || timestamp == ULLONG_MAX) { + ALOGW("Potentially wrong timestamp: %" PRIu64, timestamp); + } + timestampRead = true; + } + } else if (words.size() == 2) { + std::string railName = words[0]; + if (mPm.railsInfo.count(railName) != 0) { + size_t index = mPm.railsInfo[railName].index; + mPm.reading[index].index = index; + mPm.reading[index].timestamp = timestamp; + mPm.reading[index].energy = strtoull(words[1].c_str(), NULL, 10); + if (mPm.reading[index].energy == ULLONG_MAX) { + ALOGW("Potentially wrong energy value: %" PRIu64, mPm.reading[index].energy); + } + } + } else { + ALOGW("Unexpected format in file: %s", fileName.c_str()); + ret = -1; + break; + } + } + return ret; +} + +Status PowerStats::parseIioEnergyNodes() { + Status ret = Status::SUCCESS; + if (mPm.hwEnabled == false) { + return Status::NOT_SUPPORTED; + } + + for (const auto& devicePath : mPm.devicePaths) { + if (parseIioEnergyNode(devicePath) < 0) { + ALOGE("Error in parsing power stats"); + ret = Status::FILESYSTEM_ERROR; + break; + } + } + return ret; +} + +PowerStats::PowerStats() { + findIioPowerMonitorNodes(); + size_t numRails = parsePowerRails(); + if (mPm.devicePaths.empty() || numRails == 0) { + mPm.hwEnabled = false; + } else { + mPm.hwEnabled = true; + mPm.reading.resize(numRails); + } +} + +Return PowerStats::getRailInfo(getRailInfo_cb _hidl_cb) { + hidl_vec rInfo; + Status ret = Status::SUCCESS; + size_t index; + std::lock_guard _lock(mPm.mLock); + if (mPm.hwEnabled == false) { + _hidl_cb(rInfo, Status::NOT_SUPPORTED); + return Void(); + } + rInfo.resize(mPm.railsInfo.size()); + for (const auto& railData : mPm.railsInfo) { + index = railData.second.index; + rInfo[index].railName = railData.first; + rInfo[index].subsysName = railData.second.subsysName; + rInfo[index].index = index; + rInfo[index].samplingRate = railData.second.samplingRate; + } + _hidl_cb(rInfo, ret); + return Void(); +} + +Return PowerStats::getEnergyData(const hidl_vec& railIndices, + getEnergyData_cb _hidl_cb) { + hidl_vec eVal; + std::lock_guard _lock(mPm.mLock); + Status ret = parseIioEnergyNodes(); + + if (ret != Status::SUCCESS) { + ALOGE("Failed to getEnergyData"); + _hidl_cb(eVal, ret); + return Void(); + } + + if (railIndices.size() == 0) { + eVal.resize(mPm.railsInfo.size()); + memcpy(&eVal[0], &mPm.reading[0], mPm.reading.size() * sizeof(EnergyData)); + } else { + eVal.resize(railIndices.size()); + int i = 0; + for (const auto& railIndex : railIndices) { + if (railIndex >= mPm.reading.size()) { + ret = Status::INVALID_INPUT; + eVal.resize(0); + break; + } + memcpy(&eVal[i], &mPm.reading[railIndex], sizeof(EnergyData)); + i++; + } + } + _hidl_cb(eVal, ret); + return Void(); +} + +Return PowerStats::streamEnergyData(uint32_t timeMs, uint32_t samplingRate, + streamEnergyData_cb _hidl_cb) { + std::lock_guard _lock(mPm.mLock); + if (mPm.fmqSynchronized != nullptr) { + _hidl_cb(MessageQueueSync::Descriptor(), 0, 0, Status::INSUFFICIENT_RESOURCES); + return Void(); + } + uint32_t sps = std::min(samplingRate, MAX_SAMPLING_RATE); + uint32_t numSamples = timeMs * sps / 1000; + mPm.fmqSynchronized.reset(new (std::nothrow) MessageQueueSync(MAX_QUEUE_SIZE, true)); + if (mPm.fmqSynchronized == nullptr || mPm.fmqSynchronized->isValid() == false) { + mPm.fmqSynchronized = nullptr; + _hidl_cb(MessageQueueSync::Descriptor(), 0, 0, Status::INSUFFICIENT_RESOURCES); + return Void(); + } + std::thread pollThread = std::thread([this, sps, numSamples]() { + uint64_t sleepTimeUs = 1000000 / sps; + uint32_t currSamples = 0; + while (currSamples < numSamples) { + mPm.mLock.lock(); + if (parseIioEnergyNodes() == Status::SUCCESS) { + mPm.fmqSynchronized->writeBlocking(&mPm.reading[0], mPm.reading.size(), + WRITE_TIMEOUT_NS); + mPm.mLock.unlock(); + currSamples++; + if (usleep(sleepTimeUs) < 0) { + ALOGW("Sleep interrupted"); + break; + } + } else { + mPm.mLock.unlock(); + break; + } + } + mPm.mLock.lock(); + mPm.fmqSynchronized = nullptr; + mPm.mLock.unlock(); + return; + }); + pollThread.detach(); + _hidl_cb(*(mPm.fmqSynchronized)->getDesc(), numSamples, mPm.reading.size(), Status::SUCCESS); + return Void(); +} + +Return PowerStats::getPowerEntityInfo(getPowerEntityInfo_cb _hidl_cb) { + hidl_vec eInfo; + _hidl_cb(eInfo, Status::NOT_SUPPORTED); + return Void(); +} + +Return PowerStats::getPowerEntityStateInfo(const hidl_vec& powerEntityIds, + getPowerEntityStateInfo_cb _hidl_cb) { + (void)powerEntityIds; + hidl_vec powerEntityStateSpaces; + _hidl_cb(powerEntityStateSpaces, Status::NOT_SUPPORTED); + return Void(); +} + +Return PowerStats::getPowerEntityStateResidencyData( + const hidl_vec& powerEntityIds, getPowerEntityStateResidencyData_cb _hidl_cb) { + (void)powerEntityIds; + hidl_vec results; + _hidl_cb(results, Status::NOT_SUPPORTED); + return Void(); +} + +} // namespace implementation +} // namespace V1_0 +} // namespace stats +} // namespace power +} // namespace hardware +} // namespace android diff --git a/power/stats/1.0/default/PowerStats.h b/power/stats/1.0/default/PowerStats.h new file mode 100644 index 0000000000..fb2c6a8ce5 --- /dev/null +++ b/power/stats/1.0/default/PowerStats.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 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 ANDROID_HARDWARE_POWERSTATS_V1_0_POWERSTATS_H +#define ANDROID_HARDWARE_POWERSTATS_V1_0_POWERSTATS_H + +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace power { +namespace stats { +namespace V1_0 { +namespace implementation { + +using ::android::hardware::hidl_vec; +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::power::stats::V1_0::EnergyData; +using ::android::hardware::power::stats::V1_0::PowerEntityInfo; +using ::android::hardware::power::stats::V1_0::PowerEntityStateInfo; +using ::android::hardware::power::stats::V1_0::PowerEntityStateResidencyData; +using ::android::hardware::power::stats::V1_0::PowerEntityStateResidencyResult; +using ::android::hardware::power::stats::V1_0::PowerEntityStateSpace; +using ::android::hardware::power::stats::V1_0::PowerEntityType; +using ::android::hardware::power::stats::V1_0::RailInfo; +using ::android::hardware::power::stats::V1_0::Status; + +typedef MessageQueue MessageQueueSync; +struct RailData { + std::string devicePath; + uint32_t index; + std::string subsysName; + uint32_t samplingRate; +}; + +struct OnDeviceMmt { + std::mutex mLock; + bool hwEnabled; + std::vector devicePaths; + std::map railsInfo; + std::vector reading; + std::unique_ptr fmqSynchronized; +}; + +struct PowerStats : public IPowerStats { + PowerStats(); + // Methods from ::android::hardware::power::stats::V1_0::IPowerStats follow. + Return getRailInfo(getRailInfo_cb _hidl_cb) override; + Return getEnergyData(const hidl_vec& railIndices, + getEnergyData_cb _hidl_cb) override; + Return streamEnergyData(uint32_t timeMs, uint32_t samplingRate, + streamEnergyData_cb _hidl_cb) override; + Return getPowerEntityInfo(getPowerEntityInfo_cb _hidl_cb) override; + Return getPowerEntityStateInfo(const hidl_vec& powerEntityIds, + getPowerEntityStateInfo_cb _hidl_cb) override; + Return getPowerEntityStateResidencyData( + const hidl_vec& powerEntityIds, + getPowerEntityStateResidencyData_cb _hidl_cb) override; + + private: + OnDeviceMmt mPm; + void findIioPowerMonitorNodes(); + size_t parsePowerRails(); + int parseIioEnergyNode(std::string devName); + Status parseIioEnergyNodes(); +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace stats +} // namespace power +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_POWERSTATS_V1_0_POWERSTATS_H diff --git a/power/stats/1.0/default/android.hardware.power.stats@1.0-service.rc b/power/stats/1.0/default/android.hardware.power.stats@1.0-service.rc new file mode 100644 index 0000000000..d7e546b75e --- /dev/null +++ b/power/stats/1.0/default/android.hardware.power.stats@1.0-service.rc @@ -0,0 +1,4 @@ +service vendor.power.stats-hal-1-0 /vendor/bin/hw/android.hardware.power.stats@1.0-service + class hal + user system + group system diff --git a/power/stats/1.0/default/service.cpp b/power/stats/1.0/default/service.cpp new file mode 100644 index 0000000000..80649f5c5e --- /dev/null +++ b/power/stats/1.0/default/service.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 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. + */ + +#define LOG_TAG "android.hardware.power.stats@1.0-service" + +#include +#include + +#include "PowerStats.h" + +using android::OK; +using android::sp; +using android::status_t; + +// libhwbinder: +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; + +// Generated HIDL files +using android::hardware::power::stats::V1_0::IPowerStats; +using android::hardware::power::stats::V1_0::implementation::PowerStats; + +int main(int /* argc */, char** /* argv */) { + ALOGI("power.stats service 1.0 is starting."); + + android::sp service = new PowerStats(); + if (service == nullptr) { + ALOGE("Can not create an instance of power.stats HAL Iface, exiting."); + return 1; + } + + configureRpcThreadpool(1, true /*callerWillJoin*/); + + status_t status = service->registerAsService(); + if (status != OK) { + ALOGE("Could not register service for power.stats HAL Iface (%d), exiting.", status); + return 1; + } + + ALOGI("power.stats service is ready"); + joinRpcThreadpool(); + + // In normal operation, we don't expect the thread pool to exit + ALOGE("power.stats service is shutting down"); + return 1; +} diff --git a/power/stats/1.0/types.hal b/power/stats/1.0/types.hal new file mode 100644 index 0000000000..644224bb3d --- /dev/null +++ b/power/stats/1.0/types.hal @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018 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 android.hardware.power.stats@1.0; + +enum Status : uint32_t { + SUCCESS = 0, + NOT_SUPPORTED = 1, + INVALID_INPUT = 2, + FILESYSTEM_ERROR = 3, + INSUFFICIENT_RESOURCES = 4, +}; + +struct RailInfo { + /** Index corresponding to the rail */ + uint32_t index; + /** Name of the rail */ + string railName; + /** Name of the subsystem to which this rail belongs */ + string subsysName; + /** Hardware sampling rate */ + uint32_t samplingRate; +}; + +struct EnergyData { + /** + * Index corresponding to the rail. This index matches + * the index returned in RailInfo + */ + uint32_t index; + /** Time since device boot(CLOCK_BOOTTIME) in milli-seconds */ + uint64_t timestamp; + /** Accumulated energy since device boot in microwatt-seconds (uWs) */ + uint64_t energy; +}; + +enum PowerEntityType : uint32_t { + /** + * A subsystem is a self-contained compute unit. Some examples include + * application processor, DSP, GPU. + */ + SUBSYSTEM = 0, + /** + * A peripheral is an auxiliary device that connects to and works with a + * compute unit. Some examples include simple sensors, camera, display. + */ + PERIPHERAL = 1, + /** + * A power domain is a single subsystem or a collection of subsystems + * that is controlled by a single voltage rail. + */ + POWER_DOMAIN = 2, +}; + +/** + * PowerEntityInfo contains information, such as the ID, name, and type of a + * given PowerEntity. + */ +struct PowerEntityInfo { + /** Unique ID corresponding to the PowerEntity */ + uint32_t powerEntityId; + /** Name of the PowerEntity */ + string powerEntityName; + /** Type of the PowerEntity */ + PowerEntityType type; +}; + +struct PowerEntityStateInfo { + /** + * ID corresponding to the state. Unique for a given PowerEntityStateSpace + */ + uint32_t powerEntityStateId; + /** Name of the state */ + string powerEntityStateName; +}; + +/** + * PowerEntityStateSpace contains the state space information of a given + * PowerEntity. The state space, is the set of possible states that a given + * PowerEntity provides residency data for. + */ +struct PowerEntityStateSpace { + /** Unique ID of the corresponding PowerEntity */ + uint32_t powerEntityId; + + /** List of states that the PowerEntity may reside in */ + vec states; +}; + +/** Contains residency data for a single state */ +struct PowerEntityStateResidencyData { + /** Unique ID of the corresponding PowerEntityStateInfo */ + uint32_t powerEntityStateId; + /** + * Total time in milliseconds that the corresponding PowerEntity resided + * in this state since the PowerEntity was reset + */ + uint64_t totalTimeInStateMs; + /** + * Total number of times that the state was entered since the corresponding + * PowerEntity was reset + */ + uint64_t totalStateEntryCount; + /** + * Last time this state was entered. Time in milliseconds since the + * corresponding PowerEntity was reset + */ + uint64_t lastEntryTimestampMs; +}; + +struct PowerEntityStateResidencyResult { + /** Unique ID of the corresponding PowerEntity */ + uint32_t powerEntityId; + /** Residency data for each state the PowerEntity's state space */ + vec stateResidencyData; +}; diff --git a/power/stats/1.0/vts/functional/Android.bp b/power/stats/1.0/vts/functional/Android.bp new file mode 100644 index 0000000000..4f0b325d4c --- /dev/null +++ b/power/stats/1.0/vts/functional/Android.bp @@ -0,0 +1,38 @@ +// +// Copyright (C) 2018 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. +// + +cc_test { + name: "VtsHalPowerStatsV1_0TargetTest", + defaults: [ + "VtsHalTargetTestDefaults" + ], + srcs: [ + "VtsHalPowerStatsV1_0TargetTest.cpp" + ], + static_libs: [ + "android.hardware.power.stats@1.0" + ], + shared_libs: [ + "libbase", + "libcutils", + "liblog", + "libhidlbase", + "libfmq", + "libhidltransport", + "libhwbinder", + "libutils", + ], +} diff --git a/power/stats/1.0/vts/functional/VtsHalPowerStatsV1_0TargetTest.cpp b/power/stats/1.0/vts/functional/VtsHalPowerStatsV1_0TargetTest.cpp new file mode 100644 index 0000000000..9f007e47f2 --- /dev/null +++ b/power/stats/1.0/vts/functional/VtsHalPowerStatsV1_0TargetTest.cpp @@ -0,0 +1,580 @@ +/* + * Copyright (C) 2018 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. + */ + +#define LOG_TAG "android.power.stats.vts" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { +namespace power { +namespace stats { +namespace vts { +namespace { + +using android::sp; +using android::hardware::hidl_vec; +using android::hardware::kSynchronizedReadWrite; +using android::hardware::Return; +using android::hardware::Void; +using android::hardware::power::stats::V1_0::EnergyData; +using android::hardware::power::stats::V1_0::IPowerStats; +using android::hardware::power::stats::V1_0::PowerEntityInfo; +using android::hardware::power::stats::V1_0::PowerEntityStateResidencyResult; +using android::hardware::power::stats::V1_0::PowerEntityStateSpace; +using android::hardware::power::stats::V1_0::RailInfo; +using android::hardware::power::stats::V1_0::Status; + +} // namespace + +typedef hardware::MessageQueue MessageQueueSync; +// Test environment for Power HIDL HAL. +class PowerStatsHidlEnv : public ::testing::VtsHalHidlTargetTestEnvBase { + public: + // get the test environment singleton + static PowerStatsHidlEnv* Instance() { + static PowerStatsHidlEnv* instance = new PowerStatsHidlEnv; + return instance; + } + + virtual void registerTestServices() override { registerTestService(); } +}; + +class PowerStatsHidlTest : public ::testing::VtsHalHidlTargetTestBase { + public: + virtual void SetUp() override { + service_ = ::testing::VtsHalHidlTargetTestBase::getService( + PowerStatsHidlEnv::Instance()->getServiceName()); + ASSERT_NE(service_, nullptr); + } + + virtual void TearDown() override {} + + void getInfos(hidl_vec& infos); + void getStateSpaces(hidl_vec& stateSpaces, + const std::vector& ids); + void getResidencyResults(hidl_vec& results, + const std::vector& ids); + void getRandomIds(std::vector& ids); + + sp service_; +}; + +void PowerStatsHidlTest::getInfos(hidl_vec& infos) { + Status status; + Return ret = service_->getPowerEntityInfo([&status, &infos](auto rInfos, auto rStatus) { + status = rStatus; + infos = rInfos; + }); + ASSERT_TRUE(ret.isOk()); + + if (status == Status::SUCCESS) { + ASSERT_NE(infos.size(), 0) << "powerEntityInfos must have entries if supported"; + } else { + ASSERT_EQ(status, Status::NOT_SUPPORTED); + ASSERT_EQ(infos.size(), 0); + LOG(INFO) << "getPowerEntityInfo not supported"; + } +} + +void PowerStatsHidlTest::getStateSpaces(hidl_vec& stateSpaces, + const std::vector& ids = {}) { + Status status; + Return ret = service_->getPowerEntityStateInfo( + ids, [&status, &stateSpaces](auto rStateSpaces, auto rStatus) { + status = rStatus; + stateSpaces = rStateSpaces; + }); + ASSERT_TRUE(ret.isOk()); + + if (status == Status::SUCCESS) { + ASSERT_NE(stateSpaces.size(), 0) << "powerEntityStateSpaces must have entries if supported"; + } else { + ASSERT_EQ(status, Status::NOT_SUPPORTED); + ASSERT_EQ(stateSpaces.size(), 0); + LOG(INFO) << "getPowerEntityStateInfo not supported"; + } +} + +void PowerStatsHidlTest::getResidencyResults(hidl_vec& results, + const std::vector& ids = {}) { + Status status; + Return ret = service_->getPowerEntityStateResidencyData( + ids, [&status, &results](auto rResults, auto rStatus) { + status = rStatus; + results = rResults; + }); + ASSERT_TRUE(ret.isOk()); + + if (status == Status::SUCCESS) { + ASSERT_NE(results.size(), 0); + } else { + ASSERT_EQ(status, Status::NOT_SUPPORTED); + ASSERT_EQ(results.size(), 0); + LOG(INFO) << "getPowerEntityStateResidencyData not supported"; + } +} + +void PowerStatsHidlTest::getRandomIds(std::vector& ids) { + hidl_vec stateSpaces; + getStateSpaces(stateSpaces); + + if (stateSpaces.size() == 0) { + return; + } + + for (auto stateSpace : stateSpaces) { + ids.push_back(stateSpace.powerEntityId); + } + + unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); + auto gen = std::default_random_engine(seed); + std::uniform_int_distribution dist(1, stateSpaces.size()); + + std::shuffle(ids.begin(), ids.end(), gen); + ids.resize(dist(gen)); +} + +// Each PowerEntity must have a valid name +TEST_F(PowerStatsHidlTest, ValidatePowerEntityNames) { + hidl_vec infos; + getInfos(infos); + for (auto info : infos) { + ASSERT_NE(info.powerEntityName, ""); + } +} + +// Each PowerEntity must have a unique ID +TEST_F(PowerStatsHidlTest, ValidatePowerEntityIds) { + hidl_vec infos; + getInfos(infos); + + set ids; + for (auto info : infos) { + ASSERT_TRUE(ids.insert(info.powerEntityId).second); + } +} + +// Each PowerEntityStateSpace must have an associated PowerEntityInfo +TEST_F(PowerStatsHidlTest, ValidateStateInfoAssociation) { + hidl_vec infos; + getInfos(infos); + + hidl_vec stateSpaces; + getStateSpaces(stateSpaces); + + std::set ids; + for (auto info : infos) { + ids.insert(info.powerEntityId); + } + + for (auto stateSpace : stateSpaces) { + ASSERT_NE(ids.count(stateSpace.powerEntityId), 0); + } +} + +// Each state must have a valid name +TEST_F(PowerStatsHidlTest, ValidateStateNames) { + hidl_vec stateSpaces; + getStateSpaces(stateSpaces); + + for (auto stateSpace : stateSpaces) { + for (auto state : stateSpace.states) { + ASSERT_NE(state.powerEntityStateName, ""); + } + } +} + +// Each state must have an ID that is unique to the PowerEntityStateSpace +TEST_F(PowerStatsHidlTest, ValidateStateUniqueIds) { + hidl_vec stateSpaces; + getStateSpaces(stateSpaces); + + for (auto stateSpace : stateSpaces) { + set stateIds; + for (auto state : stateSpace.states) { + ASSERT_TRUE(stateIds.insert(state.powerEntityStateId).second); + } + } +} + +// getPowerEntityStateInfo must support passing in requested IDs +// Results must contain state space information for all requested IDs +TEST_F(PowerStatsHidlTest, ValidateStateInfoAssociationSelect) { + std::vector randomIds; + getRandomIds(randomIds); + + if (randomIds.empty()) { + return; + } + + hidl_vec stateSpaces; + getStateSpaces(stateSpaces, randomIds); + + ASSERT_EQ(stateSpaces.size(), randomIds.size()); + + std::set ids; + for (auto id : randomIds) { + ids.insert(id); + } + for (auto stateSpace : stateSpaces) { + ASSERT_NE(ids.count(stateSpace.powerEntityId), 0); + } +} + +// Requested state space info must match initially obtained stateinfos +TEST_F(PowerStatsHidlTest, ValidateStateInfoSelect) { + hidl_vec stateSpaces; + getStateSpaces(stateSpaces); + if (stateSpaces.size() == 0) { + return; + } + + std::vector randomIds; + getRandomIds(randomIds); + ASSERT_FALSE(randomIds.empty()); + + hidl_vec selectedStateSpaces; + getStateSpaces(selectedStateSpaces, randomIds); + + std::map stateSpaceMap; + for (auto stateSpace : stateSpaces) { + stateSpaceMap[stateSpace.powerEntityId] = stateSpace; + } + + for (auto stateSpace : selectedStateSpaces) { + auto it = stateSpaceMap.find(stateSpace.powerEntityId); + ASSERT_NE(it, stateSpaceMap.end()); + + ASSERT_EQ(stateSpace.states.size(), it->second.states.size()); + for (auto i = 0; i < stateSpace.states.size(); i++) { + ASSERT_EQ(stateSpace.states[i].powerEntityStateId, + it->second.states[i].powerEntityStateId); + ASSERT_EQ(stateSpace.states[i].powerEntityStateName, + it->second.states[i].powerEntityStateName); + } + } +} + +// stateResidencyResults must contain results for every PowerEntityStateSpace +// returned by getPowerEntityStateInfo +TEST_F(PowerStatsHidlTest, ValidateResidencyResultsAssociation) { + hidl_vec stateSpaces; + getStateSpaces(stateSpaces); + + hidl_vec results; + getResidencyResults(results); + + std::map resultsMap; + for (auto result : results) { + resultsMap[result.powerEntityId] = result; + } + + for (auto stateSpace : stateSpaces) { + auto it = resultsMap.find(stateSpace.powerEntityId); + ASSERT_NE(it, resultsMap.end()); + + ASSERT_EQ(stateSpace.states.size(), it->second.stateResidencyData.size()); + + std::set stateIds; + for (auto residency : it->second.stateResidencyData) { + stateIds.insert(residency.powerEntityStateId); + } + + for (auto state : stateSpace.states) { + ASSERT_NE(stateIds.count(state.powerEntityStateId), 0); + } + } +} + +// getPowerEntityStateResidencyData must support passing in requested IDs +// stateResidencyResults must contain results for each PowerEntityStateSpace +// returned by getPowerEntityStateInfo +TEST_F(PowerStatsHidlTest, ValidateResidencyResultsAssociationSelect) { + std::vector randomIds; + getRandomIds(randomIds); + if (randomIds.empty()) { + return; + } + + hidl_vec stateSpaces; + getStateSpaces(stateSpaces, randomIds); + + hidl_vec results; + getResidencyResults(results, randomIds); + + std::map resultsMap; + for (auto result : results) { + resultsMap[result.powerEntityId] = result; + } + + for (auto stateSpace : stateSpaces) { + auto it = resultsMap.find(stateSpace.powerEntityId); + ASSERT_NE(it, resultsMap.end()); + + ASSERT_EQ(stateSpace.states.size(), it->second.stateResidencyData.size()); + + std::set stateIds; + for (auto residency : it->second.stateResidencyData) { + stateIds.insert(residency.powerEntityStateId); + } + + for (auto state : stateSpace.states) { + ASSERT_NE(stateIds.count(state.powerEntityStateId), 0); + } + } +} + +TEST_F(PowerStatsHidlTest, ValidateRailInfo) { + hidl_vec rails[2]; + Status s; + auto cb = [&rails, &s](hidl_vec rail_subsys, Status status) { + rails[0] = rail_subsys; + s = status; + }; + Return ret = service_->getRailInfo(cb); + EXPECT_TRUE(ret.isOk()); + if (s == Status::SUCCESS) { + /* Rails size should be non-zero on SUCCESS*/ + ASSERT_NE(rails[0].size(), 0); + /* check if indices returned are unique*/ + set ids; + for (auto rail : rails[0]) { + ASSERT_TRUE(ids.insert(rail.index).second); + } + auto cb = [&rails, &s](hidl_vec rail_subsys, Status status) { + rails[1] = rail_subsys; + s = status; + }; + Return ret = service_->getRailInfo(cb); + EXPECT_TRUE(ret.isOk()); + ASSERT_EQ(s, Status::SUCCESS); + ASSERT_EQ(rails[0].size(), rails[1].size()); + /* check if data returned by two calls to getRailInfo is same*/ + for (int i = 0; i < rails[0].size(); i++) { + ASSERT_NE(rails[0][i].railName, ""); + ASSERT_NE(rails[0][i].subsysName, ""); + int j = 0; + bool match = false; + for (j = 0; j < rails[1].size(); j++) { + if (rails[0][i].index == rails[1][j].index) { + ASSERT_EQ(rails[0][i].railName, rails[1][i].railName); + ASSERT_EQ(rails[0][i].subsysName, rails[1][i].subsysName); + match = true; + break; + } + } + ASSERT_TRUE(match); + } + } else if (s == Status::FILESYSTEM_ERROR) { + ALOGI("ValidateRailInfo returned FILESYSTEM_ERROR"); + ASSERT_EQ(rails[0].size(), 0); + } else if (s == Status::NOT_SUPPORTED) { + ALOGI("ValidateRailInfo returned NOT_SUPPORTED"); + ASSERT_EQ(rails[0].size(), 0); + } else if (s == Status::INVALID_INPUT) { + ALOGI("ValidateRailInfo returned INVALID_INPUT"); + ASSERT_EQ(rails[0].size(), 0); + } else if (s == Status::INSUFFICIENT_RESOURCES) { + ALOGI("ValidateRailInfo returned INSUFFICIENT_RESOURCES"); + ASSERT_EQ(rails[0].size(), 0); + } +} + +TEST_F(PowerStatsHidlTest, ValidateAllPowerData) { + hidl_vec measurements[2]; + Status s; + auto cb = [&measurements, &s](hidl_vec measure, Status status) { + measurements[0] = measure; + s = status; + }; + Return ret = service_->getEnergyData(hidl_vec(), cb); + EXPECT_TRUE(ret.isOk()); + if (s == Status::SUCCESS) { + /*measurements size should be non-zero on SUCCESS*/ + ASSERT_NE(measurements[0].size(), 0); + auto cb = [&measurements, &s](hidl_vec measure, Status status) { + measurements[1] = measure; + s = status; + }; + Return ret = service_->getEnergyData(hidl_vec(), cb); + EXPECT_TRUE(ret.isOk()); + ASSERT_EQ(s, Status::SUCCESS); + /*Both calls should returns same amount of data*/ + ASSERT_EQ(measurements[0].size(), measurements[1].size()); + /*Check is energy and timestamp are monotonically increasing*/ + for (int i = 0; i < measurements[0].size(); i++) { + int j; + for (j = 0; j < measurements[1].size(); j++) { + if (measurements[0][i].index == measurements[1][j].index) { + EXPECT_GE(measurements[1][j].timestamp, measurements[0][i].timestamp); + EXPECT_GE(measurements[1][j].energy, measurements[0][i].energy); + break; + } + } + /*Check is indices for two call match*/ + ASSERT_NE(j, measurements[1].size()); + } + } else if (s == Status::FILESYSTEM_ERROR) { + ALOGI("ValidateAllPowerData returned FILESYSTEM_ERROR"); + ASSERT_EQ(measurements[0].size(), 0); + } else if (s == Status::NOT_SUPPORTED) { + ALOGI("ValidateAllPowerData returned NOT_SUPPORTED"); + ASSERT_EQ(measurements[0].size(), 0); + } else if (s == Status::INVALID_INPUT) { + ALOGI("ValidateAllPowerData returned INVALID_INPUT"); + ASSERT_EQ(measurements[0].size(), 0); + } else if (s == Status::INSUFFICIENT_RESOURCES) { + ALOGI("ValidateAllPowerData returned INSUFFICIENT_RESOURCES"); + ASSERT_EQ(measurements[0].size(), 0); + } +} + +TEST_F(PowerStatsHidlTest, ValidateFilteredPowerData) { + hidl_vec rails; + hidl_vec measurements; + hidl_vec indices; + std::string debugString; + Status s; + auto cb = [&rails, &s](hidl_vec rail_subsys, Status status) { + rails = rail_subsys; + s = status; + }; + Return ret = service_->getRailInfo(cb); + EXPECT_TRUE(ret.isOk()); + std::time_t seed = std::time(nullptr); + std::srand(seed); + if (s == Status::SUCCESS) { + size_t sz = std::max(1, (int)(std::rand() % rails.size())); + indices.resize(sz); + for (int i = 0; i < sz; i++) { + int j = std::rand() % rails.size(); + indices[i] = rails[j].index; + debugString += std::to_string(indices[i]) + ", "; + } + debugString += "\n"; + ALOGI("ValidateFilteredPowerData for indices: %s", debugString.c_str()); + auto cb = [&measurements, &s](hidl_vec measure, Status status) { + measurements = measure; + s = status; + }; + Return ret = service_->getEnergyData(indices, cb); + EXPECT_TRUE(ret.isOk()); + if (s == Status::SUCCESS) { + /* Make sure that all the measurements are returned */ + ASSERT_EQ(sz, measurements.size()); + for (int i = 0; i < measurements.size(); i++) { + int j; + bool match = false; + /* Check that the measurement belongs to the requested index */ + for (j = 0; j < indices.size(); j++) { + if (indices[j] == measurements[i].index) { + match = true; + break; + } + } + ASSERT_TRUE(match); + } + } + } else { + /* size should be zero is stats is NOT SUCCESS */ + ASSERT_EQ(rails.size(), 0); + } +} + +void readEnergy(sp service_, uint32_t timeMs) { + std::unique_ptr mQueue; + Status s; + uint32_t railsInSample; + uint32_t totalSamples; + auto cb = [&s, &mQueue, &totalSamples, &railsInSample]( + const hardware::MQDescriptorSync& in, uint32_t numSamples, + uint32_t railsPerSample, Status status) { + mQueue.reset(new (std::nothrow) MessageQueueSync(in)); + s = status; + totalSamples = numSamples; + railsInSample = railsPerSample; + }; + service_->streamEnergyData(timeMs, 10, cb); + if (s == Status::SUCCESS) { + ASSERT_NE(nullptr, mQueue); + ASSERT_TRUE(mQueue->isValid()); + bool rc; + int sampleCount = 0; + uint32_t totalQuants = railsInSample * totalSamples; + uint64_t timeout_ns = 10000000000; + if (totalSamples > 0) { + uint32_t batch = std::max(1, (int)((std::rand() % totalSamples) * railsInSample)); + ALOGI("Read energy, timsMs: %u, batch: %u", timeMs, batch); + std::vector data(batch); + while (sampleCount < totalQuants) { + rc = mQueue->readBlocking(&data[0], batch, timeout_ns); + if (rc == false) { + break; + } + sampleCount = sampleCount + batch; + if (batch > totalQuants - sampleCount) { + batch = 1; + } + } + ASSERT_EQ(totalQuants, sampleCount); + } + } else if (s == Status::FILESYSTEM_ERROR) { + ASSERT_FALSE(mQueue->isValid()); + ASSERT_EQ(totalSamples, 0); + ASSERT_EQ(railsInSample, 0); + } else if (s == Status::NOT_SUPPORTED) { + ASSERT_FALSE(mQueue->isValid()); + ASSERT_EQ(totalSamples, 0); + ASSERT_EQ(railsInSample, 0); + } else if (s == Status::INVALID_INPUT) { + ASSERT_FALSE(mQueue->isValid()); + ASSERT_EQ(totalSamples, 0); + ASSERT_EQ(railsInSample, 0); + } else if (s == Status::INSUFFICIENT_RESOURCES) { + ASSERT_FALSE(mQueue->isValid()); + ASSERT_EQ(totalSamples, 0); + ASSERT_EQ(railsInSample, 0); + } +} + +TEST_F(PowerStatsHidlTest, StreamEnergyData) { + std::time_t seed = std::time(nullptr); + std::srand(seed); + std::thread thread1 = std::thread(readEnergy, service_, std::rand() % 5000); + thread1.join(); +} + +int main(int argc, char** argv) { + ::testing::AddGlobalTestEnvironment(PowerStatsHidlEnv::Instance()); + ::testing::InitGoogleTest(&argc, argv); + PowerStatsHidlEnv::Instance()->init(&argc, argv); + int status = RUN_ALL_TESTS(); + LOG(INFO) << "Test result = " << status; + return status; +} +} // namespace vts +} // namespace stats +} // namespace power +} // namespace android