blob: a75a8851938331ef90a7b99dc54f6f92c0011ee3 [file]
/*
* Copyright 2025 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.
*/
#include "ProtoLog.h"
#ifdef __ANDROID__
#include <android_tracing.h>
#include <log/log.h>
#include <perfetto/public/producer.h>
#include "DataSource.h"
#include "protos/trace/android/protolog.pzc.h"
#include "protos/trace/profiling/profile_common.pzc.h"
#include <time.h>
#include <cinttypes>
#include <cstdarg>
#include <string>
#include <utility>
#include <vector>
#include <perfetto/public/pb_utils.h>
#include <perfetto/public/protos/trace/interned_data/interned_data.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
#include <perfetto/public/data_source.h>
#include <string.h>
namespace android {
namespace protolog {
thread_local std::vector<std::pair<const std::string, datasource::InternResult>> new_strings;
void Initialize() {
Initialize(PERFETTO_BACKEND_SYSTEM);
}
void Initialize(uint32_t backends) {
ALOGD("Initializing ProtoLog");
if (!android_tracing_native_proto_logging()) {
ALOGD("ProtoLog is disabled. Skipping initialization.");
return;
}
android::protolog::datasource::InitDataSource(backends);
}
static inline uint64_t GetBootTimeNanos() {
struct timespec ts;
if (clock_gettime(CLOCK_BOOTTIME, &ts) != 0) return 0;
return static_cast<uint64_t>(ts.tv_sec) * 1000000000 + static_cast<uint64_t>(ts.tv_nsec);
}
static inline perfetto_protos_ProtoLogLevel GetLogLevelProtoValue(ProtoLogLevel level) {
switch (level) {
case ProtoLogLevel::UNDEFINED:
return perfetto_protos_PROTOLOG_LEVEL_UNDEFINED;
case ProtoLogLevel::VERBOSE:
return perfetto_protos_PROTOLOG_LEVEL_VERBOSE;
case ProtoLogLevel::DEBUG:
return perfetto_protos_PROTOLOG_LEVEL_DEBUG;
case ProtoLogLevel::INFO:
return perfetto_protos_PROTOLOG_LEVEL_INFO;
case ProtoLogLevel::WARN:
return perfetto_protos_PROTOLOG_LEVEL_WARN;
case ProtoLogLevel::ERROR:
return perfetto_protos_PROTOLOG_LEVEL_ERROR;
case ProtoLogLevel::WTF:
return perfetto_protos_PROTOLOG_LEVEL_WTF;
}
}
static inline datasource::IncrementalState* useIncrementalState(
struct PerfettoDsTracerIterator* ctx) {
datasource::IncrementalState* incr_state = static_cast<datasource::IncrementalState*>(
PerfettoDsGetIncrementalState(&datasource::gDataSource, ctx));
if (!incr_state->clearReported) {
struct PerfettoDsRootTracePacket clear_packet;
PerfettoDsTracerPacketBegin(ctx, &clear_packet);
uint32_t flags =
static_cast<uint32_t>(perfetto_protos_TracePacket_SEQ_INCREMENTAL_STATE_CLEARED);
perfetto_protos_TracePacket_set_sequence_flags(&clear_packet.msg, flags);
PerfettoDsTracerPacketEnd(ctx, &clear_packet);
incr_state->clearReported = true;
}
return incr_state;
}
static inline uint64_t internProtoLogMessage(struct PerfettoDsTracerIterator* ctx,
datasource::IncrementalState* incr_state,
ProtoLogLevel level, const std::string_view group,
const char* format, uint64_t ts) {
auto group_res = incr_state->internGroup(group);
auto msg_res = incr_state->internMessage(level, group, format);
bool needs_viewer_config_packet = group_res.is_new || msg_res.is_new;
if (needs_viewer_config_packet) {
struct PerfettoDsRootTracePacket intern_packet;
PerfettoDsTracerPacketBegin(ctx, &intern_packet);
perfetto_protos_TracePacket_set_timestamp(&intern_packet.msg, ts);
// Add viewer config for new groups/messages
if (group_res.is_new || msg_res.is_new) {
struct perfetto_protos_ProtoLogViewerConfig viewer_config_data;
perfetto_protos_TracePacket_begin_protolog_viewer_config(&intern_packet.msg,
&viewer_config_data);
if (group_res.is_new) {
struct perfetto_protos_ProtoLogViewerConfig_Group group_data;
perfetto_protos_ProtoLogViewerConfig_begin_groups(&viewer_config_data, &group_data);
perfetto_protos_ProtoLogViewerConfig_Group_set_id(&group_data, group_res.id);
perfetto_protos_ProtoLogViewerConfig_Group_set_name(&group_data, group.cbegin(),
group.length());
perfetto_protos_ProtoLogViewerConfig_Group_set_tag(&group_data, group.cbegin(),
group.length());
perfetto_protos_ProtoLogViewerConfig_end_groups(&viewer_config_data, &group_data);
}
if (msg_res.is_new) {
struct perfetto_protos_ProtoLogViewerConfig_MessageData message_data;
perfetto_protos_ProtoLogViewerConfig_begin_messages(&viewer_config_data,
&message_data);
perfetto_protos_ProtoLogViewerConfig_MessageData_set_message(&message_data, format,
strlen(format));
perfetto_protos_ProtoLogViewerConfig_MessageData_set_message_id(&message_data,
msg_res.id);
perfetto_protos_ProtoLogViewerConfig_MessageData_set_level(&message_data,
GetLogLevelProtoValue(
level));
perfetto_protos_ProtoLogViewerConfig_MessageData_set_group_id(&message_data,
group_res.id);
perfetto_protos_ProtoLogViewerConfig_end_messages(&viewer_config_data,
&message_data);
}
perfetto_protos_TracePacket_end_protolog_viewer_config(&intern_packet.msg,
&viewer_config_data);
}
PerfettoDsTracerPacketEnd(ctx, &intern_packet);
}
return msg_res.id;
}
static inline void internStringArgs(
struct PerfettoDsTracerIterator* ctx,
const std::vector<std::pair<const std::string, datasource::InternResult>>& strings) {
struct PerfettoDsRootTracePacket intern_packet;
PerfettoDsTracerPacketBegin(ctx, &intern_packet);
struct perfetto_protos_InternedData interned_data;
perfetto_protos_TracePacket_begin_interned_data(&intern_packet.msg, &interned_data);
for (const auto& pair : strings) {
if (pair.second.is_new) {
struct perfetto_protos_InternedString internedString;
perfetto_protos_InternedData_begin_protolog_string_args(&interned_data,
&internedString);
perfetto_protos_InternedString_set_iid(&internedString, pair.second.id);
perfetto_protos_InternedString_set_str(&internedString, pair.first.c_str(),
pair.first.length());
perfetto_protos_InternedData_end_protolog_string_args(&interned_data, &internedString);
}
}
perfetto_protos_TracePacket_end_interned_data(&intern_packet.msg, &interned_data);
PerfettoDsTracerPacketEnd(ctx, &intern_packet);
}
template <typename MessageIdGetter, typename ArgProcessor>
void LogInternal(ProtoLogLevel level, const std::string_view group,
MessageIdGetter message_id_getter, ArgProcessor arg_processor,
bool incrementalStateRequired) {
if (!android_tracing_native_proto_logging()) {
return;
}
auto current_boottime_ns = GetBootTimeNanos();
PERFETTO_DS_TRACE(datasource::gDataSource, ctx) {
datasource::ProtoLogTlsState* tls_state = static_cast<datasource::ProtoLogTlsState*>(
PerfettoDsGetCustomTls(&datasource::gDataSource, &ctx));
auto group_config = tls_state->config->getConfigForGroup(group);
if (level < group_config.log_from) {
continue;
}
datasource::IncrementalState* incr_state = useIncrementalState(&ctx);
datasource::InternResult stacktrace_res = {0, false};
if (group_config.collect_stacktrace) {
// TODO(b/477870887): Unsupported for now. Context: b/473868195.
}
uint64_t msgId = message_id_getter(ctx, *incr_state, current_boottime_ns);
struct PerfettoDsRootTracePacket msg_packet;
PerfettoDsTracerPacketBegin(&ctx, &msg_packet);
perfetto_protos_TracePacket_set_timestamp(&msg_packet.msg, current_boottime_ns);
struct perfetto_protos_ProtoLogMessage protolog_msg;
perfetto_protos_TracePacket_begin_protolog_message(&msg_packet.msg, &protolog_msg);
perfetto_protos_ProtoLogMessage_set_message_id(&protolog_msg, msgId);
bool needsIncrementalState = incrementalStateRequired;
if (stacktrace_res.id != 0) {
perfetto_protos_ProtoLogMessage_set_stacktrace_iid(&protolog_msg, stacktrace_res.id);
needsIncrementalState = true;
}
new_strings.clear();
if (arg_processor(protolog_msg, *incr_state)) {
needsIncrementalState = true;
}
perfetto_protos_TracePacket_end_protolog_message(&msg_packet.msg, &protolog_msg);
if (needsIncrementalState) {
perfetto_protos_TracePacket_set_sequence_flags(
&msg_packet.msg,
static_cast<uint32_t>(perfetto_protos_TracePacket_SEQ_NEEDS_INCREMENTAL_STATE));
}
PerfettoDsTracerPacketEnd(&ctx, &msg_packet);
if (!new_strings.empty()) {
internStringArgs(&ctx, new_strings);
}
}
}
class VarArgumentProvider : public IArgumentProvider {
public:
explicit VarArgumentProvider(va_list& original_args) : original_args_(original_args) {}
~VarArgumentProvider() override { va_end(current_args_); }
void newPass() override { va_copy(current_args_, original_args_); }
void endPass() override { va_end(current_args_); }
long long nextInt() override { return va_arg(current_args_, int); }
double nextDouble() override { return va_arg(current_args_, double); }
const char* nextString() override {
const char* str = va_arg(current_args_, const char*);
return str ? str : "null";
}
void releaseString() override {
// no-op
}
bool nextBool() override { return va_arg(current_args_, int) != 0; }
// Disallow copy and assignment.
VarArgumentProvider(const VarArgumentProvider&) = delete;
VarArgumentProvider& operator=(const VarArgumentProvider&) = delete;
private:
va_list& original_args_; // Reference to the va_list in the Log function's scope
va_list current_args_; // Per-pass copy of the va_list
};
void Log(ProtoLogLevel level, const std::string_view group, const char* format, ...) {
va_list args;
va_start(args, format);
VarArgumentProvider arg_provider(args);
Log(level, group, format, arg_provider);
va_end(args);
}
void Log(ProtoLogLevel level, const std::string_view group, const char* format,
IArgumentProvider& args_provider) {
LogInternal(
level, group,
[&](struct PerfettoDsTracerIterator& ctx, datasource::IncrementalState& incr_state,
uint64_t ts) {
return internProtoLogMessage(&ctx, &incr_state, level, group, format, ts);
},
[&](perfetto_protos_ProtoLogMessage& protolog_msg,
datasource::IncrementalState& incr_state) {
args_provider.newPass();
bool needsIncrementalState = false;
const char* p = format;
while (*p) {
if (*p == '%') {
p++;
switch (*p) {
case 'd':
case 'i':
perfetto_protos_ProtoLogMessage_set_sint64_params(
&protolog_msg, args_provider.nextInt());
break;
case 'f':
perfetto_protos_ProtoLogMessage_set_double_params(
&protolog_msg, args_provider.nextDouble());
break;
case 's': {
const char* str = args_provider.nextString();
if (!str) {
str = "null";
}
auto res = incr_state.internString(str);
if (res.is_new) {
new_strings.emplace_back(str, res);
}
args_provider.releaseString();
perfetto_protos_ProtoLogMessage_set_str_param_iids(&protolog_msg,
res.id);
needsIncrementalState = true;
break;
}
case 'b':
perfetto_protos_ProtoLogMessage_set_boolean_params(
&protolog_msg, args_provider.nextBool());
break;
case '%':
break; // Skip '%%'
default:
ALOGE("Unknown format specifier: %c", *p);
break;
}
}
p++;
}
args_provider.endPass();
return needsIncrementalState;
},
true);
}
void Log(ProtoLogLevel level, const std::string_view group, uint64_t messageHash,
uint64_t paramsMask, int argCount, IArgumentProvider& args_provider) {
LogInternal(
level, group,
[&](struct PerfettoDsTracerIterator&, datasource::IncrementalState&, uint64_t) {
return messageHash;
},
[&](perfetto_protos_ProtoLogMessage& protolog_msg,
datasource::IncrementalState& incr_state) {
args_provider.newPass();
bool needsIncrementalState = false;
for (int i = 0; i < argCount; i++) {
int type = (paramsMask >> (i * 2)) & 0b11;
switch (type) {
case 0b00: { // String
const char* str = args_provider.nextString();
if (!str) str = "null";
auto res = incr_state.internString(str);
if (res.is_new) {
new_strings.emplace_back(str, res);
}
args_provider.releaseString();
perfetto_protos_ProtoLogMessage_set_str_param_iids(&protolog_msg,
res.id);
needsIncrementalState = true;
break;
}
case 0b01: { // Integer
perfetto_protos_ProtoLogMessage_set_sint64_params(&protolog_msg,
args_provider
.nextInt());
break;
}
case 0b10: { // Float
perfetto_protos_ProtoLogMessage_set_double_params(
&protolog_msg, args_provider.nextDouble());
break;
}
case 0b11: { // Boolean
perfetto_protos_ProtoLogMessage_set_boolean_params(&protolog_msg,
args_provider
.nextBool());
break;
}
}
}
args_provider.endPass();
return needsIncrementalState;
},
false);
}
} // namespace protolog
} // namespace android
#endif