1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "tools/gn/trace.h" 6 7 #include <algorithm> 8 #include <map> 9 #include <sstream> 10 #include <vector> 11 12 #include "base/files/file_util.h" 13 #include "base/json/string_escape.h" 14 #include "base/logging.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/synchronization/lock.h" 17 #include "tools/gn/filesystem_utils.h" 18 #include "tools/gn/label.h" 19 20 namespace { 21 22 class TraceLog { 23 public: 24 TraceLog() { 25 events_.reserve(16384); 26 } 27 // Trace items leaked intentionally. 28 29 void Add(TraceItem* item) { 30 base::AutoLock lock(lock_); 31 events_.push_back(item); 32 } 33 34 // Returns a copy for threadsafety. 35 std::vector<TraceItem*> events() const { return events_; } 36 37 private: 38 base::Lock lock_; 39 40 std::vector<TraceItem*> events_; 41 42 DISALLOW_COPY_AND_ASSIGN(TraceLog); 43 }; 44 45 TraceLog* trace_log = NULL; 46 47 struct Coalesced { 48 Coalesced() : name_ptr(NULL), total_duration(0.0), count(0) {} 49 50 const std::string* name_ptr; // Pointer to a string with the name in it. 51 double total_duration; 52 int count; 53 }; 54 55 bool DurationGreater(const TraceItem* a, const TraceItem* b) { 56 return a->delta() > b->delta(); 57 } 58 59 bool CoalescedDurationGreater(const Coalesced& a, const Coalesced& b) { 60 return a.total_duration > b.total_duration; 61 } 62 63 void SummarizeParses(std::vector<const TraceItem*>& loads, 64 std::ostream& out) { 65 out << "File parse times: (time in ms, name)\n"; 66 67 std::sort(loads.begin(), loads.end(), &DurationGreater); 68 69 for (size_t i = 0; i < loads.size(); i++) { 70 out << base::StringPrintf(" %8.2f ", 71 loads[i]->delta().InMillisecondsF()); 72 out << loads[i]->name() << std::endl; 73 } 74 } 75 76 void SummarizeCoalesced(std::vector<const TraceItem*>& items, 77 std::ostream& out) { 78 // Group by file name. 79 std::map<std::string, Coalesced> coalesced; 80 for (size_t i = 0; i < items.size(); i++) { 81 Coalesced& c = coalesced[items[i]->name()]; 82 c.name_ptr = &items[i]->name(); 83 c.total_duration += items[i]->delta().InMillisecondsF(); 84 c.count++; 85 } 86 87 // Sort by duration. 88 std::vector<Coalesced> sorted; 89 for (std::map<std::string, Coalesced>::iterator iter = coalesced.begin(); 90 iter != coalesced.end(); ++iter) 91 sorted.push_back(iter->second); 92 std::sort(sorted.begin(), sorted.end(), &CoalescedDurationGreater); 93 94 for (size_t i = 0; i < sorted.size(); i++) { 95 out << base::StringPrintf(" %8.2f %d ", 96 sorted[i].total_duration, sorted[i].count); 97 out << *sorted[i].name_ptr << std::endl; 98 } 99 } 100 101 void SummarizeFileExecs(std::vector<const TraceItem*>& execs, 102 std::ostream& out) { 103 out << "File execute times: (total time in ms, # executions, name)\n"; 104 SummarizeCoalesced(execs, out); 105 } 106 107 void SummarizeScriptExecs(std::vector<const TraceItem*>& execs, 108 std::ostream& out) { 109 out << "Script execute times: (total time in ms, # executions, name)\n"; 110 SummarizeCoalesced(execs, out); 111 } 112 113 } // namespace 114 115 TraceItem::TraceItem(Type type, 116 const std::string& name, 117 base::PlatformThreadId thread_id) 118 : type_(type), 119 name_(name), 120 thread_id_(thread_id) { 121 } 122 123 TraceItem::~TraceItem() { 124 } 125 126 ScopedTrace::ScopedTrace(TraceItem::Type t, const std::string& name) 127 : item_(NULL), 128 done_(false) { 129 if (trace_log) { 130 item_ = new TraceItem(t, name, base::PlatformThread::CurrentId()); 131 item_->set_begin(base::TimeTicks::HighResNow()); 132 } 133 } 134 135 ScopedTrace::ScopedTrace(TraceItem::Type t, const Label& label) 136 : item_(NULL), 137 done_(false) { 138 if (trace_log) { 139 item_ = new TraceItem(t, label.GetUserVisibleName(false), 140 base::PlatformThread::CurrentId()); 141 item_->set_begin(base::TimeTicks::HighResNow()); 142 } 143 } 144 145 ScopedTrace::~ScopedTrace() { 146 Done(); 147 } 148 149 void ScopedTrace::SetToolchain(const Label& label) { 150 if (item_) 151 item_->set_toolchain(label.GetUserVisibleName(false)); 152 } 153 154 void ScopedTrace::SetCommandLine(const CommandLine& cmdline) { 155 if (item_) 156 item_->set_cmdline(FilePathToUTF8(cmdline.GetArgumentsString())); 157 } 158 159 void ScopedTrace::Done() { 160 if (!done_) { 161 done_ = true; 162 if (trace_log) { 163 item_->set_end(base::TimeTicks::HighResNow()); 164 AddTrace(item_); 165 } 166 } 167 } 168 169 void EnableTracing() { 170 if (!trace_log) 171 trace_log = new TraceLog; 172 } 173 174 void AddTrace(TraceItem* item) { 175 trace_log->Add(item); 176 } 177 178 std::string SummarizeTraces() { 179 if (!trace_log) 180 return std::string(); 181 182 std::vector<TraceItem*> events = trace_log->events(); 183 184 // Classify all events. 185 std::vector<const TraceItem*> parses; 186 std::vector<const TraceItem*> file_execs; 187 std::vector<const TraceItem*> script_execs; 188 std::vector<const TraceItem*> check_headers; 189 int headers_checked = 0; 190 for (size_t i = 0; i < events.size(); i++) { 191 switch (events[i]->type()) { 192 case TraceItem::TRACE_FILE_PARSE: 193 parses.push_back(events[i]); 194 break; 195 case TraceItem::TRACE_FILE_EXECUTE: 196 file_execs.push_back(events[i]); 197 break; 198 case TraceItem::TRACE_SCRIPT_EXECUTE: 199 script_execs.push_back(events[i]); 200 break; 201 case TraceItem::TRACE_CHECK_HEADERS: 202 check_headers.push_back(events[i]); 203 break; 204 case TraceItem::TRACE_CHECK_HEADER: 205 headers_checked++; 206 break; 207 case TraceItem::TRACE_SETUP: 208 case TraceItem::TRACE_FILE_LOAD: 209 case TraceItem::TRACE_FILE_WRITE: 210 case TraceItem::TRACE_DEFINE_TARGET: 211 break; // Ignore these for the summary. 212 } 213 } 214 215 std::ostringstream out; 216 SummarizeParses(parses, out); 217 out << std::endl; 218 SummarizeFileExecs(file_execs, out); 219 out << std::endl; 220 SummarizeScriptExecs(script_execs, out); 221 out << std::endl; 222 223 // Generally there will only be one header check, but it's theoretically 224 // possible for more than one to run if more than one build is going in 225 // parallel. Just report the total of all of them. 226 if (!check_headers.empty()) { 227 float check_headers_time = 0; 228 for (size_t i = 0; i < check_headers.size(); i++) 229 check_headers_time += check_headers[i]->delta().InMillisecondsF(); 230 231 out << "Header check time: (total time in ms, files checked)\n"; 232 out << base::StringPrintf(" %8.2f %d\n", 233 check_headers_time, headers_checked); 234 } 235 236 return out.str(); 237 } 238 239 void SaveTraces(const base::FilePath& file_name) { 240 std::ostringstream out; 241 242 out << "{\"traceEvents\":["; 243 244 std::string quote_buffer; // Allocate outside loop to prevent reallocationg. 245 246 // Write main thread metadata (assume this is being written on the main 247 // thread). 248 out << "{\"pid\":0,\"tid\":" << base::PlatformThread::CurrentId(); 249 out << ",\"ts\":0,\"ph\":\"M\","; 250 out << "\"name\":\"thread_name\",\"args\":{\"name\":\"Main thread\"}},"; 251 252 std::vector<TraceItem*> events = trace_log->events(); 253 for (size_t i = 0; i < events.size(); i++) { 254 const TraceItem& item = *events[i]; 255 256 if (i != 0) 257 out << ","; 258 out << "{\"pid\":0,\"tid\":" << item.thread_id(); 259 out << ",\"ts\":" << item.begin().ToInternalValue(); 260 out << ",\"ph\":\"X\""; // "X" = complete event with begin & duration. 261 out << ",\"dur\":" << item.delta().InMicroseconds(); 262 263 quote_buffer.resize(0); 264 base::EscapeJSONString(item.name(), true, "e_buffer); 265 out << ",\"name\":" << quote_buffer; 266 267 out << ",\"cat\":"; 268 switch (item.type()) { 269 case TraceItem::TRACE_SETUP: 270 out << "\"setup\""; 271 break; 272 case TraceItem::TRACE_FILE_LOAD: 273 out << "\"load\""; 274 break; 275 case TraceItem::TRACE_FILE_PARSE: 276 out << "\"parse\""; 277 break; 278 case TraceItem::TRACE_FILE_EXECUTE: 279 out << "\"file_exec\""; 280 break; 281 case TraceItem::TRACE_FILE_WRITE: 282 out << "\"file_write\""; 283 break; 284 case TraceItem::TRACE_SCRIPT_EXECUTE: 285 out << "\"script_exec\""; 286 break; 287 case TraceItem::TRACE_DEFINE_TARGET: 288 out << "\"define\""; 289 break; 290 case TraceItem::TRACE_CHECK_HEADER: 291 out << "\"hdr\""; 292 break; 293 case TraceItem::TRACE_CHECK_HEADERS: 294 out << "\"header_check\""; 295 break; 296 } 297 298 if (!item.toolchain().empty() || !item.cmdline().empty()) { 299 out << ",\"args\":{"; 300 bool needs_comma = false; 301 if (!item.toolchain().empty()) { 302 quote_buffer.resize(0); 303 base::EscapeJSONString(item.toolchain(), true, "e_buffer); 304 out << "\"toolchain\":" << quote_buffer; 305 needs_comma = true; 306 } 307 if (!item.cmdline().empty()) { 308 quote_buffer.resize(0); 309 base::EscapeJSONString(item.cmdline(), true, "e_buffer); 310 if (needs_comma) 311 out << ","; 312 out << "\"cmdline\":" << quote_buffer; 313 needs_comma = true; 314 } 315 out << "}"; 316 } 317 out << "}"; 318 } 319 320 out << "]}"; 321 322 std::string out_str = out.str(); 323 base::WriteFile(file_name, out_str.data(), 324 static_cast<int>(out_str.size())); 325 } 326