blob: 55a0c0a63976b9d8f850aea11a2cab404a36189a [file] [log] [blame]
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "GpuService.h"
#include <android-base/stringprintf.h>
#include <binder/IPCThreadState.h>
#include <binder/IResultReceiver.h>
#include <binder/Parcel.h>
#include <binder/PermissionCache.h>
#include <cutils/properties.h>
#include <private/android_filesystem_config.h>
#include <utils/String8.h>
#include <utils/Trace.h>
#include <array>
#include <fstream>
#include <sstream>
#include <sys/types.h>
#include <vkjson.h>
#include <unistd.h>
#include "gpustats/GpuStats.h"
namespace android {
using base::StringAppendF;
namespace {
status_t cmdHelp(int out);
status_t cmdVkjson(int out, int err);
void dumpGameDriverInfo(std::string* result);
void dumpMemoryInfo(std::string* result, const GpuMemoryMap& memories, uint32_t pid);
} // namespace
const String16 sDump("android.permission.DUMP");
const char* const GpuService::SERVICE_NAME = "gpu";
GpuService::GpuService() : mGpuStats(std::make_unique<GpuStats>()){};
void GpuService::setGpuStats(const std::string& driverPackageName,
const std::string& driverVersionName, uint64_t driverVersionCode,
int64_t driverBuildTime, const std::string& appPackageName,
const int32_t vulkanVersion, GpuStatsInfo::Driver driver,
bool isDriverLoaded, int64_t driverLoadingTime) {
mGpuStats->insert(driverPackageName, driverVersionName, driverVersionCode, driverBuildTime,
appPackageName, vulkanVersion, driver, isDriverLoaded, driverLoadingTime);
}
status_t GpuService::getGpuStatsGlobalInfo(std::vector<GpuStatsGlobalInfo>* outStats) const {
mGpuStats->pullGlobalStats(outStats);
return OK;
}
status_t GpuService::getGpuStatsAppInfo(std::vector<GpuStatsAppInfo>* outStats) const {
mGpuStats->pullAppStats(outStats);
return OK;
}
void GpuService::setTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode,
const GpuStatsInfo::Stats stats, const uint64_t value) {
mGpuStats->insertTargetStats(appPackageName, driverVersionCode, stats, value);
}
bool isExpectedFormat(const char* str) {
// Should match in order:
// gpuaddr useraddr size id flags type usage sglen mapsize eglsrf eglimg
std::istringstream iss;
iss.str(str);
std::string word;
iss >> word;
if (word != "gpuaddr") { return false; }
iss >> word;
if (word != "useraddr") { return false; }
iss >> word;
if (word != "size") { return false; }
iss >> word;
if (word != "id") { return false; }
iss >> word;
if (word != "flags") { return false; }
iss >> word;
if (word != "type") { return false; }
iss >> word;
if (word != "usage") { return false; }
iss >> word;
if (word != "sglen") { return false; }
iss >> word;
if (word != "mapsize") { return false; }
iss >> word;
if (word != "eglsrf") { return false; }
iss >> word;
if (word != "eglimg") { return false; }
return true;
}
// Queries gpu memory via Qualcomm's /d/kgsl/proc/*/mem interface.
status_t GpuService::getQCommGpuMemoryInfo(GpuMemoryMap* memories, std::string* result, int32_t dumpPid) const {
const std::string kDirectoryPath = "/d/kgsl/proc";
DIR* directory = opendir(kDirectoryPath.c_str());
if (!directory) { return PERMISSION_DENIED; }
// File Format:
// gpuaddr useraddr size id flags type usage sglen mapsize eglsrf eglimg
// 0000000000000000 0000000000000000 8359936 23 --w--pY-- gpumem VK/others( 38) 0 0 0 0
// 0000000000000000 0000000000000000 16293888 24 --wL--N-- ion surface 41 0 0 1
const bool dumpAll = dumpPid == 0;
static constexpr size_t kMaxLineLength = 1024;
static char line[kMaxLineLength];
while(dirent* subdir = readdir(directory)) {
// Skip "." and ".." in directory.
if (strcmp(subdir->d_name, ".") == 0 || strcmp(subdir->d_name, "..") == 0 ) { continue; }
std::string pid_str(subdir->d_name);
const uint32_t pid(stoi(pid_str));
if (!dumpAll && dumpPid != pid) {
continue;
}
std::string filepath(kDirectoryPath + "/" + pid_str + "/mem");
std::ifstream file(filepath);
// Check first line
file.getline(line, kMaxLineLength);
if (!isExpectedFormat(line)) {
continue;
}
if (result) {
StringAppendF(result, "%d:\n%s\n", pid, line);
}
while( file.getline(line, kMaxLineLength) ) {
if (result) {
StringAppendF(result, "%s\n", line);
}
std::istringstream iss;
iss.str(line);
// Skip gpuaddr, useraddr.
const char delimiter = ' ';
iss >> std::ws;
iss.ignore(kMaxLineLength, delimiter);
iss >> std::ws;
iss.ignore(kMaxLineLength, delimiter);
// Get size.
int64_t memsize;
iss >> memsize;
// Skip id, flags.
iss >> std::ws;
iss.ignore(kMaxLineLength, delimiter);
iss >> std::ws;
iss.ignore(kMaxLineLength, delimiter);
// Get type, usage.
std::string memtype;
std::string usage;
iss >> memtype >> usage;
// Adjust for the space in VK/others( #)
if (usage == "VK/others(") {
std::string vkTypeEnd;
iss >> vkTypeEnd;
usage.append(vkTypeEnd);
}
// Skip sglen.
iss >> std::ws;
iss.ignore(kMaxLineLength, delimiter);
// Get mapsize.
int64_t mapsize;
iss >> mapsize;
if (memsize == 0 && mapsize == 0) {
continue;
}
if (memtype == "gpumem") {
(*memories)[pid][usage].gpuMemory += memsize;
} else {
(*memories)[pid][usage].ionMemory += memsize;
}
if (mapsize > 0) {
(*memories)[pid][usage].mappedMemory += mapsize;
}
}
if (result) {
StringAppendF(result, "\n");
}
}
closedir(directory);
return OK;
}
status_t GpuService::shellCommand(int /*in*/, int out, int err, std::vector<String16>& args) {
ATRACE_CALL();
ALOGV("shellCommand");
for (size_t i = 0, n = args.size(); i < n; i++)
ALOGV(" arg[%zu]: '%s'", i, String8(args[i]).string());
if (args.size() >= 1) {
if (args[0] == String16("vkjson")) return cmdVkjson(out, err);
if (args[0] == String16("help")) return cmdHelp(out);
}
// no command, or unrecognized command
cmdHelp(err);
return BAD_VALUE;
}
status_t GpuService::doDump(int fd, const Vector<String16>& args, bool /*asProto*/) {
std::string result;
IPCThreadState* ipc = IPCThreadState::self();
const int pid = ipc->getCallingPid();
const int uid = ipc->getCallingUid();
if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) {
StringAppendF(&result, "Permission Denial: can't dump gpu from pid=%d, uid=%d\n", pid, uid);
} else {
bool dumpAll = true;
bool dumpDriverInfo = false;
bool dumpStats = false;
bool dumpMemory = false;
size_t numArgs = args.size();
int32_t pid = 0;
if (numArgs) {
dumpAll = false;
for (size_t index = 0; index < numArgs; ++index) {
if (args[index] == String16("--gpustats")) {
dumpStats = true;
} else if (args[index] == String16("--gpudriverinfo")) {
dumpDriverInfo = true;
} else if (args[index] == String16("--gpumem")) {
dumpMemory = true;
} else if (args[index].startsWith(String16("--gpumem="))) {
dumpMemory = true;
pid = atoi(String8(&args[index][9]));
}
}
}
if (dumpAll || dumpDriverInfo) {
dumpGameDriverInfo(&result);
result.append("\n");
}
if (dumpAll || dumpStats) {
mGpuStats->dump(args, &result);
result.append("\n");
}
if (dumpAll || dumpMemory) {
GpuMemoryMap memories;
// Currently only queries Qualcomm gpu memory. More will be added later.
if (getQCommGpuMemoryInfo(&memories, &result, pid) == OK) {
dumpMemoryInfo(&result, memories, pid);
result.append("\n");
}
}
}
write(fd, result.c_str(), result.size());
return NO_ERROR;
}
namespace {
status_t cmdHelp(int out) {
FILE* outs = fdopen(out, "w");
if (!outs) {
ALOGE("vkjson: failed to create out stream: %s (%d)", strerror(errno), errno);
return BAD_VALUE;
}
fprintf(outs,
"GPU Service commands:\n"
" vkjson dump Vulkan properties as JSON\n");
fclose(outs);
return NO_ERROR;
}
void vkjsonPrint(FILE* out) {
std::string json = VkJsonInstanceToJson(VkJsonGetInstance());
fwrite(json.data(), 1, json.size(), out);
fputc('\n', out);
}
status_t cmdVkjson(int out, int /*err*/) {
FILE* outs = fdopen(out, "w");
if (!outs) {
int errnum = errno;
ALOGE("vkjson: failed to create output stream: %s", strerror(errnum));
return -errnum;
}
vkjsonPrint(outs);
fclose(outs);
return NO_ERROR;
}
void dumpGameDriverInfo(std::string* result) {
if (!result) return;
char stableGameDriver[PROPERTY_VALUE_MAX] = {};
property_get("ro.gfx.driver.0", stableGameDriver, "unsupported");
StringAppendF(result, "Stable Game Driver: %s\n", stableGameDriver);
char preReleaseGameDriver[PROPERTY_VALUE_MAX] = {};
property_get("ro.gfx.driver.1", preReleaseGameDriver, "unsupported");
StringAppendF(result, "Pre-release Game Driver: %s\n", preReleaseGameDriver);
}
// Read and print all memory info for each process from /d/kgsl/proc/<pid>/mem.
void dumpMemoryInfo(std::string* result, const GpuMemoryMap& memories, uint32_t pid) {
if (!result) return;
// Write results.
StringAppendF(result, "GPU Memory Summary:\n");
for(auto& mem : memories) {
uint32_t process = mem.first;
if (pid != 0 && pid != process) {
continue;
}
StringAppendF(result, "%d:\n", process);
for(auto& memStruct : mem.second) {
StringAppendF(result, " %s", memStruct.first.c_str());
if(memStruct.second.gpuMemory > 0)
StringAppendF(result, ", GPU memory = %" PRId64, memStruct.second.gpuMemory);
if(memStruct.second.mappedMemory > 0)
StringAppendF(result, ", Mapped memory = %" PRId64, memStruct.second.mappedMemory);
if(memStruct.second.ionMemory > 0)
StringAppendF(result, ", Ion memory = %" PRId64, memStruct.second.ionMemory);
StringAppendF(result, "\n");
}
}
}
} // anonymous namespace
} // namespace android