Ref https://github.com/apache/incubator-doris/issues/3566 Introduce trace utility from Kudu to BE. This utility has been widely used in Kudu, Impala also import this trace utility. This trace util is used for tracing each phases in a thread, and can be dumped to string to see each phases' time cost and diagnose which phase cost more time. This util store a Trace object as a threadlocal variable, we can add trace entries which record the current file name, line number, user specified symbols and timestamp to this object, and it's able to add some counters to this Trace object. And then, it can be dumped to human readable string. There are some helpful macros defined in trace.h, here is a simple example for usage: ``` scoped_refptr<Trace> t1(new Trace); // New 2 traces scoped_refptr<Trace> t2(new Trace); t1->AddChildTrace("child_trace", t2.get()); // t1 add t2 as a child named "child_trace" TRACE_TO(t1, "step $0", 1); // Explicitly trace to t1 usleep(10); // ... do some work ADOPT_TRACE(t1.get()); // Explicitly adopt to trace to t1 TRACE("step $0", 2); // Implicitly trace to t1 { // The time spent in this scope is added to counter t1.scope_time_cost TRACE_COUNTER_SCOPE_LATENCY_US("scope_time_cost"); ADOPT_TRACE(t2.get()); // Adopt to trace to t2 for the duration of the current scope TRACE("sub start"); // Implicitly trace to t2 usleep(10); // ... do some work TRACE("sub before loop"); for (int i = 0; i < 10; ++i) { TRACE_COUNTER_INCREMENT("iterate_count", 1); // Increase counter t2.iterate_count MicrosecondsInt64 start_time = GetMonoTimeMicros(); usleep(10); // ... do some work MicrosecondsInt64 end_time = GetMonoTimeMicros(); int64_t dur = end_time - start_time; // t2's simple histogram metric with name prefixed with "lbm_writes" const char* counter = BUCKETED_COUNTER_NAME("lbm_writes", dur); TRACE_COUNTER_INCREMENT(counter, 1); } TRACE("sub after loop"); } TRACE("goodbye $0", "cruel world"); // Automatically restore to trace to t1 std::cout << t1->DumpToString(Trace::INCLUDE_ALL) << std::endl; ``` output looks like: ``` 0514 02:16:07.988054 (+ 0us) trace_test.cpp:76] step 1 0514 02:16:07.988112 (+ 58us) trace_test.cpp:80] step 2 0514 02:16:07.988863 (+ 751us) trace_test.cpp:103] goodbye cruel world Related trace 'child_trace': 0514 02:16:07.988120 (+ 0us) trace_test.cpp:85] sub start 0514 02:16:07.988188 (+ 68us) trace_test.cpp:88] sub before loop 0514 02:16:07.988850 (+ 662us) trace_test.cpp:101] sub after loop Metrics: {"scope_time_cost":744,"child_traces":[["child_trace",{"iterate_count":10,"lbm_writes_lt_1ms":10}]]} ``` Exclude the original source code, this patch do the following work to adapt to Doris: - Rename "kudu" namespace to "doris" - Update some names to the existing function names in Doris, i.g. strings::internal::SubstituteArg::kNoArg -> strings::internal::SubstituteArg::NoArg - Use doris::SpinLock instead of kudu::simple_spinlock which hasn't been imported - Use manual malloc() and free() instead of kudu::Arena which hasn't been imported - Use manual rapidjson::Writer instead of kudu::JsonWriter which hasn't been imported - Remove all TRACE_EVENT related unit tests since TRACE_EVENT is not imported this time - Update CMakeLists.txt NOTICE(#3622): This is a "revert of revert pull request". This pr is mainly used to synthesize the PRs whose commits were scattered and submitted due to the wrong merge method into a complete single commit.
267 lines
7.8 KiB
C++
267 lines
7.8 KiB
C++
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you 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 "util/trace.h"
|
|
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <glog/logging.h>
|
|
#include <rapidjson/rapidjson.h>
|
|
|
|
#include "gutil/strings/substitute.h"
|
|
#include "gutil/walltime.h"
|
|
#include "util/logging.h"
|
|
//#include "util/memory/arena.h"
|
|
|
|
using std::pair;
|
|
using std::string;
|
|
using std::vector;
|
|
using strings::internal::SubstituteArg;
|
|
|
|
namespace doris {
|
|
|
|
__thread Trace* Trace::threadlocal_trace_;
|
|
|
|
Trace::Trace()
|
|
: // arena_(new ThreadSafeArena(1024)),
|
|
entries_head_(nullptr),
|
|
entries_tail_(nullptr) {
|
|
// We expect small allocations from our Arena so no need to have
|
|
// a large arena component. Small allocations are more likely to
|
|
// come out of thread cache and be fast.
|
|
// arena_->SetMaxBufferSize(4096);
|
|
}
|
|
|
|
// Struct which precedes each entry in the trace.
|
|
struct TraceEntry {
|
|
MicrosecondsInt64 timestamp_micros;
|
|
|
|
// The source file and line number which generated the trace message.
|
|
const char* file_path;
|
|
int line_number;
|
|
|
|
uint32_t message_len;
|
|
TraceEntry* next;
|
|
|
|
// The actual trace message follows the entry header.
|
|
char* message() {
|
|
return reinterpret_cast<char*>(this) + sizeof(*this);
|
|
}
|
|
};
|
|
|
|
Trace::~Trace() {
|
|
while (entries_head_ != nullptr) {
|
|
TraceEntry* tmp = entries_head_;
|
|
entries_head_ = entries_head_->next;
|
|
free(tmp);
|
|
}
|
|
}
|
|
|
|
// Get the part of filepath after the last path separator.
|
|
// (Doesn't modify filepath, contrary to basename() in libgen.h.)
|
|
// Borrowed from glog.
|
|
static const char* const_basename(const char* filepath) {
|
|
const char* base = strrchr(filepath, '/');
|
|
#ifdef OS_WINDOWS // Look for either path separator in Windows
|
|
if (!base)
|
|
base = strrchr(filepath, '\\');
|
|
#endif
|
|
return base ? (base+1) : filepath;
|
|
}
|
|
|
|
|
|
void Trace::SubstituteAndTrace(const char* file_path,
|
|
int line_number,
|
|
StringPiece format,
|
|
const SubstituteArg& arg0, const SubstituteArg& arg1,
|
|
const SubstituteArg& arg2, const SubstituteArg& arg3,
|
|
const SubstituteArg& arg4, const SubstituteArg& arg5,
|
|
const SubstituteArg& arg6, const SubstituteArg& arg7,
|
|
const SubstituteArg& arg8, const SubstituteArg& arg9) {
|
|
const SubstituteArg* const args_array[] = {
|
|
&arg0, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8, &arg9, nullptr
|
|
};
|
|
|
|
int msg_len = strings::internal::SubstitutedSize(format, args_array);
|
|
TraceEntry* entry = NewEntry(msg_len, file_path, line_number);
|
|
SubstituteToBuffer(format, args_array, entry->message());
|
|
AddEntry(entry);
|
|
}
|
|
|
|
TraceEntry* Trace::NewEntry(int msg_len, const char* file_path, int line_number) {
|
|
int size = sizeof(TraceEntry) + msg_len;
|
|
//uint8_t* dst = reinterpret_cast<uint8_t*>(arena_->AllocateBytes(size));
|
|
uint8_t* dst = reinterpret_cast<uint8_t*>(malloc(size));
|
|
TraceEntry* entry = reinterpret_cast<TraceEntry*>(dst);
|
|
entry->timestamp_micros = GetCurrentTimeMicros();
|
|
entry->message_len = msg_len;
|
|
entry->file_path = file_path;
|
|
entry->line_number = line_number;
|
|
return entry;
|
|
}
|
|
|
|
void Trace::AddEntry(TraceEntry* entry) {
|
|
std::lock_guard<SpinLock> l(lock_);
|
|
entry->next = nullptr;
|
|
|
|
if (entries_tail_ != nullptr) {
|
|
entries_tail_->next = entry;
|
|
} else {
|
|
DCHECK(entries_head_ == nullptr);
|
|
entries_head_ = entry;
|
|
}
|
|
entries_tail_ = entry;
|
|
}
|
|
|
|
void Trace::Dump(std::ostream* out, int flags) const {
|
|
// Gather a copy of the list of entries under the lock. This is fast
|
|
// enough that we aren't worried about stalling concurrent tracers
|
|
// (whereas doing the logging itself while holding the lock might be
|
|
// too slow, if the output stream is a file, for example).
|
|
vector<TraceEntry*> entries;
|
|
vector<pair<StringPiece, scoped_refptr<Trace>>> child_traces;
|
|
{
|
|
std::lock_guard<SpinLock> l(lock_);
|
|
for (TraceEntry* cur = entries_head_;
|
|
cur != nullptr;
|
|
cur = cur->next) {
|
|
entries.push_back(cur);
|
|
}
|
|
|
|
child_traces = child_traces_;
|
|
}
|
|
|
|
// Save original flags.
|
|
std::ios::fmtflags save_flags(out->flags());
|
|
|
|
int64_t prev_usecs = 0;
|
|
for (TraceEntry* e : entries) {
|
|
// Log format borrowed from glog/logging.cc
|
|
int64_t usecs_since_prev = 0;
|
|
if (prev_usecs != 0) {
|
|
usecs_since_prev = e->timestamp_micros - prev_usecs;
|
|
}
|
|
prev_usecs = e->timestamp_micros;
|
|
|
|
using std::setw;
|
|
*out << FormatTimestampForLog(e->timestamp_micros);
|
|
*out << ' ';
|
|
if (flags & INCLUDE_TIME_DELTAS) {
|
|
out->fill(' ');
|
|
*out << "(+" << setw(6) << usecs_since_prev << "us) ";
|
|
}
|
|
*out << const_basename(e->file_path) << ':' << e->line_number
|
|
<< "] ";
|
|
out->write(reinterpret_cast<char*>(e) + sizeof(TraceEntry),
|
|
e->message_len);
|
|
*out << std::endl;
|
|
}
|
|
|
|
for (const auto& entry : child_traces) {
|
|
const auto& t = entry.second;
|
|
*out << "Related trace '" << entry.first << "':" << std::endl;
|
|
*out << t->DumpToString(flags & (~INCLUDE_METRICS));
|
|
}
|
|
|
|
if (flags & INCLUDE_METRICS) {
|
|
*out << "Metrics: " << MetricsAsJSON();
|
|
}
|
|
|
|
// Restore stream flags.
|
|
out->flags(save_flags);
|
|
}
|
|
|
|
string Trace::DumpToString(int flags) const {
|
|
std::ostringstream s;
|
|
Dump(&s, flags);
|
|
return s.str();
|
|
}
|
|
|
|
string Trace::MetricsAsJSON() const {
|
|
// TODO(yingchun): simplily implement here, we could import JsonWriter in the future.
|
|
rapidjson::StringBuffer buf;
|
|
rapidjson::Writer<rapidjson::StringBuffer> jw(buf);
|
|
MetricsToJSON(&jw);
|
|
return buf.GetString();
|
|
}
|
|
|
|
void Trace::MetricsToJSON(rapidjson::Writer<rapidjson::StringBuffer>* jw) const {
|
|
// Convert into a map with 'std::string' keys instead of 'const char*'
|
|
// keys, so that the results are in a consistent (sorted) order.
|
|
std::map<string, int64_t> counters;
|
|
for (const auto& entry : metrics_.Get()) {
|
|
counters[entry.first] = entry.second;
|
|
}
|
|
|
|
jw->StartObject();
|
|
for (const auto& e : counters) {
|
|
jw->String(e.first.c_str());
|
|
jw->Int64(e.second);
|
|
}
|
|
vector<pair<StringPiece, scoped_refptr<Trace>>> child_traces;
|
|
{
|
|
std::lock_guard<SpinLock> l(lock_);
|
|
child_traces = child_traces_;
|
|
}
|
|
|
|
if (!child_traces.empty()) {
|
|
jw->String("child_traces");
|
|
jw->StartArray();
|
|
|
|
for (const auto& e : child_traces) {
|
|
jw->StartArray();
|
|
jw->String(e.first.data(), e.first.size());
|
|
e.second->MetricsToJSON(jw);
|
|
jw->EndArray();
|
|
}
|
|
jw->EndArray();
|
|
}
|
|
jw->EndObject();
|
|
}
|
|
|
|
void Trace::DumpCurrentTrace() {
|
|
Trace* t = CurrentTrace();
|
|
if (t == nullptr) {
|
|
LOG(INFO) << "No trace is currently active.";
|
|
return;
|
|
}
|
|
t->Dump(&std::cerr, true);
|
|
}
|
|
|
|
void Trace::AddChildTrace(StringPiece label, Trace* child_trace) {
|
|
//CHECK(arena_->RelocateStringPiece(label, &label));
|
|
|
|
std::lock_guard<SpinLock> l(lock_);
|
|
scoped_refptr<Trace> ptr(child_trace);
|
|
child_traces_.emplace_back(label, ptr);
|
|
}
|
|
|
|
std::vector<std::pair<StringPiece, scoped_refptr<Trace>>> Trace::ChildTraces() const {
|
|
std::lock_guard<SpinLock> l(lock_);
|
|
return child_traces_;
|
|
}
|
|
|
|
} // namespace doris
|