blob: 979d7459b25f59d2d6eda408bfab76464e5d26b8 [file] [log] [blame]
/*
* Copyright (C) 2023 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 LOG_TAG "uprobestats"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android_uprobestats_flags.h>
#include <config.pb.h>
#include <iostream>
#include <stdio.h>
#include <string>
#include <thread>
#include "Bpf.h"
#include "ConfigResolver.h"
#include <stats_event.h>
using namespace android::uprobestats;
const std::string kGenericBpfName = std::string("GenericInstrumentation");
const int kJavaArgumentRegisterOffset = 2;
const bool kDebug = false;
#define LOG_IF_DEBUG(msg) \
do { \
if (kDebug) { \
LOG(INFO) << msg; \
} \
} while (0)
bool isUserBuild() {
return android::base::GetProperty("ro.build.type", "unknown") == "user";
}
bool isUprobestatsEnabled() {
return android::uprobestats::flags::enable_uprobestats();
}
const std::string bpf_path = std::string("/sys/fs/bpf/uprobestats/");
std::string prefix_bpf(std::string value) { return bpf_path + value.c_str(); }
struct PollArgs {
std::string mapPath;
::uprobestats::protos::UprobestatsConfig::Task taskConfig;
bool isGeneric;
};
void doPoll(PollArgs args) {
auto mapPath = args.mapPath;
auto durationSeconds = args.taskConfig.duration_seconds();
auto duration = std::chrono::seconds(durationSeconds);
auto startTime = std::chrono::steady_clock::now();
auto now = startTime;
while (now - startTime < duration) {
auto remaining = duration - (std::chrono::steady_clock::now() - startTime);
auto timeoutMs = static_cast<int>(
std::chrono::duration_cast<std::chrono::milliseconds>(remaining)
.count());
if (args.isGeneric) {
auto result =
bpf::pollRingBuf<bpf::CallResult>(mapPath.c_str(), timeoutMs);
for (auto value : result) {
LOG_IF_DEBUG("ringbuf generic java result...");
LOG_IF_DEBUG("register: pc = " << value.pc);
for (int i = 0; i < 10; i++) {
auto reg = value.regs[i];
LOG_IF_DEBUG("register: " << i << " = " << reg);
}
if (args.taskConfig.has_statsd_logging_config()) {
auto statsd_logging_config = args.taskConfig.statsd_logging_config();
int atom_id = statsd_logging_config.atom_id();
LOG_IF_DEBUG("attempting to write atom id: " << atom_id);
AStatsEvent *event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, atom_id);
for (int primitiveArgumentPosition :
statsd_logging_config.primitive_argument_positions()) {
int primitiveArgument = value.regs[primitiveArgumentPosition +
kJavaArgumentRegisterOffset];
LOG_IF_DEBUG("writing argument value: "
<< primitiveArgument
<< " from position: " << primitiveArgumentPosition);
AStatsEvent_writeInt32(event, primitiveArgument);
}
AStatsEvent_write(event);
AStatsEvent_release(event);
LOG_IF_DEBUG("successfully wrote atom id: " << atom_id);
} else {
LOG_IF_DEBUG("no statsd logging config");
}
}
} else {
auto result = bpf::pollRingBuf<uint32_t>(mapPath.c_str(), timeoutMs);
for (auto value : result) {
LOG_IF_DEBUG("ringbuf result callback. value: " << value << " mapPath: "
<< mapPath);
}
}
now = std::chrono::steady_clock::now();
}
LOG_IF_DEBUG("finished polling for mapPath: " << mapPath);
}
int main(int argc, char **argv) {
if (isUserBuild()) {
// TODO(296108553): See if we could avoid shipping this binary on user
// builds.
LOG(ERROR) << "uprobestats disabled on user build. Exiting.";
return 1;
}
if (!isUprobestatsEnabled()) {
LOG(ERROR) << "uprobestats disabled by flag. Exiting.";
return 1;
}
if (argc < 2) {
LOG(ERROR) << "Not enough command line arguments. Exiting.";
return 1;
}
auto config = config_resolver::readConfig(
std::string("/data/misc/uprobestats-configs/") + argv[1]);
if (!config.has_value()) {
return 1;
}
auto resolvedTask = config_resolver::resolveSingleTask(config.value());
if (!resolvedTask.has_value()) {
return 1;
}
LOG_IF_DEBUG("Found task config: " << resolvedTask.value());
std::set<std::string> mapPaths;
auto resolvedProbeConfigs =
config_resolver::resolveProbes(resolvedTask.value().taskConfig);
if (!resolvedProbeConfigs.has_value()) {
return 1;
}
for (auto &resolvedProbe : resolvedProbeConfigs.value()) {
LOG_IF_DEBUG("Opening bpf perf event from probe: " << resolvedProbe);
mapPaths.insert(prefix_bpf(resolvedProbe.probeConfig.bpf_map()));
bpf::bpfPerfEventOpen(
resolvedProbe.filename.c_str(), resolvedProbe.offset,
resolvedTask.value().pid,
prefix_bpf(resolvedProbe.probeConfig.bpf_name()).c_str());
}
std::vector<std::thread> threads;
for (auto mapPath : mapPaths) {
auto poll_args = PollArgs{mapPath, resolvedTask.value().taskConfig};
if (mapPath.find(kGenericBpfName) != std::string::npos) {
poll_args.isGeneric = true;
}
LOG_IF_DEBUG(
"Starting thread to collect results from mapPath: " << mapPath);
threads.emplace_back(doPoll, poll_args);
}
for (auto &thread : threads) {
thread.join();
}
LOG_IF_DEBUG("done.");
return 0;
}