diff --git a/Android.bp b/Android.bp index 0d9cbd8daf5c..a74e3b43257e 100644 --- a/Android.bp +++ b/Android.bp @@ -415,6 +415,13 @@ filegroup { path: "core/java", } +filegroup { + name: "graphicsstats_proto", + srcs: [ + "libs/hwui/protos/graphicsstats.proto", + ], +} + filegroup { name: "libvibrator_aidl", srcs: [ diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 19b9709e1d41..f6680f3453f5 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -336,7 +336,7 @@ message Atom { } // Pulled events will start at field 10000. - // Next: 10068 + // Next: 10069 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -405,6 +405,7 @@ message Atom { VmsClientStats vms_client_stats = 10065; NotificationRemoteViews notification_remote_views = 10066; DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067; + GraphicsStats graphics_stats = 10068; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -7553,3 +7554,69 @@ message DangerousPermissionStateSampled { optional int32 permission_flags = 4; } +/** + * HWUI renders pipeline type: GL (0) or Vulkan (1). + */ +enum PipelineType { + GL = 0; + VULKAN = 1; +} + +/** + * HWUI stats for a given app. + */ +message GraphicsStats { + // The package name of the app + optional string package_name = 1; + + // The version code of the app + optional int64 version_code = 2; + + // The start & end timestamps in UTC as + // milliseconds since January 1, 1970 + // Compatible with java.util.Date#setTime() + optional int64 stats_start = 3; + + optional int64 stats_end = 4; + + // HWUI renders pipeline type: GL or Vulkan. + optional PipelineType pipeline = 5; + + // Distinct frame count. + optional int32 total_frames = 6; + + // Number of "missed vsync" events. + optional int32 missed_vsync_count = 7; + + // Number of frames in triple-buffering scenario (high input latency) + optional int32 high_input_latency_count = 8; + + // Number of "slow UI thread" events. + optional int32 slow_ui_thread_count = 9; + + // Number of "slow bitmap upload" events. + optional int32 slow_bitmap_upload_count = 10; + + // Number of "slow draw" events. + optional int32 slow_draw_count = 11; + + // Number of frames that missed their deadline (aka, visibly janked) + optional int32 missed_deadline_count = 12; + + // The frame time histogram for the package + optional FrameTimingHistogram cpu_histogram = 13 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // The gpu frame time histogram for the package + optional FrameTimingHistogram gpu_histogram = 14 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // UI mainline module version. + optional int64 version_ui_module = 15; + + // If true, these are HWUI stats for up to a 24h period for a given app from today. + // If false, these are HWUI stats for a 24h period for a given app from the last complete + // day (yesterday). Stats from yesterday stay constant, while stats from today may change as + // more apps are running / rendering. + optional bool is_today = 16; +} diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto index 557075cc5bfa..2de5b7fd45f6 100644 --- a/core/proto/android/service/graphicsstats.proto +++ b/core/proto/android/service/graphicsstats.proto @@ -32,6 +32,10 @@ message GraphicsStatsServiceDumpProto { } message GraphicsStatsProto { + enum PipelineType { + GL = 0; + VULKAN = 1; + } option (android.msg_privacy).dest = DEST_AUTOMATIC; // The package name of the app @@ -54,6 +58,9 @@ message GraphicsStatsProto { // The gpu frame time histogram for the package repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7; + + // HWUI renders pipeline type: GL or Vulkan + optional PipelineType pipeline = 8; } message GraphicsStatsJankSummaryProto { diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp index 7921662b213c..a8e36e37905d 100644 --- a/libs/hwui/ProfileData.cpp +++ b/libs/hwui/ProfileData.cpp @@ -15,6 +15,7 @@ */ #include "ProfileData.h" +#include "Properties.h" #include @@ -102,6 +103,7 @@ void ProfileData::mergeWith(const ProfileData& other) { mGPUFrameCounts[i] >>= divider; mGPUFrameCounts[i] += other.mGPUFrameCounts[i]; } + mPipelineType = other.mPipelineType; } void ProfileData::dump(int fd) const { @@ -157,6 +159,7 @@ void ProfileData::reset() { mTotalFrameCount = 0; mJankFrameCount = 0; mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC); + mPipelineType = Properties::getRenderPipelineType(); } void ProfileData::reportFrame(int64_t duration) { diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h index ccbffc6f136e..dd3ba661dd29 100644 --- a/libs/hwui/ProfileData.h +++ b/libs/hwui/ProfileData.h @@ -16,6 +16,7 @@ #pragma once +#include "Properties.h" #include "utils/Macros.h" #include @@ -65,6 +66,7 @@ public: uint32_t jankFrameCount() const { return mJankFrameCount; } nsecs_t statsStartTime() const { return mStatStartTime; } uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast(type)]; } + RenderPipelineType pipelineType() const { return mPipelineType; } struct HistogramEntry { uint32_t renderTimeMs; @@ -103,6 +105,9 @@ private: uint32_t mTotalFrameCount; uint32_t mJankFrameCount; nsecs_t mStatStartTime; + + // true if HWUI renders with Vulkan pipeline + RenderPipelineType mPipelineType; }; // For testing diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto index 0cd5c6228504..dd5676c9c961 100644 --- a/libs/hwui/protos/graphicsstats.proto +++ b/libs/hwui/protos/graphicsstats.proto @@ -29,6 +29,11 @@ message GraphicsStatsServiceDumpProto { } message GraphicsStatsProto { + enum PipelineType { + GL = 0; + VULKAN = 1; + } + // The package name of the app optional string package_name = 1; @@ -49,6 +54,9 @@ message GraphicsStatsProto { // The gpu frame time histogram for the package repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7; + + // HWUI renders pipeline type: GL or Vulkan + optional PipelineType pipeline = 8; } message GraphicsStatsJankSummaryProto { diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp index 12c5b836f711..c4186174b637 100644 --- a/libs/hwui/service/GraphicsStatsService.cpp +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -16,24 +16,28 @@ #include "GraphicsStatsService.h" -#include "JankTracker.h" -#include "protos/graphicsstats.pb.h" - -#include -#include - #include #include +#include #include +#include #include #include #include #include +#include +#include +#include + +#include "JankTracker.h" +#include "protos/graphicsstats.pb.h" + namespace android { namespace uirenderer { using namespace google::protobuf; +using namespace uirenderer::protos; constexpr int32_t sCurrentFileVersion = 1; constexpr int32_t sHeaderSize = 4; @@ -42,9 +46,9 @@ static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong" constexpr int sHistogramSize = ProfileData::HistogramSize(); constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize(); -static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, - const std::string& package, int64_t versionCode, - int64_t startTime, int64_t endTime, const ProfileData* data); +static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package, + int64_t versionCode, int64_t startTime, int64_t endTime, + const ProfileData* data); static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd); class FileDescriptor { @@ -57,7 +61,7 @@ public: } } bool valid() { return mFd != -1; } - operator int() { return mFd; } // NOLINT(google-explicit-constructor) + operator int() { return mFd; } // NOLINT(google-explicit-constructor) private: int mFd; @@ -167,6 +171,8 @@ bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::str } proto->set_package_name(package); proto->set_version_code(versionCode); + proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ? + GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN); auto summary = proto->mutable_summary(); summary->set_total_frames(summary->total_frames() + data->totalFrameCount()); summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount()); @@ -179,8 +185,8 @@ bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::str summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync)); summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT)); - summary->set_missed_deadline_count(summary->missed_deadline_count() - + data->jankTypeCount(kMissedDeadline)); + summary->set_missed_deadline_count(summary->missed_deadline_count() + + data->jankTypeCount(kMissedDeadline)); bool creatingHistogram = false; if (proto->histogram_size() == 0) { @@ -365,17 +371,69 @@ void GraphicsStatsService::saveBuffer(const std::string& path, const std::string class GraphicsStatsService::Dump { public: - Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {} + Dump(int outFd, DumpType type) : mFd(outFd), mType(type) { + if (mFd == -1 && mType == DumpType::Protobuf) { + mType = DumpType::ProtobufStatsd; + } + } int fd() { return mFd; } DumpType type() { return mType; } protos::GraphicsStatsServiceDumpProto& proto() { return mProto; } + void mergeStat(const protos::GraphicsStatsProto& stat); + void updateProto(); private: + // use package name and app version for a key + typedef std::pair DumpKey; + + std::map mStats; int mFd; DumpType mType; protos::GraphicsStatsServiceDumpProto mProto; }; +void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) { + auto dumpKey = std::make_pair(stat.package_name(), stat.version_code()); + auto findIt = mStats.find(dumpKey); + if (findIt == mStats.end()) { + mStats[dumpKey] = stat; + } else { + auto summary = findIt->second.mutable_summary(); + summary->set_total_frames(summary->total_frames() + stat.summary().total_frames()); + summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames()); + summary->set_missed_vsync_count(summary->missed_vsync_count() + + stat.summary().missed_vsync_count()); + summary->set_high_input_latency_count(summary->high_input_latency_count() + + stat.summary().high_input_latency_count()); + summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() + + stat.summary().slow_ui_thread_count()); + summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + + stat.summary().slow_bitmap_upload_count()); + summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count()); + summary->set_missed_deadline_count(summary->missed_deadline_count() + + stat.summary().missed_deadline_count()); + for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) { + auto bucket = findIt->second.mutable_histogram(bucketIndex); + bucket->set_frame_count(bucket->frame_count() + + stat.histogram(bucketIndex).frame_count()); + } + for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size(); + bucketIndex++) { + auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex); + bucket->set_frame_count(bucket->frame_count() + + stat.gpu_histogram(bucketIndex).frame_count()); + } + findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start())); + findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end())); + } +} + +void GraphicsStatsService::Dump::updateProto() { + for (auto& stat : mStats) { + mProto.add_stats()->CopyFrom(stat.second); + } +} + GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) { return new Dump(outFd, type); } @@ -396,8 +454,9 @@ void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, path.empty() ? "" : path.c_str(), data); return; } - - if (dump->type() == DumpType::Protobuf) { + if (dump->type() == DumpType::ProtobufStatsd) { + dump->mergeStat(statsProto); + } else if (dump->type() == DumpType::Protobuf) { dump->proto().add_stats()->CopyFrom(statsProto); } else { dumpAsTextToFd(&statsProto, dump->fd()); @@ -409,7 +468,9 @@ void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) { if (!parseFromFile(path, &statsProto)) { return; } - if (dump->type() == DumpType::Protobuf) { + if (dump->type() == DumpType::ProtobufStatsd) { + dump->mergeStat(statsProto); + } else if (dump->type() == DumpType::Protobuf) { dump->proto().add_stats()->CopyFrom(statsProto); } else { dumpAsTextToFd(&statsProto, dump->fd()); @@ -424,5 +485,79 @@ void GraphicsStatsService::finishDump(Dump* dump) { delete dump; } +class MemOutputStreamLite : public io::ZeroCopyOutputStream { +public: + explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {} + virtual ~MemOutputStreamLite() {} + + virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); } + + virtual void BackUp(int count) override { mImpl.BackUp(count); } + + virtual int64 ByteCount() const override { return mImpl.ByteCount(); } + + bool Flush() { return mImpl.Flush(); } + + void copyData(const DumpMemoryFn& reader, void* param1, void* param2) { + int bufferOffset = 0; + int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize; + int totalDataLeft = totalSize; + for (auto& it : mCopyAdapter.mBuffers) { + int bufferSize = std::min(totalDataLeft, (int)it.size()); // last buffer is not full + reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2); + bufferOffset += bufferSize; + totalDataLeft -= bufferSize; + } + } + +private: + struct MemAdapter : public io::CopyingOutputStream { + // Data is stored in an array of buffers. + // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer. + std::vector> mBuffers; + int mBuffersSize = 0; // total bytes allocated in mBuffers + int mCurrentBufferUnusedSize = 0; // unused bytes in the last buffer mBuffers.back() + unsigned char* mCurrentBuffer = nullptr; // pointer to next free byte in mBuffers.back() + + explicit MemAdapter() {} + virtual ~MemAdapter() {} + + virtual bool Write(const void* buffer, int size) override { + while (size > 0) { + if (0 == mCurrentBufferUnusedSize) { + mCurrentBufferUnusedSize = + std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000); + mBuffers.emplace_back(); + mBuffers.back().resize(mCurrentBufferUnusedSize); + mCurrentBuffer = mBuffers.back().data(); + mBuffersSize += mCurrentBufferUnusedSize; + } + int dataMoved = std::min(mCurrentBufferUnusedSize, size); + memcpy(mCurrentBuffer, buffer, dataMoved); + mCurrentBufferUnusedSize -= dataMoved; + mCurrentBuffer += dataMoved; + buffer = reinterpret_cast(buffer) + dataMoved; + size -= dataMoved; + } + return true; + } + }; + + MemOutputStreamLite::MemAdapter mCopyAdapter; + io::CopyingOutputStreamAdaptor mImpl; +}; + +void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, + void* param2) { + MemOutputStreamLite stream; + dump->updateProto(); + bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush(); + delete dump; + if (!success) { + return; + } + stream.copyData(reader, param1, param2); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h index 389f599f439f..4bed96330a52 100644 --- a/libs/hwui/service/GraphicsStatsService.h +++ b/libs/hwui/service/GraphicsStatsService.h @@ -27,6 +27,9 @@ namespace protos { class GraphicsStatsProto; } +typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize, + void* param1, void* param2); + /* * The exported entry points used by GraphicsStatsService.java in f/b/services/core * @@ -40,6 +43,7 @@ public: enum class DumpType { Text, Protobuf, + ProtobufStatsd, }; ANDROID_API static void saveBuffer(const std::string& path, const std::string& package, @@ -52,6 +56,8 @@ public: int64_t startTime, int64_t endTime, const ProfileData* data); ANDROID_API static void addToDump(Dump* dump, const std::string& path); ANDROID_API static void finishDump(Dump* dump); + ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, + void* param2); // Visible for testing static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output); diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java index 70569db5e2d3..5179fa7a6eb5 100644 --- a/services/core/java/com/android/server/GraphicsStatsService.java +++ b/services/core/java/com/android/server/GraphicsStatsService.java @@ -38,11 +38,13 @@ import android.view.IGraphicsStats; import android.view.IGraphicsStatsCallback; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FastPrintWriter; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -78,6 +80,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private static final int SAVE_BUFFER = 1; private static final int DELETE_OLD = 2; + private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever. + // This isn't static because we need this to happen after registerNativeMethods, however // the class is loaded (and thus static ctor happens) before that occurs. private final int ASHMEM_SIZE = nGetAshmemSize(); @@ -121,6 +125,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { return true; } }); + nativeInit(); } /** @@ -186,6 +191,86 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { return pfd; } + // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period + // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the + // current day. + // This method is invoked from native code only. + @SuppressWarnings({"UnusedDeclaration"}) + private long pullGraphicsStats(boolean lastFullDay) throws RemoteException { + int uid = Binder.getCallingUid(); + + // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method. + // TODO: remove exception for statsd daemon after required permissions are granted. statsd + // TODO: should have these permissions granted by data/etc/platform.xml, but it does not. + if (uid != AID_STATSD) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new FastPrintWriter(sw); + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { + pw.flush(); + throw new RemoteException(sw.toString()); + } + } + + long callingIdentity = Binder.clearCallingIdentity(); + try { + return pullGraphicsStatsImpl(lastFullDay); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + private long pullGraphicsStatsImpl(boolean lastFullDay) { + long targetDay; + if (lastFullDay) { + // Get stats from yesterday. Stats stay constant, because the day is over. + targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis(); + } else { + // Get stats from today. Stats may change as more apps are run today. + targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis(); + } + + // Find active buffers for targetDay. + ArrayList buffers; + synchronized (mLock) { + buffers = new ArrayList<>(mActive.size()); + for (int i = 0; i < mActive.size(); i++) { + ActiveBuffer buffer = mActive.get(i); + if (buffer.mInfo.startTime == targetDay) { + try { + buffers.add(new HistoricalBuffer(buffer)); + } catch (IOException ex) { + // Ignore + } + } + } + } + + // Dump active and historic buffers for targetDay in a serialized + // GraphicsStatsServiceDumpProto proto. + long dump = nCreateDump(-1, true); + try { + synchronized (mFileAccessLock) { + HashSet skipList = dumpActiveLocked(dump, buffers); + buffers.clear(); + String subPath = String.format("%d", targetDay); + File dateDir = new File(mGraphicsStatsDir, subPath); + if (dateDir.exists()) { + for (File pkg : dateDir.listFiles()) { + for (File version : pkg.listFiles()) { + File data = new File(version, "total"); + if (skipList.contains(data)) { + continue; + } + nAddToDump(dump, data.getAbsolutePath()); + } + } + } + } + } finally { + return nFinishDumpInMemory(dump); + } + } + private ParcelFileDescriptor getPfd(MemoryFile file) { try { if (!file.getFileDescriptor().valid()) { @@ -379,12 +464,21 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } } + @Override + protected void finalize() throws Throwable { + nativeDestructor(); + } + + private native void nativeInit(); + private static native void nativeDestructor(); + private static native int nGetAshmemSize(); private static native long nCreateDump(int outFd, boolean isProto); private static native void nAddToDump(long dump, String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); private static native void nAddToDump(long dump, String path); private static native void nFinishDump(long dump); + private static native long nFinishDumpInMemory(long dump); private static native void nSaveBuffer(String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 1ad6e86bae84..03969b01f3f9 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -13,6 +13,7 @@ cc_library_static { ], srcs: [ + ":graphicsstats_proto", "BroadcastRadio/JavaRef.cpp", "BroadcastRadio/NativeCallbackThread.cpp", "BroadcastRadio/BroadcastRadioService.cpp", @@ -103,6 +104,11 @@ cc_defaults { "libinputflinger", "libinputflinger_base", "libinputservice", + "libprotobuf-cpp-lite", + "libprotoutil", + "libstatspull", + "libstatssocket", + "libstatslog", "libschedulerservicehidl", "libsensorservice", "libsensorservicehidl", diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp index d1d253b94713..9353fbd71214 100644 --- a/services/core/jni/com_android_server_GraphicsStatsService.cpp +++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp @@ -23,6 +23,16 @@ #include #include #include +#include +#include +#include +#include +#include +#include "android/graphics/Utils.h" +#include "core_jni_helpers.h" +#include "protos/graphicsstats.pb.h" +#include +#include namespace android { @@ -77,6 +87,20 @@ static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { GraphicsStatsService::finishDump(dump); } +static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) { + GraphicsStatsService::Dump* dump = reinterpret_cast(dumpPtr); + std::vector* result = new std::vector(); + GraphicsStatsService::finishDumpInMemory(dump, + [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) { + std::vector* outBuffer = reinterpret_cast*>(param2); + if (outBuffer->size() < totalSize) { + outBuffer->resize(totalSize); + } + std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize); + }, env, result); + return reinterpret_cast(result); +} + static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { ScopedByteArrayRO buffer(env, jdata); @@ -93,19 +117,173 @@ static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpacka GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); } +static jobject gGraphicsStatsServiceObject = nullptr; +static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID; + +static JNIEnv* getJNIEnv() { + JavaVM* vm = AndroidRuntime::getJavaVM(); + JNIEnv* env = nullptr; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; +} + +using namespace google::protobuf; + +// Field ids taken from FrameTimingHistogram message in atoms.proto +#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 +#define FRAME_COUNTS_FIELD_NUMBER 2 + +static void writeCpuHistogram(stats_event* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector outVector; + proto.serializeToVector(&outVector); + stats_event_write_byte_array(event, outVector.data(), outVector.size()); +} + +static void writeGpuHistogram(stats_event* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector outVector; + proto.serializeToVector(&outVector); + stats_event_write_byte_array(event, outVector.data(), outVector.size()); +} + +// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. +static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data, + const void* cookie) { + JNIEnv* env = getJNIEnv(); + if (!env) { + return false; + } + if (gGraphicsStatsServiceObject == nullptr) { + ALOGE("Failed to get graphicsstats service"); + return false; + } + + for (bool lastFullDay : {true, false}) { + jlong jdata = (jlong) env->CallLongMethod( + gGraphicsStatsServiceObject, + gGraphicsStatsService_pullGraphicsStatsMethodID, + (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + ALOGE("Failed to invoke graphicsstats service"); + return false; + } + if (!jdata) { + // null means data is not available for that day. + continue; + } + android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump; + std::vector* buffer = reinterpret_cast*>(jdata); + std::unique_ptr> bufferRelease(buffer); + int dataSize = buffer->size(); + if (!dataSize) { + // Data is not available for that day. + continue; + } + io::ArrayInputStream input{buffer->data(), dataSize}; + bool success = serviceDump.ParseFromZeroCopyStream(&input); + if (!success) { + ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'", + serviceDump.InitializationErrorString().c_str(), dataSize); + return false; + } + + for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { + auto& stat = serviceDump.stats(stat_index); + stats_event* event = add_stats_event_to_pull_data(data); + stats_event_set_atom_id(event, android::util::GRAPHICS_STATS); + stats_event_write_string8(event, stat.package_name().c_str()); + stats_event_write_int64(event, (int64_t)stat.version_code()); + stats_event_write_int64(event, (int64_t)stat.stats_start()); + stats_event_write_int64(event, (int64_t)stat.stats_end()); + stats_event_write_int32(event, (int32_t)stat.pipeline()); + stats_event_write_int32(event, (int32_t)stat.summary().total_frames()); + stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count()); + stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count()); + stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count()); + writeCpuHistogram(event, stat); + writeGpuHistogram(event, stat); + // TODO: fill in UI mainline module version, when the feature is available. + stats_event_write_int64(event, (int64_t)0); + stats_event_write_bool(event, !lastFullDay); + stats_event_build(event); + } + } + return true; +} + +// Register a puller for GRAPHICS_STATS atom with the statsd service. +static void nativeInit(JNIEnv* env, jobject javaObject) { + gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); + pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds + .timeout_ns = 2 * NS_PER_SEC, // 2 seconds + .additive_fields = nullptr, + .additive_fields_size = 0}; + register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback, + &metadata, nullptr); +} + +static void nativeDestructor(JNIEnv* env, jobject javaObject) { + //TODO: Unregister the puller callback when a new API is available. + env->DeleteGlobalRef(gGraphicsStatsServiceObject); + gGraphicsStatsServiceObject = nullptr; +} + static const JNINativeMethod sMethods[] = { { "nGetAshmemSize", "()I", (void*) getAshmemSize }, { "nCreateDump", "(IZ)J", (void*) createDump }, { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump }, { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump }, { "nFinishDump", "(J)V", (void*) finishDump }, + { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory }, { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer }, + { "nativeInit", "()V", (void*) nativeInit }, + { "nativeDestructor", "()V", (void*)nativeDestructor } }; int register_android_server_GraphicsStatsService(JNIEnv* env) { + jclass graphicsStatsService_class = FindClassOrDie(env, + "com/android/server/GraphicsStatsService"); + gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env, + graphicsStatsService_class, "pullGraphicsStats", "(Z)J"); return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService", sMethods, NELEM(sMethods)); } -} // namespace android \ No newline at end of file +} // namespace android