cpp/perfevents/jni.cpp (191 lines of code) (raw):
/**
* Copyright 2004-present, Facebook, Inc.
*
* 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.
*/
#include <limits>
#include <tuple>
#include <vector>
#include <fb/log.h>
#include <fb/xplat_init.h>
#include <fbjni/fbjni.h>
#include <jni.h>
#include <profilo/LogEntry.h>
#include <profilo/jni/JMultiBufferLogger.h>
#include <profilo/perfevents/Session.h>
#include <profilo/perfevents/detail/ClockOffsetMeasurement.h>
#include <profilo/perfevents/detail/FileBackedMappingsList.h>
#include <profilo/util/common.h>
namespace fbjni = facebook::jni;
const char* kPerfSessionType =
"com/facebook/profilo/provider/perfevents/PerfEventsSession";
namespace facebook {
namespace perfevents {
static std::vector<EventSpec> providersToSpecs(jboolean faults) {
auto specs = std::vector<EventSpec>{};
if (faults) {
EventSpec major_spec = {
.type = EVENT_TYPE_MAJOR_FAULTS, .tid = EventSpec::kAllThreads};
specs.push_back(major_spec);
EventSpec minor_spec = {
.type = EVENT_TYPE_MINOR_FAULTS, .tid = EventSpec::kAllThreads};
specs.push_back(minor_spec);
}
return specs;
}
static Session* handleToSession(jlong handle) {
if (handle == 0) {
throw std::invalid_argument("Empty handle passed");
}
return reinterpret_cast<Session*>(handle);
}
namespace {
using namespace profilo;
using namespace profilo::logger;
using namespace profilo::entries;
class ProfiloWriterListener : public RecordListener {
using FileBackedMappingsList = detail::FileBackedMappingsList;
public:
ProfiloWriterListener(
JMultiBufferLogger& logger,
int64_t clock_offset,
std::vector<EventSpec> const& specs)
: logger_(logger),
offset_(clock_offset),
file_mappings_(buildMappingsFromSpecs(specs)),
have_filled_mappings_(false) {}
virtual void onMmap(const RecordMmap& record) {
if (record.isAnonymous()) {
return;
}
if (file_mappings_) {
file_mappings_->add(record.addr, record.addr + record.len);
}
}
virtual void onSample(const EventType type, const RecordSample& record) {
if (file_mappings_ && !have_filled_mappings_) {
// We fill on first event instead of on FileMappings (or this Listener)
// construction because this way we know we're attached and won't miss
// a mapping.
file_mappings_->fillFromProcMaps();
have_filled_mappings_ = true;
}
switch (type) {
case EVENT_TYPE_MAJOR_FAULTS: {
logger_.nativeInstance().write(StandardEntry{
.id = 0,
.type = EntryType::MAJOR_FAULT,
.timestamp = ((int64_t)record.time()) + offset_,
.tid = (int32_t)record.tid(),
.callid = 0,
.matchid = 0,
.extra = (int64_t)record.addr(),
});
return;
}
case EVENT_TYPE_MINOR_FAULTS: {
if (file_mappings_ && !file_mappings_->contains(record.addr())) {
// Ignore anonymous mappings.
return;
}
logger_.nativeInstance().write(StandardEntry{
.id = 0,
.type = EntryType::MINOR_FAULT,
.timestamp = ((int64_t)record.time()) + offset_,
.tid = (int32_t)record.tid(),
.callid = 0,
.matchid = 0,
.extra = (int64_t)record.addr(),
});
return;
}
default: {
} // ignore
}
}
virtual void onForkEnter(const RecordForkExit& record) {}
virtual void onForkExit(const RecordForkExit& record) {}
virtual void onLost(const RecordLost& record) {
logger_.nativeInstance().write(StandardEntry{
.id = 0,
.type = EntryType::PERFEVENTS_LOST,
.timestamp = monotonicTime(),
.tid = threadID(),
.callid = 0,
.matchid = 0,
.extra = (int64_t)record.lost,
});
FBLOGV("Lost records: %u", record.lost);
}
virtual void onReaderStop() {}
private:
JMultiBufferLogger& logger_;
int64_t offset_;
// Contains file-backed mappings, kept up-to-date by
// virtue of RecordMmap events.
//
// First event fills this from /proc/self/maps, after
// which we add new RecordMmap ranges ourselves.
std::unique_ptr<FileBackedMappingsList> file_mappings_;
bool have_filled_mappings_;
static std::unique_ptr<FileBackedMappingsList> buildMappingsFromSpecs(
std::vector<EventSpec> const& specs) {
bool use_mappings = false;
for (auto& spec : specs) {
if (spec.type == EVENT_TYPE_MINOR_FAULTS) {
// We can't keep up with every single minor fault, we need to use
// the file mappings to filter out the anonymous memory ranges.
use_mappings = true;
}
}
return use_mappings ? std::make_unique<FileBackedMappingsList>() : nullptr;
}
};
} // namespace
static jlong nativeAttach(
fbjni::alias_ref<jobject> cls,
jboolean faults,
jint fallbacks,
jint maxIterations,
jfloat maxAttachedFdsRatio,
JMultiBufferLogger* logger) {
auto specs = providersToSpecs(faults);
if (specs.empty()) {
throw std::invalid_argument("Could not convert providers");
}
if (maxIterations > std::numeric_limits<uint16_t>::max()) {
throw std::invalid_argument("Max iterations must fit in uint16_t");
}
auto clockOffset = detail::clock::measureOffsetFromPerfClock(CLOCK_MONOTONIC);
if (clockOffset == INT64_MIN) {
return 0;
}
auto session = new Session(
specs,
{
.fallbacks = FALLBACK_RAISE_RLIMIT,
.maxAttachIterations = static_cast<uint16_t>(maxIterations),
.maxAttachedFdsRatio = maxAttachedFdsRatio,
},
std::unique_ptr<RecordListener>(
new ProfiloWriterListener(*logger, clockOffset, specs)));
if (!session->attach()) {
delete session;
FBLOGV("Session failed to attach");
return 0;
}
FBLOGV("Session attached");
return reinterpret_cast<jlong>((void*)session);
}
static void nativeDetach(JNIEnv*, jobject cls, jlong handle) {
auto session = handleToSession(handle);
delete session;
}
static void nativeRun(JNIEnv*, jobject cls, jlong handle) {
FBLOGV("Session about to run");
handleToSession(handle)->run();
}
static void nativeStop(JNIEnv*, jobject cls, jlong handle) {
FBLOGV("Session about to stop");
handleToSession(handle)->stop();
}
} // namespace perfevents
} // namespace facebook
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
return facebook::xplat::initialize(vm, [] {
fbjni::registerNatives(
kPerfSessionType,
{
makeNativeMethod(
"nativeAttach", facebook::perfevents::nativeAttach),
makeNativeMethod(
"nativeDetach", facebook::perfevents::nativeDetach),
makeNativeMethod("nativeRun", facebook::perfevents::nativeRun),
makeNativeMethod("nativeStop", facebook::perfevents::nativeStop),
});
});
}