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:
parent
f2be9da2fc
commit
637ba5e8da
@ -415,6 +415,13 @@ filegroup {
|
||||
path: "core/java",
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "graphicsstats_proto",
|
||||
srcs: [
|
||||
"libs/hwui/protos/graphicsstats.proto",
|
||||
],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "libvibrator_aidl",
|
||||
srcs: [
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user