blob: 687c21de6eba4dbe73cadc44437b78fde67a859b [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/memory_tracker_tool.h"
#include <map>
#include <string>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "cobalt/browser/memory_tracker/memory_tracker_tool_impl.h"
#include "nb/analytics/memory_tracker_helpers.h"
#include "starboard/log.h"
namespace cobalt {
namespace browser {
namespace memory_tracker {
using nb::analytics::MemoryTracker;
namespace {
enum SwitchEnum {
kNull,
kStartup,
kContinuousPrinter,
kCompressedTimeseries,
kBinnerAnalytics,
kAllocationLogger,
};
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:
virtual void Output(const char* str) OVERRIDE { SbLogRaw(str); }
virtual 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;
}
} // namespace.
class MemoryTrackerThreadImpl : public MemoryTrackerTool {
public:
explicit MemoryTrackerThreadImpl(base::SimpleThread* ptr)
: thread_ptr_(ptr) {}
virtual ~MemoryTrackerThreadImpl() OVERRIDE { thread_ptr_.reset(NULL); }
scoped_ptr<base::SimpleThread> thread_ptr_;
};
scoped_ptr<MemoryTrackerTool> 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", // Name of tool.
"Records high-frequency memory metrics for the first 60 "
"seconds of program launch and then dumps it out in CSV format "
"to stdout.",
kStartup);
SwitchVal continuous_printer_tool(
"continuous_printer", // Name of tool.
"Once every second the memory state is dumped to stdout.",
kContinuousPrinter);
SwitchVal compressed_timeseries_tool(
"compressed_timeseries", // Name of tool.
"Use this tool to see the growth in memory usage as the app runs. The "
"memory growth is segmented into memory scopes and outputted as CSV. "
"The compressed time-series will depict the full history of the memory "
"using a fixed number of rows. Older history has degraded resolution and "
"while new entries are captured in full detail. This achieved by "
"evicting old entries by an exponential decay scheme.",
kCompressedTimeseries);
SwitchVal binner_tool(
"binner",
"Dumps memory statistics once a second in CSV format to stdout. "
"The default memory region is all memory regions. Pass the "
"name of the memory region to specify that only that memory region "
"should be tracked. For example: binner(Javascript).",
kBinnerAnalytics);
SwitchVal allocation_logger_tool(
"allocation_logger",
"Continuously writes allocations and deallocations to memory_log.txt. "
"This is a legacy format used by lbshell. The location of this "
"memory_log.txt file is in the platform dependent directory specified "
"by kSbSystemPathDebugOutputDirectory.",
kAllocationLogger);
SwitchMap switch_map;
switch_map[startup_tool.tool_name] = startup_tool;
switch_map[continuous_printer_tool.tool_name] = continuous_printer_tool;
switch_map[compressed_timeseries_tool.tool_name] = compressed_timeseries_tool;
switch_map[binner_tool.tool_name] = binner_tool;
switch_map[allocation_logger_tool.tool_name] = allocation_logger_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<AbstractMemoryTrackerTool> tool_ptr;
const SwitchVal& value = found_it->second;
switch (value.enum_value) {
case kNull: {
SB_NOTREACHED();
break;
}
case kStartup: {
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
// Create a thread that will gather memory metrics for startup.
int sampling_interval_ms = 1000; // Sample once a second.
int sampling_time_ms = 60 * 1000; // Output after 60 seconds
tool_ptr.reset(
new MemoryTrackerPrintCSV(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.
tool_ptr.reset(new MemoryTrackerPrint);
break;
}
case kCompressedTimeseries: {
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
// Create a thread that will continuously report memory use.
tool_ptr.reset(new MemoryTrackerCompressedTimeSeries);
break;
}
case kBinnerAnalytics: {
memory_tracker = MemoryTracker::Get();
memory_tracker->InstallGlobalTrackingHooks();
// Create a thread that will continuously report javascript memory
// analytics.
tool_ptr.reset(new MemorySizeBinner(tool_arg));
break;
}
case kAllocationLogger: {
scoped_ptr<MemoryTrackerLogWriter> disk_writer_tool(
new MemoryTrackerLogWriter());
tool_ptr.reset(disk_writer_tool.release());
break;
}
default: {
SB_NOTREACHED() << "Unhandled case.";
break;
}
}
if (tool_ptr.get()) {
base::SimpleThread* thread =
new MemoryTrackerToolThread(memory_tracker, // May be NULL.
tool_ptr.release(), new SbLogger);
return scoped_ptr<MemoryTrackerTool>(new MemoryTrackerThreadImpl(thread));
} else {
return scoped_ptr<MemoryTrackerTool>();
}
}
} // namespace memory_tracker
} // namespace browser
} // namespace cobalt