| // 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 |