// Copyright (c) 2009 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 "net/base/load_log_util.h"
#include "base/format_macros.h"
#include "base/string_util.h"
#include "net/base/net_errors.h"
namespace net {
namespace {
class FormatHelper {
public:
std::string ToString(const LoadLog* log) {
entries_.clear();
// Pass 1: Match the start/end of indentation blocks. Fills |entries_|
// with the results.
PopulateEntries(log);
// Pass 2: Figure out the maximum width of each column. This allows us
// to right-justify text within each column.
size_t max_time_width, max_indentation, max_type_width, max_dt_width;
GetMaxExtent(
&max_time_width, &max_indentation, &max_type_width, &max_dt_width);
// Pass 3: Assemble the string.
std::string result;
const int kSpacesPerIndentation = 2;
for (size_t i = 0; i < entries_.size(); ++i) {
if (log->num_entries_truncated() > 0 && i + 1 == entries_.size()) {
StringAppendF(&result, " ... Truncated %" PRIuS " entries ...\n",
log->num_entries_truncated());
}
if (entries_[i].block_index != -1 &&
static_cast<size_t>(entries_[i].block_index + 1) == i) {
// If there were no entries in between the START/END block then don't
// bother printing a line for END (it just adds noise, and we already
// show the time delta besides START anyway).
continue;
}
int indentation_spaces = entries_[i].indentation * kSpacesPerIndentation;
std::string entry_str = GetEntryString(i);
StringAppendF(&result, "t=%s: %s%s",
PadStringLeft(GetTimeString(i), max_time_width).c_str(),
PadStringLeft("", indentation_spaces).c_str(),
entry_str.c_str());
if (entries_[i].IsBeginEvent()) {
// Summarize how long this block lasted.
int padding = ((max_indentation - entries_[i].indentation) *
kSpacesPerIndentation) + (max_type_width - entry_str.size());
StringAppendF(&result, "%s [dt=%s]",
PadStringLeft("", padding).c_str(),
PadStringLeft(GetBlockDtString(i), max_dt_width).c_str());
}
if (i + 1 != entries_.size())
result += "\n";
}
return result;
}
private:
struct Entry {
explicit Entry(const LoadLog::Entry* log_entry)
: log_entry(log_entry), indentation(0), block_index(-1) {}
bool IsBeginEvent() const {
return log_entry->type == LoadLog::Entry::TYPE_EVENT &&
log_entry->event.phase == LoadLog::PHASE_BEGIN;
}
bool IsEndEvent() const {
return log_entry->type == LoadLog::Entry::TYPE_EVENT &&
log_entry->event.phase == LoadLog::PHASE_END;
}
const LoadLog::Entry* log_entry;
size_t indentation;
int block_index; // The index of the matching start / end of block.
};
void PopulateEntries(const LoadLog* log) {
int current_indentation = 0;
for (size_t i = 0; i < log->entries().size(); ++i) {
Entry entry(&log->entries()[i]);
entry.indentation = current_indentation;
if (entry.IsBeginEvent()) {
// Indent everything contained in this block.
current_indentation++;
}
if (entry.IsEndEvent()) {
int start_index = FindStartOfBlockIndex(entry);
if (start_index != -1) {
// Point the start / end of block at each other.
entry.block_index = start_index;
entries_[start_index].block_index = i;
// Restore the indentation prior to the block.
// (Could be more than 1 level if close of blocks are missing).
current_indentation = entries_[start_index].indentation;
entry.indentation = current_indentation;
}
}
entries_.push_back(entry);
}
}
int FindStartOfBlockIndex(const Entry& entry) {
DCHECK(entry.IsEndEvent());
// Find the matching start of block by scanning backwards.
for (int i = entries_.size() - 1; i >= 0; --i) {
if (entries_[i].IsBeginEvent() &&
entries_[i].log_entry->event.type == entry.log_entry->event.type) {
return i;
}
}
return -1; // Start not found.
}
void GetMaxExtent(size_t* max_time_width,
size_t* max_indentation,
size_t* max_type_width,
size_t* max_dt_width) {
*max_time_width = *max_indentation = *max_type_width = *max_dt_width = 0;
for (size_t i = 0; i < entries_.size(); ++i) {
*max_time_width = std::max(*max_time_width, GetTimeString(i).size());
if (entries_[i].log_entry->type == LoadLog::Entry::TYPE_EVENT)
*max_type_width = std::max(*max_type_width, GetEntryString(i).size());
*max_indentation = std::max(*max_indentation, entries_[i].indentation);
if (entries_[i].IsBeginEvent())
*max_dt_width = std::max(*max_dt_width, GetBlockDtString(i).size());
}
}
std::string GetBlockDtString(size_t start_index) {
int end_index = entries_[start_index].block_index;
if (end_index == -1) {
// Block is not closed, implicitly close it at EOF.
end_index = entries_.size() - 1;
}
int64 dt_ms = (entries_[end_index].log_entry->time -
entries_[start_index].log_entry->time).InMilliseconds();
return Int64ToString(dt_ms);
}
std::string GetTimeString(size_t index) {
int64 t_ms = (entries_[index].log_entry->time -
base::TimeTicks()).InMilliseconds();
return Int64ToString(t_ms);
}
std::string GetEntryString(size_t index) {
const LoadLog::Entry* entry = entries_[index].log_entry;
std::string entry_str;
LoadLog::EventPhase phase = LoadLog::PHASE_NONE;
switch (entry->type) {
case LoadLog::Entry::TYPE_EVENT:
entry_str = LoadLog::EventTypeToString(entry->event.type);
phase = entry->event.phase;
if (phase == LoadLog::PHASE_BEGIN &&
index + 1 < entries_.size() &&
static_cast<size_t>(entries_[index + 1].block_index) == index) {
// If this starts an empty block, we will pretend it is a PHASE_NONE
// so we don't print the "+" prefix.
phase = LoadLog::PHASE_NONE;
}
break;
case LoadLog::Entry::TYPE_ERROR_CODE:
entry_str = StringPrintf("error code: %d (%s)",
entry->error_code,
ErrorToString(entry->error_code));
break;
case LoadLog::Entry::TYPE_STRING:
entry_str = StringPrintf("\"%s\"", entry->string.c_str());
break;
case LoadLog::Entry::TYPE_STRING_LITERAL:
entry_str = StringPrintf("\"%s\"", entry->literal);
break;
default:
NOTREACHED();
}
switch (phase) {
case LoadLog::PHASE_BEGIN:
return std::string("+") + entry_str;
case LoadLog::PHASE_END:
return std::string("-") + entry_str;
case LoadLog::PHASE_NONE:
return std::string(" ") + entry_str;
default:
NOTREACHED();
return std::string();
}
}
static std::string PadStringLeft(const std::string& str, size_t width) {
DCHECK_LE(str.size(), width);
std::string padding;
padding.resize(width - str.size(), ' ');
return padding + str;
}
std::vector<Entry> entries_;
};
} // namespace
// static
std::string LoadLogUtil::PrettyPrintAsEventTree(const LoadLog* log) {
FormatHelper helper;
return helper.ToString(log);
}
} // namespace net