Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c9043817 authored by Stan Iliev's avatar Stan Iliev
Browse files

Refactor GraphicsStatsService for updateability

Move GraphicsStatsService to android.graphics package.
Move GraphicsStatsService JNI from libservices.core to
libandroid_runtime.
Declare GraphicsStatsService ctor as the only @SystemApi.
Remove MemoryFile usage from GraphicsStatsService, but use
SharedMemory and other SDK APIs instead. This is done to
avoid using unstable API MemoryFile.getFileDescriptor.
Propose new SharedMemory.getFdDup API for next release, which
is hidden for now.
Refactor statsd puller to avoid proto serialization by moving
data directly into AStatsEventList.
"libprotoutil" is added as a static dependancy to libhwui, which
should be fine because its implementation does not link anything.

Bug: 146353313
Test: Ran "adb shell cmd stats pull-source 10068"
Test: Passed unit tests and GraphicsStatsValidationTest CTS
Change-Id: If16c5addbd519cba33e03bd84ac312595032e0e1
parent 76d19db2
Loading
Loading
Loading
Loading
+0 −7
Original line number Diff line number Diff line
@@ -444,13 +444,6 @@ filegroup {
    path: "core/java",
}

filegroup {
    name: "graphicsstats_proto",
    srcs: [
        "libs/hwui/protos/graphicsstats.proto",
    ],
}

filegroup {
    name: "libvibrator_aidl",
    srcs: [
+15 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import dalvik.system.VMRuntime;

import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.DirectByteBuffer;
import java.nio.NioUtils;
@@ -272,6 +273,20 @@ public final class SharedMemory implements Parcelable, Closeable {
        dest.writeFileDescriptor(mFileDescriptor);
    }

    /**
     * Returns a dup'd ParcelFileDescriptor from the SharedMemory FileDescriptor.
     * This obeys standard POSIX semantics, where the
     * new file descriptor shared state such as file position with the
     * original file descriptor.
     * TODO: propose this method as a public or system API for next release to achieve parity with
     *  NDK ASharedMemory_dupFromJava.
     *
     * @hide
     */
    public ParcelFileDescriptor getFdDup() throws IOException {
        return ParcelFileDescriptor.dup(mFileDescriptor);
    }

    public static final @android.annotation.NonNull Parcelable.Creator<SharedMemory> CREATOR =
            new Parcelable.Creator<SharedMemory>() {
        @Override
+2 −0
Original line number Diff line number Diff line
@@ -182,6 +182,7 @@ cc_library_shared {
                "android_hardware_UsbRequest.cpp",
                "android_hardware_location_ActivityRecognitionHardware.cpp",
                "android_util_FileObserver.cpp",
                "android/graphics/GraphicsStatsService.cpp",
                "android/graphics/SurfaceTexture.cpp",
                "android/opengl/poly_clip.cpp", // TODO: .arm
                "android/opengl/util.cpp",
@@ -273,6 +274,7 @@ cc_library_shared {
                "libstats_jni",
                "libstatslog",
                "server_configurable_flags",
                "libstatspull",
            ],
            export_shared_lib_headers: [
                // AndroidRuntime.h depends on nativehelper/jni.h
+195 −0
Original line number Diff line number Diff line
@@ -16,23 +16,17 @@

#define LOG_TAG "GraphicsStatsService"

#include <JankTracker.h>
#include <jni.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
#include <JankTracker.h>
#include <service/GraphicsStatsService.h>
#include <stats_pull_atom_callback.h>
#include <stats_event.h>
#include <stats_pull_atom_callback.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 {

@@ -43,8 +37,10 @@ static jint getAshmemSize(JNIEnv*, jobject) {
}

static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) {
    GraphicsStatsService::Dump* dump = GraphicsStatsService::createDump(fd, isProto
            ? GraphicsStatsService::DumpType::Protobuf : GraphicsStatsService::DumpType::Text);
    GraphicsStatsService::Dump* dump =
            GraphicsStatsService::createDump(fd,
                                             isProto ? GraphicsStatsService::DumpType::Protobuf
                                                     : GraphicsStatsService::DumpType::Text);
    return reinterpret_cast<jlong>(dump);
}

@@ -57,16 +53,19 @@ static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstrin
    if (jdata != nullptr) {
        buffer.reset(jdata);
        LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
                "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData));
                            "Buffer size %zu doesn't match expected %zu!", buffer.size(),
                            sizeof(ProfileData));
        data = reinterpret_cast<const ProfileData*>(buffer.get());
    }
    if (jpath != nullptr) {
        ScopedUtfChars pathChars(env, jpath);
        LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
        LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(),
                            "Failed to get path chars");
        path.assign(pathChars.c_str(), pathChars.size());
    }
    ScopedUtfChars packageChars(env, jpackage);
    LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars");
    LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(),
                        "Failed to get path chars");
    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
    LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer");

@@ -87,29 +86,24 @@ static void finishDump(JNIEnv*, jobject, jlong dumpPtr) {
    GraphicsStatsService::finishDump(dump);
}

static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) {
static void finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr, jlong pulledData,
                               jboolean lastFullDay) {
    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);
    AStatsEventList* data = reinterpret_cast<AStatsEventList*>(pulledData);
    GraphicsStatsService::finishDumpInMemory(dump, data, lastFullDay == JNI_TRUE);
}

