Expose HWUI metrics via statsd

Add atom definition for HWUI stats. Implement a C++
statsd puller inside GraphicsStatsService service.
GraphicsStatsService has new private API, which
returns a serialized proto with HWUI stats grouped
by application package and version.

Test: Ran "adb shell cmd stats pull-source 10068"
Test: Ran "statsd_testdrive 10068" and it looks OK
Bug: 142665516
Change-Id: I400c0dbf9e25181d36f9018688b03d86839ac3de
This commit is contained in:
Stan Iliev 2019-08-16 13:43:08 -04:00
parent f2be9da2fc
commit 637ba5e8da
11 changed files with 534 additions and 18 deletions

View File

@ -415,6 +415,13 @@ filegroup {
path: "core/java",
}
filegroup {
name: "graphicsstats_proto",
srcs: [
"libs/hwui/protos/graphicsstats.proto",
],
}
filegroup {
name: "libvibrator_aidl",
srcs: [

View File

@ -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;
}

View File

@ -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 {

View File

@ -15,6 +15,7 @@
*/
#include "ProfileData.h"
#include "Properties.h"
#include <cinttypes>
@ -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) {

View File

@ -16,6 +16,7 @@
#pragma once
#include "Properties.h"
#include "utils/Macros.h"
#include <utils/Timers.h>
@ -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<int>(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

View File

@ -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 {

View File

@ -16,24 +16,28 @@
#include "GraphicsStatsService.h"
#include "JankTracker.h"
#include "protos/graphicsstats.pb.h"
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <log/log.h>
#include <errno.h>
#include <fcntl.h>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <inttypes.h>
#include <log/log.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <map>
#include <vector>
#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<std::string, int64_t> DumpKey;
std::map<DumpKey, protos::GraphicsStatsProto> 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() ? "<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<std::vector<unsigned char>> 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<const unsigned char*>(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 */

View File

@ -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);

View File

@ -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<HistoricalBuffer> 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<File> 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);

View File

@ -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",

View File

@ -23,6 +23,16 @@
#include <nativehelper/ScopedUtfChars.h>
#include <JankTracker.h>
#include <service/GraphicsStatsService.h>
#include <stats_pull_atom_callback.h>
#include <stats_event.h>
#include <statslog.h>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <android/util/ProtoOutputStream.h>
#include "android/graphics/Utils.h"
#include "core_jni_helpers.h"
#include "protos/graphicsstats.pb.h"
#include <cstring>
#include <memory>
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<GraphicsStatsService::Dump*>(dumpPtr);
std::vector<uint8_t>* result = new std::vector<uint8_t>();
GraphicsStatsService::finishDumpInMemory(dump,
[](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) {
std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2);
if (outBuffer->size() < totalSize) {
outBuffer->resize(totalSize);
}
std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize);
}, env, result);
return reinterpret_cast<jlong>(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<void**>(&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<uint8_t> 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<uint8_t> 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<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata);
std::unique_ptr<std::vector<uint8_t>> 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
} // namespace android