cpp/mmapbuf/writer/MmapBufferTraceWriter.cpp (283 lines of code) (raw):
/**
* Copyright 2004-present, Facebook, Inc.
*
* <p>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
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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 "MmapBufferTraceWriter.h"
#include <fb/fbjni.h>
#include <fb/log.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <cstring>
#include <fstream>
#include <memory>
#include <stdexcept>
#include <profilo/entries/EntryType.h>
#include <profilo/logger/buffer/RingBuffer.h>
#include <profilo/mmapbuf/Buffer.h>
#include <profilo/mmapbuf/header/MmapBufferHeader.h>
#include <profilo/util/common.h>
#include <profilo/writer/TraceWriter.h>
#include <profilo/writer/trace_headers.h>
namespace facebook {
namespace profilo {
namespace mmapbuf {
namespace writer {
using namespace facebook::profilo::mmapbuf::header;
namespace {
constexpr int64_t kTriggerEventFlag = 0x0002000000000000L; // 1 << 49
static constexpr char kMemoryMappingKey[] = "l:s:u:o:s";
void loggerWrite(
Logger& logger,
EntryType type,
int32_t callid,
int32_t matchid,
int64_t extra,
int64_t timestamp = monotonicTime()) {
logger.write(StandardEntry{
.id = 0,
.type = type,
.timestamp = timestamp,
.tid = threadID(),
.callid = callid,
.matchid = matchid,
.extra = extra,
});
}
void loggerWriteStringAnnotation(
Logger& logger,
EntryType type,
int32_t callid,
const std::string& annotationKey,
const std::string& annotationValue,
int64_t extra = 0,
int64_t timestamp = monotonicTime()) {
StandardEntry annotationEntry{};
annotationEntry.type = type;
annotationEntry.tid = threadID();
annotationEntry.timestamp = timestamp;
annotationEntry.callid = callid;
auto matchid = logger.write(std::move(annotationEntry));
auto key = annotationKey.c_str();
matchid = logger.writeBytes(
EntryType::STRING_KEY,
matchid,
reinterpret_cast<const uint8_t*>(key),
strlen(key));
auto value = annotationValue.c_str();
logger.writeBytes(
EntryType::STRING_VALUE,
matchid,
reinterpret_cast<const uint8_t*>(value),
strlen(value));
}
void loggerWriteTraceStringAnnotation(
Logger& logger,
int32_t annotationQuicklogId,
const std::string& annotationKey,
const std::string& annotationValue,
int64_t timestamp = monotonicTime()) {
loggerWriteStringAnnotation(
logger,
EntryType::TRACE_ANNOTATION,
annotationQuicklogId,
annotationKey,
annotationValue,
0,
timestamp);
}
void loggerWriteQplTriggerAnnotation(
Logger& logger,
int32_t marker_id,
const std::string& annotationKey,
const std::string& annotationValue,
int64_t timestamp = monotonicTime()) {
loggerWriteStringAnnotation(
logger,
EntryType::QPL_ANNOTATION,
marker_id,
annotationKey,
annotationValue,
kTriggerEventFlag,
timestamp);
}
//
// Process entries from source buffer and write to the destination.
// It's okay if not all entries were successfully copied.
// Returns false if were able to copy less than 50% of source buffer entries.
//
bool copyBufferEntries(TraceBuffer& source, TraceBuffer& dest) {
TraceBuffer::Cursor cursor = source.currentTail(0);
alignas(4) Packet packet;
uint32_t processed_count = 0;
while (source.tryRead(packet, cursor)) {
Packet writePacket = packet; // copy
dest.write(writePacket);
++processed_count;
if (!cursor.moveForward()) {
break;
}
}
return processed_count > 0;
}
void processMemoryMappingsFile(
Logger& logger,
const char* file_path,
int64_t timestamp) {
std::ifstream mappingsFile(file_path);
if (!mappingsFile.is_open()) {
return;
}
int32_t tid = threadID();
std::string mappingLine;
while (std::getline(mappingsFile, mappingLine)) {
auto mappingId = logger.write(entries::StandardEntry{
.type = EntryType::MAPPING,
.timestamp = timestamp,
.tid = tid,
});
auto keyId = logger.writeBytes(
EntryType::STRING_KEY,
mappingId,
reinterpret_cast<const uint8_t*>(kMemoryMappingKey),
sizeof(kMemoryMappingKey));
logger.writeBytes(
EntryType::STRING_VALUE,
keyId,
reinterpret_cast<const uint8_t*>(mappingLine.c_str()),
mappingLine.size());
}
}
} // namespace
int64_t MmapBufferTraceWriter::nativeInitAndVerify(
const std::string& dump_path) {
dump_path_ = dump_path;
bufferMapHolder_ = std::make_unique<BufferFileMapHolder>(dump_path);
MmapBufferPrefix* mapBufferPrefix =
reinterpret_cast<MmapBufferPrefix*>(bufferMapHolder_->map_ptr);
if (mapBufferPrefix->staticHeader.magic != kMagic) {
return 0;
}
if (mapBufferPrefix->staticHeader.version != kVersion) {
return 0;
}
if (mapBufferPrefix->header.bufferVersion != RingBuffer::kVersion) {
return 0;
}
int64_t trace_id = mapBufferPrefix->header.traceId;
trace_id_ = trace_id;
return trace_id;
}
void MmapBufferTraceWriter::nativeWriteTrace(
const std::string& type,
bool persistent,
const std::string& trace_folder,
const std::string& trace_prefix,
int32_t trace_flags,
fbjni::alias_ref<JNativeTraceWriterCallbacks> callbacks) {
writeTrace(
type,
persistent,
trace_folder,
trace_prefix,
trace_flags,
std::make_shared<NativeTraceWriterCallbacksProxy>(callbacks));
}
void MmapBufferTraceWriter::writeTrace(
const std::string& type,
bool persistent,
const std::string& trace_folder,
const std::string& trace_prefix,
int32_t trace_flags,
std::shared_ptr<TraceCallbacks> callbacks,
uint64_t timestamp) {
if (bufferMapHolder_.get() == nullptr) {
throw std::runtime_error(
"Not initialized. Method nativeInitAndVerify() should be called first.");
}
if (trace_id_ == 0) {
throw std::runtime_error(
"Buffer is not associated with a trace. Trace Id is 0.");
}
MmapBufferPrefix* mapBufferPrefix =
reinterpret_cast<MmapBufferPrefix*>(bufferMapHolder_->map_ptr);
int32_t qpl_marker_id =
static_cast<int32_t>(mapBufferPrefix->header.longContext);
auto entriesCount = mapBufferPrefix->header.size;
// Number of additional records we need to log in addition to entries from the
// buffer file + memory mappings file records + some buffer for long string
// entries.
constexpr auto kExtraRecordCount = 4096;
std::shared_ptr<mmapbuf::Buffer> buffer =
std::make_shared<mmapbuf::Buffer>(entriesCount + kExtraRecordCount);
auto& ringBuffer = buffer->ringBuffer();
TraceBuffer::Cursor startCursor = ringBuffer.currentHead();
Logger::EntryIDCounter newBufferEntryID{1};
Logger logger([&]() -> TraceBuffer& { return ringBuffer; }, newBufferEntryID);
// It's not technically backwards trace but that's what we use to denote Black
// Box traces.
loggerWrite(
logger, EntryType::TRACE_BACKWARDS, 0, trace_flags, trace_id_, timestamp);
{
// Copying entries from the saved buffer to the new one.
TraceBuffer* historicBuffer = reinterpret_cast<TraceBuffer*>(
reinterpret_cast<char*>(bufferMapHolder_->map_ptr) +
sizeof(MmapBufferPrefix));
bool ok = copyBufferEntries(*historicBuffer, ringBuffer);
if (!ok) {
throw std::runtime_error("Unable to read the file-backed buffer.");
}
}
loggerWrite(
logger,
EntryType::QPL_START,
qpl_marker_id,
0,
kTriggerEventFlag,
timestamp);
loggerWrite(
logger,
EntryType::TRACE_ANNOTATION,
QuickLogConstants::APP_VERSION_CODE,
0,
mapBufferPrefix->header.versionCode,
timestamp);
loggerWrite(
logger,
EntryType::TRACE_ANNOTATION,
QuickLogConstants::CONFIG_ID,
0,
mapBufferPrefix->header.configId,
timestamp);
loggerWriteTraceStringAnnotation(
logger,
QuickLogConstants::SESSION_ID,
"Asl Session Id",
std::string(mapBufferPrefix->header.sessionId));
loggerWriteQplTriggerAnnotation(
logger, qpl_marker_id, "type", type, timestamp);
if (persistent) {
loggerWriteQplTriggerAnnotation(
logger, qpl_marker_id, "collection_method", "persistent", timestamp);
}
const char* mapsFilePath = mapBufferPrefix->header.memoryMapsFilePath;
if (mapsFilePath[0] != '\0') {
processMemoryMappingsFile(logger, mapsFilePath, timestamp);
}
loggerWrite(logger, EntryType::TRACE_END, 0, 0, trace_id_, timestamp);
TraceWriter writer(
std::move(trace_folder),
std::move(trace_prefix),
buffer,
callbacks,
calculateHeaders(mapBufferPrefix->header.pid));
try {
writer.processTrace(trace_id_, startCursor);
} catch (std::exception& e) {
FBLOGE("Error during dump processing: %s", e.what());
callbacks->onTraceAbort(trace_id_, AbortReason::UNKNOWN);
}
}
fbjni::local_ref<MmapBufferTraceWriter::jhybriddata>
MmapBufferTraceWriter::initHybrid(fbjni::alias_ref<jclass>) {
return makeCxxInstance();
}
void MmapBufferTraceWriter::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", MmapBufferTraceWriter::initHybrid),
makeNativeMethod(
"nativeWriteTrace", MmapBufferTraceWriter::nativeWriteTrace),
makeNativeMethod(
"nativeInitAndVerify", MmapBufferTraceWriter::nativeInitAndVerify),
});
}
} // namespace writer
} // namespace mmapbuf
} // namespace profilo
} // namespace facebook