blob: 6789df701b7b2f8accacb5050995276e05c2acbd [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 "cobalt/browser/memory_tracker/tool.h"
#include <cstdlib>
#include <map>
#include <string>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/string_number_conversions.h"
#include "cobalt/browser/memory_tracker/tool/compressed_time_series_tool.h"
#include "cobalt/browser/memory_tracker/tool/leak_finder_tool.h"
#include "cobalt/browser/memory_tracker/tool/log_writer_tool.h"
#include "cobalt/browser/memory_tracker/tool/malloc_logger_tool.h"
#include "cobalt/browser/memory_tracker/tool/malloc_stats_tool.h"
#include "cobalt/browser/memory_tracker/tool/memory_size_binner_tool.h"
#include "cobalt/browser/memory_tracker/tool/print_csv_tool.h"
#include "cobalt/browser/memory_tracker/tool/print_tool.h"
#include "cobalt/browser/memory_tracker/tool/tool_impl.h"
#include "cobalt/browser/memory_tracker/tool/tool_thread.h"
#include "nb/analytics/memory_tracker_helpers.h"
#include "starboard/double.h"
#include "starboard/log.h"
namespace cobalt {
namespace browser {
namespace memory_tracker {
#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
// Null implementation for memory tracker tool.
class NullTool : public Tool {
public:
virtual ~NullTool() {}
};
// Instantiates the memory tracker tool from the command argument.
scoped_ptr<Tool> CreateMemoryTrackerTool(const std::string&) {
return scoped_ptr<Tool>(new NullTool);
}
#else
using nb::analytics::MemoryTracker;
namespace {
enum SwitchEnum {
kNull,
kStartup,
kContinuousPrinter,
kCompressedTimeseries,
kBinnerAnalytics,
kAllocationLogger,
kLeakTracer,
kJavascriptLeakTracer,
kMallocStats,
kMallocLogger,
};
struct SwitchVal {
SwitchVal() : help_msg(), enum_value(kNull) {}
SwitchVal(const SwitchVal& other)
: tool_name(other.tool_name),
help_msg(other.help_msg),
enum_value(other.enum_value) {}
SwitchVal(const char* name, const char* msg, SwitchEnum val)
: tool_name(name), help_msg(msg), enum_value(val) {}
std::string tool_name;
std::string help_msg;
SwitchEnum enum_value;
};
class SbLogger : public AbstractLogger {
public:
void Output(const char* str) override { SbLogRaw(str); }
void Flush() override { SbLogFlush(); }
};
// Parses out the toolname for a tool command.
// Example:
// INPUT: "tool_name(arg)"
// OUTPUT: "tool_name"
std::string ParseToolName(const std::string& command_arg) {
const size_t first_open_paren_idx = command_arg.find('(');
if (first_open_paren_idx == std::string::npos) {
return command_arg;
}
return command_arg.substr(0, first_open_paren_idx);
}
// Parses out the arguments for a tool.
// Example:
// INPUT: "tool_name(arg)"
// OUTPUT: "arg"
std::string ParseToolArg(const std::string& command_arg) {
const size_t first_open_paren_idx = command_arg.find('(');
const size_t last_closing_paren_idx = command_arg.find_last_of(')');
if ((first_open_paren_idx == std::string::npos) ||
(last_closing_paren_idx == std::string::npos)) {
return "";
}
if (last_closing_paren_idx < first_open_paren_idx) {
return "";
}
const size_t begin_idx = first_open_paren_idx + 1;
const size_t length = (last_closing_paren_idx - begin_idx);
std::string arg_str = command_arg.substr(begin_idx, length);
return arg_str;
}
struct DisableMemoryTrackerInScope {
explicit DisableMemoryTrackerInScope(MemoryTracker* tracker)
: tracker_(tracker) {
if (tracker_) {
tracker_->SetMemoryTrackingEnabled(false);
}
}
~DisableMemoryTrackerInScope() {
if (tracker_) {
tracker_->SetMemoryTrackingEnabled(true);
}
}
MemoryTracker* tracker_;
};
} // namespace.
class MemoryTrackerThreadImpl : public Tool {
public:
explicit MemoryTrackerThreadImpl(base::SimpleThread* ptr)
: thread_ptr_(ptr) {}
~MemoryTrackerThreadImpl() override { thread_ptr_.reset(NULL); }
scoped_ptr<base::SimpleThread> thread_ptr_;
};
scoped_ptr<Tool> CreateMemoryTrackerTool(const std::string& command_arg) {
// The command line switch for memory_tracker was used. Look into the args
// and determine the mode that the memory_tracker should be used for.
// The map is used to
// 1) Resolve the string to an enum.
// 2) Print a useful help method of all known memory_tracker modes.
typedef std::map<std::string, SwitchVal> SwitchMap;
SwitchVal startup_tool(
"startup(num_mins=1)", // Name of tool.
" Records high-frequency memory metrics for the first 60\n"
" seconds of program launch and then dumps it out in CSV format\n"
" to stdout.\n",
kStartup);
SwitchVal continuous_printer_tool(
"continuous_printer", // Name of tool.
" Once every second the memory state is dumped to stdout.\n",
kContinuousPrinter);
SwitchVal compressed_timeseries_tool(
"compressed_timeseries", // Name of tool.
" Use this tool to see the growth in memory usage as the app runs.\n"
" The memory growth is segmented into memory scopes and outputted as\n"
" CSV. The compressed time-series will depict the full history of\n"
" the memory using a fixed number of rows. Older history has degraded\n"
" resolution and while new entries are captured in full detail. This\n"
" achieved by evicting old entries by an exponential decay scheme.\n",
kCompressedTimeseries);
SwitchVal binner_tool(
"binner(region=NULL)",
" Dumps memory statistics once a second in CSV format to stdout.\n"
" The default memory region is all memory regions. Pass the\n"
" name of the memory region to specify that only that memory region\n"
" should be tracked. For example: binner(Javascript).\n",
kBinnerAnalytics);
SwitchVal allocation_logger_tool(
"allocation_logger",
" Continuously writes allocations and deallocations to\n"
" memory_log.txt. This is a legacy format used by lbshell. The\n"
" location of this memory_log.txt file is in the platform dependent\n"
" directory specified by kSbSystemPathDebugOutputDirectory.\n",
kAllocationLogger);
SwitchVal leak_tracing_tool(
"leak_tracer",
" Automatically detects leaks and reports them in CSV format.\n",
kLeakTracer);
SwitchVal js_leak_tracing_tool(
"js_leak_tracer",
" Automatically detects Javascript leaks and reports them in CSV\n"
" format.\n",
kJavascriptLeakTracer);
SwitchVal malloc_stats_tool(
"malloc_stats",
" Queries the allocation system for memory usage. This is the most\n"
" lightweight tool. Output is CSV format.\n",
kMallocStats);
SwitchVal malloc_logger_tool(
"malloc_logger",
" Continuously writes allocations, deallocations, allocation location\n"
" and malloc stats to memory_log_<timestamp>.csv, without headers.\n"
" The location of this log file is in the platform dependent\n"
" directory specified by kSbSystemPathDebugOutputDirectory.\n",
kMallocLogger);
SwitchMap switch_map;
switch_map[ParseToolName(startup_tool.tool_name)] = startup_tool;
switch_map[ParseToolName(continuous_printer_tool.tool_name)] =
continuous_printer_tool;
switch_map[ParseToolName(compressed_timeseries_tool.tool_name)] =
compressed_timeseries_tool;
switch_map[ParseToolName(binner_tool.tool_name)] = binner_tool;
switch_map[ParseToolName(allocation_logger_tool.tool_name)] =
allocation_logger_tool;
switch_map[ParseToolName(leak_tracing_tool.tool_name)] = leak_tracing_tool;
switch_map[ParseToolName(js_leak_tracing_tool.tool_name)] =
js_leak_tracing_tool;
switch_map[ParseToolName(malloc_logger_tool.tool_name)] =
malloc_logger_tool;
switch_map[ParseToolName(malloc_stats_tool.tool_name)] = malloc_stats_tool;
std::string tool_name = ParseToolName(command_arg);
std::string tool_arg = ParseToolArg(command_arg);
// FAST OUT - is a thread type not selected? Then print out a help menu
// and request that the app should shut down.
SwitchMap::const_iterator found_it = switch_map.find(tool_name);
if (found_it == switch_map.end()) {
// Error, tell the user what to do instead and then exit.
std::stringstream ss;
ss << "\nNo memory tracker tool selected so help has been invoked:\n";
ss << "Memory Tracker help:\n";
for (SwitchMap::const_iterator it = switch_map.begin();
it != switch_map.end(); ++it) {
const std::string& name = it->first;
const SwitchVal& val = it->second;
ss << " memory_tracker=" << name << " "
<< "\"" << val.help_msg << "\"\n";
}
ss << "\n";
SbLogRaw(ss.str().c_str());
ss.str(""); // Clears the contents of stringstream.
SbLogFlush();
// Try and turn off all logging so that the stdout is less likely to be
// polluted by interleaving output.
logging::SetMinLogLevel(INT_MAX);
// SbThreadSleep wants microseconds. We sleep here so that the user can
// read the help message before the engine starts up again and the
// fills the output with more logging text.
const SbTime four_seconds = 4000 * kSbTimeMillisecond;
SbThreadSleep(four_seconds);
found_it = switch_map.find(continuous_printer_tool.tool_name);
ss << "Defaulting to tool: " << found_it->first << "\n";
SbLogRaw(ss.str().c_str());
SbLogFlush();
// One more help message and 1-second pause. Then continue on with the
// execution as normal.
const SbTime one_second = 1000 * kSbTimeMillisecond;
SbThreadSleep(one_second);
}
// Okay we have been resolved to use a memory tracker in some way.
DLOG(INFO) << "\n\nUsing MemoryTracking=" << found_it->first << "\n";
// Tools are expected to instantiate the MemoryTracker if they need it.
MemoryTracker* memory_tracker = NULL;
scoped_ptr<AbstractTool> tool_ptr;
const SwitchVal& value = found_it->second;
switch (value.enum_value) {
case kNull: {
SB_NOTREACHED();
break;
}
case kStartup: {
double num_mins = 1.0;
if (!tool_arg.empty()) {
if (!base::StringToDouble(tool_arg, &num_mins) ||
SbDoubleIsNan(num_mins) ||
num_mins <= 0) {
num_mins = 1.0;
}
}
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
// Converts minutes into Sampling interval and also total sampling time.
// To keep the amount of elements in the output static, we adjust the
// sampling.
//
// The number of samples produced is held constants and is:
// kNumSamples = F::TotalSamplingTime(num_mins) /
// F::SamplingIntervalMs(num_mins)
struct F {
static int SamplingIntervalMs(double mins) {
// kNumSamples is chosen such that SamplingIntervalMs(1) outputs
// 240ms.
static const double kNumSamples = 250.;
const int sample_time_ms =
static_cast<int>(ToMilliseconds(mins) / kNumSamples);
return static_cast<int>(sample_time_ms);
}
// TotalSamplingTime(1) outputs 60,000 milliseconds, or 1 minute.
static int ToMilliseconds(double mins) {
const double millseconds = mins * 60. * 1000.;
return static_cast<int>(millseconds);
}
};
// Sample time increases with the number of seconds. At one second we
// sample 4 times a second. This keeps the number of samples constant
// regardless of input time.
int sampling_interval_ms = F::SamplingIntervalMs(num_mins);
// Time until output is triggered.
int sampling_time_ms = F::ToMilliseconds(num_mins);
// Create a thread that will gather memory metrics for startup.
DisableMemoryTrackerInScope disable_in_scope(memory_tracker);
tool_ptr.reset(new PrintCSVTool(sampling_interval_ms, sampling_time_ms));
break;
}
case kContinuousPrinter: {
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
// Create a thread that will continuously report memory use.
DisableMemoryTrackerInScope disable_in_scope(memory_tracker);
tool_ptr.reset(new PrintTool);
break;
}
case kCompressedTimeseries: {
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
// Create a thread that will continuously report memory use.
DisableMemoryTrackerInScope disable_in_scope(memory_tracker);
tool_ptr.reset(new CompressedTimeSeriesTool);
break;
}
case kBinnerAnalytics: {
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
DisableMemoryTrackerInScope disable_in_scope(memory_tracker);
// Create a thread that will continuously report javascript memory
// analytics.
tool_ptr.reset(new MemorySizeBinnerTool(tool_arg));
break;
}
case kAllocationLogger: {
scoped_ptr<LogWriterTool> disk_writer_tool(new LogWriterTool());
tool_ptr.reset(disk_writer_tool.release());
break;
}
case kLeakTracer: {
scoped_ptr<LeakFinderTool> leak_finder(
new LeakFinderTool(LeakFinderTool::kCPlusPlus));
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
memory_tracker->SetMemoryTrackerDebugCallback(leak_finder.get());
tool_ptr.reset(leak_finder.release());
break;
}
case kJavascriptLeakTracer: {
scoped_ptr<LeakFinderTool> leak_finder(
new LeakFinderTool(LeakFinderTool::kJavascript));
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
memory_tracker->SetMemoryTrackerDebugCallback(leak_finder.get());
tool_ptr.reset(leak_finder.release());
break;
}
case kMallocStats: {
tool_ptr.reset(new MallocStatsTool);
break;
}
case kMallocLogger: {
scoped_ptr<MallocLoggerTool> malloc_logger(
new MallocLoggerTool());
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
memory_tracker->SetMemoryTrackerDebugCallback(malloc_logger.get());
tool_ptr.reset(malloc_logger.release());
break;
}
default: {
SB_NOTREACHED() << "Unhandled case.";
break;
}
}
if (tool_ptr.get()) {
DisableMemoryTrackerInScope disable_in_scope(memory_tracker);
base::SimpleThread* thread =
new ToolThread(memory_tracker, // May be NULL.
tool_ptr.release(), new SbLogger);
return scoped_ptr<Tool>(new MemoryTrackerThreadImpl(thread));
} else {
return scoped_ptr<Tool>();
}
}
#endif // !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
} // namespace memory_tracker
} // namespace browser
} // namespace cobalt