Chromium Code Reviews| Index: base/debug/trace_event.cc |
| diff --git a/base/debug/trace_event.cc b/base/debug/trace_event.cc |
| index c219cec293b1dd35c999fae2fa0df90af0480f08..58ff82fbb1d8be4a84e9791ac9fc8c6adf2e57d1 100644 |
| --- a/base/debug/trace_event.cc |
| +++ b/base/debug/trace_event.cc |
| @@ -1,16 +1,15 @@ |
| -// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| #include "base/debug/trace_event.h" |
| +#if defined(OS_WIN) |
| +#include "base/debug/trace_event_win.h" |
| +#endif |
| #include "base/format_macros.h" |
| -#include "base/file_path.h" |
| -#include "base/file_util.h" |
| -#include "base/path_service.h" |
| #include "base/process_util.h" |
| #include "base/stringprintf.h" |
| -#include "base/threading/platform_thread.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/time.h" |
| @@ -19,149 +18,368 @@ |
| namespace base { |
| namespace debug { |
| -static const char* kEventTypeNames[] = { |
| - "BEGIN", |
| - "END", |
| - "INSTANT" |
| +// Controls the number of trace events we will buffer in-memory |
| +// before throwing them away. |
| +const size_t kTraceEventBufferSize = 500000; |
| +const size_t kTraceEventBatchSize = 1000; |
| + |
| +#define TRACE_EVENT_MAX_CATEGORIES 42 |
| + |
| +static TraceCategory g_categories[TRACE_EVENT_MAX_CATEGORIES] = { |
| + { "tracing already shutdown", false }, |
| + { "tracing categories exhausted; must increase TRACE_EVENT_MAX_CATEGORIES", |
| + false } |
| }; |
| +static const TraceCategory* const g_category_already_shutdown = |
| + &g_categories[0]; |
| +static const TraceCategory* const g_category_categories_exhausted = |
| + &g_categories[1]; |
| +static int g_category_index = 2; // skip initial 2 error categories |
| -static const FilePath::CharType* kLogFileName = |
| - FILE_PATH_LITERAL("trace_%d.log"); |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// |
| +// TraceValue |
| +// |
| +//////////////////////////////////////////////////////////////////////////////// |
| -// static |
| -TraceLog* TraceLog::GetInstance() { |
| - return Singleton<TraceLog, DefaultSingletonTraits<TraceLog> >::get(); |
| +void TraceValue::Destroy() { |
| + if (type_ == TRACE_TYPE_STRING) { |
| + value_.as_string_refptr->Release(); |
| + value_.as_string_refptr = NULL; |
| + } |
| + type_ = TRACE_TYPE_UNDEFINED; |
| + value_.as_uint = 0ull; |
| } |
| -// static |
| -bool TraceLog::IsTracing() { |
| - return TraceLog::GetInstance()->enabled_; |
| +TraceValue& TraceValue::operator=(const TraceValue& rhs) { |
| + if (this == &rhs) |
| + return *this; |
| + |
| + Destroy(); |
| + |
| + type_ = rhs.type_; |
| + switch(type_) { |
| + case TRACE_TYPE_UNDEFINED: |
| + return *this; |
| + case TRACE_TYPE_BOOL: |
| + value_.as_bool = rhs.value_.as_bool; |
| + return *this; |
| + case TRACE_TYPE_UINT: |
| + value_.as_uint = rhs.value_.as_uint; |
| + return *this; |
| + case TRACE_TYPE_INT: |
| + value_.as_int = rhs.value_.as_int; |
| + return *this; |
| + case TRACE_TYPE_DOUBLE: |
| + value_.as_double = rhs.value_.as_double; |
| + return *this; |
| + case TRACE_TYPE_POINTER: |
| + value_.as_pointer = rhs.value_.as_pointer; |
| + return *this; |
| + case TRACE_TYPE_STRING: |
| + value_.as_string_refptr = rhs.value_.as_string_refptr; |
| + value_.as_string_refptr->AddRef(); |
| + return *this; |
| + default: |
| + NOTREACHED(); |
| + return *this; |
| + } |
| } |
| -// static |
| -bool TraceLog::StartTracing() { |
| - return TraceLog::GetInstance()->Start(); |
| +void TraceValue::AppendAsJSON(std::string* out) const { |
| + char temp_string[128]; |
| + std::string::size_type start_pos; |
| + switch (type_) { |
| + case TRACE_TYPE_BOOL: |
| + *out += as_bool()? "true" : "false"; |
| + break; |
| + case TRACE_TYPE_UINT: |
| + base::snprintf(temp_string, arraysize(temp_string), "%llu", |
| + static_cast<unsigned long long>(as_uint())); |
| + *out += temp_string; |
| + break; |
| + case TRACE_TYPE_INT: |
| + base::snprintf(temp_string, arraysize(temp_string), "%lld", |
| + static_cast<long long>(as_int())); |
| + *out += temp_string; |
| + break; |
| + case TRACE_TYPE_DOUBLE: |
| + base::snprintf(temp_string, arraysize(temp_string), "%f", as_double()); |
| + *out += temp_string; |
| + break; |
| + case TRACE_TYPE_POINTER: |
| + base::snprintf(temp_string, arraysize(temp_string), "%llu", |
| + static_cast<unsigned long long>( |
| + reinterpret_cast<intptr_t>( |
| + as_pointer()))); |
| + *out += temp_string; |
| + break; |
| + case TRACE_TYPE_STRING: |
| + *out += "\""; |
| + start_pos = out->size(); |
| + *out += as_string(); |
| + // replace " character with ' |
| + while ((start_pos = out->find_first_of('\"', start_pos)) != |
| + std::string::npos) |
| + (*out)[start_pos] = '\''; |
| + *out += "\""; |
| + break; |
| + default: |
| + NOTREACHED() << "Don't know how to print this value"; |
| + break; |
| + } |
| } |
| -// static |
| -void TraceLog::StopTracing() { |
| - return TraceLog::GetInstance()->Stop(); |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// |
| +// TraceEvent |
| +// |
| +//////////////////////////////////////////////////////////////////////////////// |
| + |
| +namespace { |
| + |
| +const char* GetPhaseStr(TraceEventPhase phase) { |
| + switch(phase) { |
| + case TRACE_EVENT_PHASE_BEGIN: |
| + return "B"; |
| + case TRACE_EVENT_PHASE_INSTANT: |
| + return "I"; |
| + case TRACE_EVENT_PHASE_END: |
| + return "E"; |
| + default: |
| + NOTREACHED() << "Invalid phase argument"; |
| + return "?"; |
| + } |
| } |
| -void TraceLog::Trace(const std::string& name, |
| - EventType type, |
| - const void* id, |
| - const std::wstring& extra, |
| - const char* file, |
| - int line) { |
| - if (!enabled_) |
| - return; |
| - Trace(name, type, id, WideToUTF8(extra), file, line); |
| +} // namespace |
| + |
| +TraceEvent::TraceEvent() |
| + : process_id(0), |
| + thread_id(0), |
| + phase(TRACE_EVENT_PHASE_BEGIN), |
| + category(NULL), |
| + name(NULL) { |
| + memset(arg_names, 0, sizeof(arg_names)); |
| } |
| -void TraceLog::Trace(const std::string& name, |
| - EventType type, |
| - const void* id, |
| - const std::string& extra, |
| - const char* file, |
| - int line) { |
| - if (!enabled_) |
| - return; |
| +TraceEvent::~TraceEvent() { |
| +} |
| -#ifdef USE_UNRELIABLE_NOW |
| - TimeTicks tick = TimeTicks::HighResNow(); |
| -#else |
| - TimeTicks tick = TimeTicks::Now(); |
| -#endif |
| - TimeDelta delta = tick - trace_start_time_; |
| - int64 usec = delta.InMicroseconds(); |
| - std::string msg = |
| - StringPrintf("{'pid':'0x%lx', 'tid':'0x%lx', 'type':'%s', " |
| - "'name':'%s', 'id':'%p', 'extra':'%s', 'file':'%s', " |
| - "'line_number':'%d', 'usec_begin': %" PRId64 "},\n", |
| - static_cast<unsigned long>(base::GetCurrentProcId()), |
| - static_cast<unsigned long>(PlatformThread::CurrentId()), |
| - kEventTypeNames[type], |
| - name.c_str(), |
| - id, |
| - extra.c_str(), |
| - file, |
| - line, |
| - usec); |
| - |
| - Log(msg); |
| -} |
| - |
| -TraceLog::TraceLog() : enabled_(false), log_file_(NULL) { |
| - base::ProcessHandle proc = base::GetCurrentProcessHandle(); |
| -#if !defined(OS_MACOSX) |
| - process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(proc)); |
| -#else |
| - // The default port provider is sufficient to get data for the current |
| - // process. |
| - process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(proc, |
| - NULL)); |
| -#endif |
| +void TraceEvent::AppendEventsAsJSON(const std::vector<TraceEvent>& events, |
| + size_t start, |
| + size_t count, |
| + std::string* out) { |
| + *out += "["; |
| + for (size_t i = 0; i < count && start + i < events.size(); ++i) { |
| + if (i > 0) |
| + *out += ","; |
| + events[i + start].AppendAsJSON(out); |
| + } |
| + *out += "]"; |
| } |
| -TraceLog::~TraceLog() { |
| - Stop(); |
| -} |
| - |
| -bool TraceLog::OpenLogFile() { |
| - FilePath::StringType pid_filename = |
| - StringPrintf(kLogFileName, base::GetCurrentProcId()); |
| - FilePath log_file_path; |
| - if (!PathService::Get(base::DIR_EXE, &log_file_path)) |
| - return false; |
| - log_file_path = log_file_path.Append(pid_filename); |
| - log_file_ = file_util::OpenFile(log_file_path, "a"); |
| - if (!log_file_) { |
| - // try the current directory |
| - log_file_ = file_util::OpenFile(FilePath(pid_filename), "a"); |
| - if (!log_file_) { |
| - return false; |
| - } |
| +void TraceEvent::AppendAsJSON(std::string* out) const { |
| + const char* phase_str = GetPhaseStr(phase); |
| + int64 time_int64 = timestamp.ToInternalValue(); |
| + // Category name checked at category creation time. |
| + DCHECK(!strchr(name, '"')); |
| + StringAppendF(out, |
| + "{\"cat\":\"%s\",\"pid\":%i,\"tid\":%i,\"ts\":%lld," |
| + "\"ph\":\"%s\",\"name\":\"%s\",\"args\":{", |
| + category->name, |
| + static_cast<int>(process_id), |
| + static_cast<int>(thread_id), |
| + static_cast<long long>(time_int64), |
| + phase_str, |
| + name); |
| + |
| + // Output argument names and values, stop at first NULL argument name. |
| + for (size_t i = 0; i < kTraceMaxNumArgs && arg_names[i]; ++i) { |
| + if (i > 0) |
| + *out += ","; |
| + *out += "\""; |
| + *out += arg_names[i]; |
| + *out += "\":"; |
| + arg_values[i].AppendAsJSON(out); |
| } |
| - return true; |
| + *out += "}}"; |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// |
| +// TraceLog |
| +// |
| +//////////////////////////////////////////////////////////////////////////////// |
| + |
| +// static |
| +TraceLog* TraceLog::GetInstance() { |
| + return Singleton<TraceLog, StaticMemorySingletonTraits<TraceLog> >::get(); |
| +} |
| + |
| +TraceLog::TraceLog() |
| + : enabled_(false) { |
| + logged_events_.reserve(1024); |
| } |
| -void TraceLog::CloseLogFile() { |
| - if (log_file_) { |
| - file_util::CloseFile(log_file_); |
| +TraceLog::~TraceLog() { |
| +} |
| + |
| +const TraceCategory* TraceLog::GetCategory(const char* name) { |
| + TraceLog* tracelog = GetInstance(); |
| + if (!tracelog){ |
| + CHECK(!g_category_already_shutdown->enabled); |
| + return g_category_already_shutdown; |
| } |
| + return tracelog->GetCategoryInternal(name); |
| } |
| -bool TraceLog::Start() { |
| - if (enabled_) |
| - return true; |
| - enabled_ = OpenLogFile(); |
| - if (enabled_) { |
| - Log("var raw_trace_events = [\n"); |
| - trace_start_time_ = TimeTicks::Now(); |
| - timer_.Start(TimeDelta::FromMilliseconds(250), this, &TraceLog::Heartbeat); |
| +const TraceCategory* TraceLog::GetCategoryInternal(const char* name) { |
| + AutoLock lock(lock_); |
| + DCHECK(!strchr(name, '"')) << "Names may not contain double quote char"; |
| + |
| + // Search for pre-existing category matching this name |
| + for (int i = 0; i < g_category_index; i++) { |
| + if (strcmp(g_categories[i].name, name) == 0) |
| + return &g_categories[i]; |
| } |
| - return enabled_; |
| + |
| + // Create a new category |
| + DCHECK(g_category_index < TRACE_EVENT_MAX_CATEGORIES) << |
| + "must increase TRACE_EVENT_MAX_CATEGORIES"; |
| + if (g_category_index < TRACE_EVENT_MAX_CATEGORIES) { |
| + int new_index = g_category_index++; |
| + g_categories[new_index].name = name; |
| + DCHECK(!g_categories[new_index].enabled); |
| + g_categories[new_index].enabled = enabled_; |
| + return &g_categories[new_index]; |
| + } else { |
| + return g_category_categories_exhausted; |
| + } |
| +} |
| + |
| +void TraceLog::SetEnabled(bool enabled) { |
| + { |
| + AutoLock lock(lock_); |
| + if (enabled == enabled_) |
| + return; |
| + enabled_ = enabled; |
| + for (int i = 0; i < g_category_index; i++) { |
| + //TODO(scheib): If changed to enable specific categories instead of all |
| + // check GetCategoryInternal creation code that users TraceLog::enabled_ |
| + g_categories[i].enabled = enabled; |
| + } |
| + } // release lock |
| + if (!enabled) |
| + Flush(); |
| +} |
| + |
| +float TraceLog::GetBufferPercentFull() const { |
| + return (float)((double)logged_events_.size()/(double)kTraceEventBufferSize); |
| +} |
| + |
| +void TraceLog::SetOutputCallback(const TraceLog::OutputCallback& cb) { |
| + AutoLock lock(lock_); |
| + output_callback_ = cb; |
| + logged_events_.clear(); |
|
scheib
2011/05/09 23:39:51
Correcting a shutdown crash, there is no need to F
|
| +} |
| + |
| +void TraceLog::SetBufferFullCallback(const TraceLog::BufferFullCallback& cb) { |
| + AutoLock lock(lock_); |
| + buffer_full_callback_ = cb; |
| } |
| -void TraceLog::Stop() { |
| - if (enabled_) { |
| - enabled_ = false; |
| - Log("];\n"); |
| - CloseLogFile(); |
| - timer_.Stop(); |
| +void TraceLog::Flush() { |
| + std::vector<TraceEvent> previous_logged_events; |
| + OutputCallback output_callback_copy; |
| + { |
| + AutoLock lock(lock_); |
| + previous_logged_events.swap(logged_events_); |
| + output_callback_copy = output_callback_; |
| + } // release lock |
| + |
| + if (output_callback_copy.is_null()) |
| + return; |
| + |
| + for (size_t i = 0; |
| + i < previous_logged_events.size(); |
| + i += kTraceEventBatchSize) { |
| + scoped_refptr<RefCountedString> json_events_str_ptr = |
| + new RefCountedString(); |
| + TraceEvent::AppendEventsAsJSON(previous_logged_events, |
| + i, |
| + kTraceEventBatchSize, |
| + &(json_events_str_ptr->data)); |
| + output_callback_copy.Run(json_events_str_ptr.get()); |
| } |
| } |
| -void TraceLog::Heartbeat() { |
| - std::string cpu = StringPrintf("%.0f", process_metrics_->GetCPUUsage()); |
| - TRACE_EVENT_INSTANT("heartbeat.cpu", 0, cpu); |
| +void TraceLog::AddTraceEvent(TraceEventPhase phase, |
| + const char* file, int line, |
| + const TraceCategory* category, |
| + const char* name, |
| + const char* arg1_name, TraceValue arg1_val, |
| + const char* arg2_name, TraceValue arg2_val) { |
| + DCHECK(file && name); |
| +#ifdef USE_UNRELIABLE_NOW |
| + TimeTicks now = TimeTicks::HighResNow(); |
| +#else |
| + TimeTicks now = TimeTicks::Now(); |
| +#endif |
| + BufferFullCallback buffer_full_callback_copy; |
| + { |
| + AutoLock lock(lock_); |
| + if (!enabled_ || !category->enabled) |
| + return; |
| + if (logged_events_.size() >= kTraceEventBufferSize) |
| + return; |
| + logged_events_.push_back(TraceEvent()); |
| + TraceEvent& event = logged_events_.back(); |
| + event.process_id = static_cast<unsigned long>(base::GetCurrentProcId()); |
| + event.thread_id = PlatformThread::CurrentId(); |
| + event.timestamp = now; |
| + event.phase = phase; |
| + event.category = category; |
| + event.name = name; |
| + event.arg_names[0] = arg1_name; |
| + event.arg_values[0] = arg1_val; |
| + event.arg_names[1] = arg2_name; |
| + event.arg_values[1] = arg2_val; |
| + COMPILE_ASSERT(kTraceMaxNumArgs == 2, TraceEvent_arg_count_out_of_sync); |
| + |
| + if (logged_events_.size() == kTraceEventBufferSize) { |
| + buffer_full_callback_copy = buffer_full_callback_; |
| + } |
| + } // release lock |
| + |
| + if (!buffer_full_callback_copy.is_null()) |
| + buffer_full_callback_copy.Run(); |
| } |
| -void TraceLog::Log(const std::string& msg) { |
| - AutoLock lock(file_lock_); |
| +void TraceLog::AddTraceEventEtw(TraceEventPhase phase, |
| + const char* file, int line, |
| + const char* name, |
| + const void* id, |
| + const char* extra) { |
| + // Legacy trace points on windows called to ETW |
| +#if defined(OS_WIN) |
| + TraceEventETWProvider::Trace(name, phase, id, extra); |
| +#endif |
| + |
| + // Also add new trace event behavior |
| + static const TraceCategory* category = GetCategory("ETW Trace Event"); |
| + if (category->enabled) { |
| + TraceLog* tracelog = TraceLog::GetInstance(); |
| + if (!tracelog) |
| + return; |
| + tracelog->AddTraceEvent(phase, file, line, category, name, |
| + "id", id, |
| + "extra", extra ? extra : ""); |
| + } |
| +} |
| - fprintf(log_file_, "%s", msg.c_str()); |
| +void TraceLog::Resurrect() { |
| + StaticMemorySingletonTraits<TraceLog>::Resurrect(); |
| } |
| } // namespace debug |