static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
                       jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
    ScopedByteArrayRO buffer(env, jdata);
    LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
            "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData));
                        "Buffer size %zu doesn't match expected %zu!", buffer.size(),
                        sizeof(ProfileData));
    ScopedUtfChars pathChars(env, jpath);
    LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
    ScopedUtfChars packageChars(env, jpackage);
    LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars");
    LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(),
                        "Failed to get path chars");

    const std::string path(pathChars.c_str(), pathChars.size());
    const std::string package(packageChars.c_str(), packageChars.size());
@@ -131,52 +125,6 @@ static JNIEnv* getJNIEnv() {
    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(AStatsEvent* 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);
    AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
}

static void writeGpuHistogram(AStatsEvent* 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);
    AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
}

// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
static AStatsManager_PullAtomCallbackReturn graphicsStatsPullCallback(int32_t atom_tag,
                                                                      AStatsEventList* data,
@@ -191,59 +139,16 @@ static AStatsManager_PullAtomCallbackReturn graphicsStatsPullCallback(int32_t at
    }

    for (bool lastFullDay : {true, false}) {
        jlong jdata = (jlong) env->CallLongMethod(
                    gGraphicsStatsServiceObject,
        env->CallVoidMethod(gGraphicsStatsServiceObject,
                            gGraphicsStatsService_pullGraphicsStatsMethodID,
                    (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE));
                            (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE),
                            reinterpret_cast<jlong>(data));
        if (env->ExceptionCheck()) {
            env->ExceptionDescribe();
            env->ExceptionClear();
            ALOGE("Failed to invoke graphicsstats service");
            return AStatsManager_PULL_SKIP;
        }
        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 AStatsManager_PULL_SKIP;
        }

        for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
            auto& stat = serviceDump.stats(stat_index);
            AStatsEvent* event = AStatsEventList_addStatsEvent(data);
            AStatsEvent_setAtomId(event, android::util::GRAPHICS_STATS);
            AStatsEvent_writeString(event, stat.package_name().c_str());
            AStatsEvent_writeInt64(event, (int64_t)stat.version_code());
            AStatsEvent_writeInt64(event, (int64_t)stat.stats_start());
            AStatsEvent_writeInt64(event, (int64_t)stat.stats_end());
            AStatsEvent_writeInt32(event, (int32_t)stat.pipeline());
            AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames());
            AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count());
            AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count());
            AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count());
            AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
            AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count());
            AStatsEvent_writeInt32(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.
            AStatsEvent_writeInt64(event, (int64_t)0);
            AStatsEvent_writeBool(event, !lastFullDay);
            AStatsEvent_build(event);
        }
    }
    return AStatsManager_PULL_SUCCESS;
}
@@ -267,26 +172,24 @@ static void nativeDestructor(JNIEnv* env, jobject javaObject) {
    gGraphicsStatsServiceObject = nullptr;
}

static const JNINativeMethod sMethods[] = {
    { "nGetAshmemSize", "()I", (void*) getAshmemSize },
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 },
         {"nFinishDumpInMemory", "(JJZ)V", (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));
         {"nativeDestructor", "()V", (void*)nativeDestructor}};

int register_android_graphics_GraphicsStatsService(JNIEnv* env) {
    jclass graphicsStatsService_class =
            FindClassOrDie(env, "android/graphics/GraphicsStatsService");
    gGraphicsStatsService_pullGraphicsStatsMethodID =
            GetMethodIDOrDie(env, graphicsStatsService_class, "pullGraphicsStats", "(ZJ)V");
    return jniRegisterNativeMethods(env, "android/graphics/GraphicsStatsService", sMethods,
                                    NELEM(sMethods));
}

} // namespace android
Loading