Bug 1888483 - Support power profiling on Android, r=profiler-reviewers,canaltinova.

Differential Revision: https://phabricator.services.mozilla.com/D206038
This commit is contained in:
Florian Quèze 2024-04-05 19:10:34 +00:00
parent 7c9f1121a8
commit fdaacfde35
3 changed files with 222 additions and 1 deletions

View File

@ -0,0 +1,182 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "PowerCounters.h"
#include "nsXULAppAPI.h" // for XRE_IsParentProcess
#include <dlfcn.h>
#define ALOG(args...) \
__android_log_print(ANDROID_LOG_INFO, "GeckoProfiler", ##args)
/*
* The following declarations come from the dlext.h header (not in the ndk).
* https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/include/android/dlext.h;drc=655e430b28d7404f763e7ebefe84fba5a387666d
*/
struct android_namespace_t;
typedef struct {
/** A bitmask of `ANDROID_DLEXT_` enum values. */
uint64_t flags;
/** Used by `ANDROID_DLEXT_RESERVED_ADDRESS` and
* `ANDROID_DLEXT_RESERVED_ADDRESS_HINT`. */
void* _Nullable reserved_addr;
/** Used by `ANDROID_DLEXT_RESERVED_ADDRESS` and
* `ANDROID_DLEXT_RESERVED_ADDRESS_HINT`. */
size_t reserved_size;
/** Used by `ANDROID_DLEXT_WRITE_RELRO` and `ANDROID_DLEXT_USE_RELRO`. */
int relro_fd;
/** Used by `ANDROID_DLEXT_USE_LIBRARY_FD`. */
int library_fd;
/** Used by `ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET` */
off64_t library_fd_offset;
/** Used by `ANDROID_DLEXT_USE_NAMESPACE`. */
struct android_namespace_t* _Nullable library_namespace;
} android_dlextinfo;
enum { ANDROID_DLEXT_USE_NAMESPACE = 0x200 };
extern "C"
__attribute__((visibility("default"))) void* _Nullable android_dlopen_ext(
const char* _Nullable __filename, int __flags,
const android_dlextinfo* _Nullable __info);
// See also documentation at
// https://developer.android.com/studio/profile/power-profiler#power-rails
bool GetAvailableRails(RailDescriptor*, size_t* size_of_arr);
class RailEnergy final : public BaseProfilerCount {
public:
explicit RailEnergy(RailEnergyData* data, const char* aRailName,
const char* aSubsystemName)
: BaseProfilerCount(aSubsystemName, nullptr, nullptr, "power", aRailName),
mDataPtr(data),
mLastTimestamp(0) {}
~RailEnergy() {}
RailEnergy(const RailEnergy&) = delete;
RailEnergy& operator=(const RailEnergy&) = delete;
CountSample Sample() override {
CountSample result = {
// RailEnergyData.energy is in microwatt-seconds (uWs)
// we need to return values in picowatt-hour.
.count = static_cast<int64_t>(mDataPtr->energy * 1e3 / 3.6),
.number = 0,
.isSampleNew = mDataPtr->timestamp != mLastTimestamp,
};
mLastTimestamp = mDataPtr->timestamp;
return result;
}
private:
RailEnergyData* mDataPtr;
uint64_t mLastTimestamp;
};
PowerCounters::PowerCounters() {
if (!XRE_IsParentProcess()) {
// Energy meters are global, so only sample them on the parent.
return;
}
// A direct dlopen call on libperfetto_android_internal.so fails with a
// namespace error because libperfetto_android_internal.so is missing in
// /etc/public.libraries.txt
// Instead, use android_dlopen_ext with the "default" namespace.
void* libcHandle = dlopen("libc.so", RTLD_LAZY);
if (!libcHandle) {
ALOG("failed to dlopen libc: %s", dlerror());
return;
}
struct android_namespace_t* (*android_get_exported_namespace)(const char*) =
reinterpret_cast<struct android_namespace_t* (*)(const char*)>(
dlsym(libcHandle, "__loader_android_get_exported_namespace"));
if (!android_get_exported_namespace) {
ALOG("failed to get __loader_android_get_exported_namespace: %s",
dlerror());
return;
}
struct android_namespace_t* ns = android_get_exported_namespace("default");
const android_dlextinfo dlextinfo = {
.flags = ANDROID_DLEXT_USE_NAMESPACE,
.library_namespace = ns,
};
mLibperfettoModule = android_dlopen_ext("libperfetto_android_internal.so",
RTLD_LOCAL | RTLD_LAZY, &dlextinfo);
MOZ_ASSERT(mLibperfettoModule);
if (!mLibperfettoModule) {
ALOG("failed to get libperfetto handle: %s", dlerror());
return;
}
decltype(&GetAvailableRails) getAvailableRails =
reinterpret_cast<decltype(&GetAvailableRails)>(
dlsym(mLibperfettoModule, "GetAvailableRails"));
if (!getAvailableRails) {
ALOG("failed to get GetAvailableRails pointer: %s", dlerror());
return;
}
constexpr size_t kMaxNumRails = 32;
if (!mRailDescriptors.resize(kMaxNumRails)) {
ALOG("failed to grow mRailDescriptors");
return;
}
size_t numRails = mRailDescriptors.length();
getAvailableRails(&mRailDescriptors[0], &numRails);
mRailDescriptors.shrinkTo(numRails);
ALOG("found %zu rails", numRails);
if (numRails == 0) {
// We will see 0 rails either if the device has no support for power
// profiling or if the SELinux policy blocks access (ie. on a non-rooted
// device).
return;
}
if (!mRailEnergyData.resize(numRails)) {
ALOG("failed to grow mRailEnergyData");
return;
}
for (size_t i = 0; i < numRails; ++i) {
RailDescriptor& rail = mRailDescriptors[i];
ALOG("rail %zu, name: %s, subsystem: %s", i, rail.rail_name,
rail.subsys_name);
RailEnergy* railEnergy =
new RailEnergy(&mRailEnergyData[i], rail.rail_name, rail.subsys_name);
if (!mCounters.emplaceBack(railEnergy)) {
delete railEnergy;
}
}
mGetRailEnergyData = reinterpret_cast<decltype(&GetRailEnergyData)>(
dlsym(mLibperfettoModule, "GetRailEnergyData"));
if (!mGetRailEnergyData) {
ALOG("failed to get GetRailEnergyData pointer");
return;
}
}
PowerCounters::~PowerCounters() {
if (mLibperfettoModule) {
dlclose(mLibperfettoModule);
}
for (auto* railEnergy : mCounters) {
delete railEnergy;
}
mCounters.clear();
}
void PowerCounters::Sample() {
// Energy meters are global, so only sample them on the parent.
// Also return early if we failed to access the GetRailEnergyData symbol.
if (!XRE_IsParentProcess() || !mGetRailEnergyData) {
return;
}
size_t length = mRailEnergyData.length();
mGetRailEnergyData(&mRailEnergyData[0], &length);
}

View File

@ -19,10 +19,39 @@ class ProcessPower;
#if defined(GP_PLAT_amd64_darwin)
class RAPL;
#endif
#if defined(GP_PLAT_arm64_android)
/*
* These declarations come from:
* https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/src/android_internal/power_stats.h;l=34-52;drc=1777bdef274bcfbccd4e6f8b6d00a1bac48a8645
*/
struct RailDescriptor {
// Index corresponding to the rail
uint32_t index;
// Name of the rail
char rail_name[64];
// Name of the subsystem to which this rail belongs
char subsys_name[64];
// Hardware sampling rate
uint32_t sampling_rate;
};
struct RailEnergyData {
// Index corresponding to RailDescriptor.index
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;
};
bool GetRailEnergyData(RailEnergyData*, size_t* size_of_arr);
#endif
class PowerCounters {
public:
#if defined(_MSC_VER) || defined(GP_OS_darwin) || defined(GP_PLAT_amd64_linux)
#if defined(_MSC_VER) || defined(GP_OS_darwin) || \
defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_arm64_android)
explicit PowerCounters();
~PowerCounters();
void Sample();
@ -47,6 +76,12 @@ class PowerCounters {
#if defined(GP_PLAT_amd64_darwin)
RAPL* mRapl;
#endif
#if defined(GP_PLAT_arm64_android)
void* mLibperfettoModule = nullptr;
decltype(&GetRailEnergyData) mGetRailEnergyData = nullptr;
mozilla::Vector<RailDescriptor> mRailDescriptors;
mozilla::Vector<RailEnergyData> mRailEnergyData;
#endif
};
#endif /* ndef TOOLS_POWERCOUNTERS_H_ */

View File

@ -85,6 +85,10 @@ if CONFIG["MOZ_GECKO_PROFILER"]:
UNIFIED_SOURCES += [
"core/PowerCounters-linux.cpp",
]
elif CONFIG["TARGET_CPU"] == "aarch64" and CONFIG["OS_TARGET"] == "Android":
SOURCES += [
"core/PowerCounters-android.cpp",
]
if CONFIG["TARGET_CPU"] == "arm" and CONFIG["OS_TARGET"] != "FreeBSD":
SOURCES += [
"core/EHABIStackWalk.cpp",