1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "src/perfetto_cmd/perfetto_cmd.h" 18 19 #include <fcntl.h> 20 #include <getopt.h> 21 #include <signal.h> 22 #include <stdio.h> 23 #include <sys/stat.h> 24 #include <time.h> 25 #include <unistd.h> 26 27 #include <fstream> 28 #include <iostream> 29 #include <iterator> 30 #include <sstream> 31 32 #include "perfetto/base/file_utils.h" 33 #include "perfetto/base/logging.h" 34 #include "perfetto/base/time.h" 35 #include "perfetto/base/utils.h" 36 #include "perfetto/protozero/proto_utils.h" 37 #include "perfetto/traced/traced.h" 38 #include "perfetto/tracing/core/data_source_config.h" 39 #include "perfetto/tracing/core/data_source_descriptor.h" 40 #include "perfetto/tracing/core/trace_config.h" 41 #include "perfetto/tracing/core/trace_packet.h" 42 43 #include "perfetto/config/trace_config.pb.h" 44 45 #include "src/tracing/ipc/default_socket.h" 46 47 #include "google/protobuf/io/zero_copy_stream_impl_lite.h" 48 49 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) 50 #include <android/os/DropBoxManager.h> 51 #include <utils/Looper.h> 52 #include <utils/StrongPointer.h> 53 #endif // PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) 54 55 // TODO(primiano): add the ability to pass the file descriptor directly to the 56 // traced service instead of receiving a copy of the slices and writing them 57 // from this process. 58 namespace perfetto { 59 namespace { 60 61 constexpr char kDefaultDropBoxTag[] = "perfetto"; 62 63 perfetto::PerfettoCmd* g_consumer_cmd; 64 65 } // namespace 66 67 // Temporary directory for DropBox traces. Note that this is automatically 68 // created by the system by setting setprop persist.traced.enable=1. 69 const char* kTempDropBoxTraceDir = "/data/misc/perfetto-traces"; 70 71 using protozero::proto_utils::WriteVarInt; 72 using protozero::proto_utils::MakeTagLengthDelimited; 73 74 int PerfettoCmd::PrintUsage(const char* argv0) { 75 PERFETTO_ELOG(R"( 76 Usage: %s 77 --background -b : Exits immediately and continues tracing in background 78 --config -c : /path/to/trace/config/file or - for stdin 79 --out -o : /path/to/out/trace/file 80 --dropbox -d TAG : Upload trace into DropBox using tag TAG (default: %s) 81 --no-guardrails -n : Ignore guardrails triggered when using --dropbox (for testing). 82 --help -h 83 84 statsd-specific flags: 85 --alert-id : ID of the alert that triggered this trace. 86 --config-id : ID of the triggering config. 87 --config-uid : UID of app which registered the config. 88 )", 89 argv0, kDefaultDropBoxTag); 90 return 1; 91 } 92 93 int PerfettoCmd::Main(int argc, char** argv) { 94 enum LongOption { 95 OPT_ALERT_ID = 1000, 96 OPT_CONFIG_ID, 97 OPT_CONFIG_UID, 98 }; 99 static const struct option long_options[] = { 100 // |option_index| relies on the order of options, don't reshuffle them. 101 {"help", required_argument, nullptr, 'h'}, 102 {"config", required_argument, nullptr, 'c'}, 103 {"out", required_argument, nullptr, 'o'}, 104 {"background", no_argument, nullptr, 'b'}, 105 {"dropbox", optional_argument, nullptr, 'd'}, 106 {"no-guardrails", optional_argument, nullptr, 'n'}, 107 {"alert-id", required_argument, nullptr, OPT_ALERT_ID}, 108 {"config-id", required_argument, nullptr, OPT_CONFIG_ID}, 109 {"config-uid", required_argument, nullptr, OPT_CONFIG_UID}, 110 {nullptr, 0, nullptr, 0}}; 111 112 int option_index = 0; 113 std::string trace_config_raw; 114 bool background = false; 115 bool ignore_guardrails = false; 116 perfetto::protos::TraceConfig::StatsdMetadata statsd_metadata; 117 for (;;) { 118 int option = 119 getopt_long(argc, argv, "c:o:bd::n", long_options, &option_index); 120 121 if (option == -1) 122 break; // EOF. 123 124 if (option == 'c') { 125 if (strcmp(optarg, "-") == 0) { 126 std::istreambuf_iterator<char> begin(std::cin), end; 127 trace_config_raw.assign(begin, end); 128 } else if (strcmp(optarg, ":test") == 0) { 129 // TODO(primiano): temporary for testing only. 130 perfetto::protos::TraceConfig test_config; 131 test_config.add_buffers()->set_size_kb(4096); 132 test_config.set_duration_ms(2000); 133 auto* ds_config = test_config.add_data_sources()->mutable_config(); 134 ds_config->set_name("linux.ftrace"); 135 ds_config->mutable_ftrace_config()->add_ftrace_events("sched_switch"); 136 ds_config->mutable_ftrace_config()->add_ftrace_events("cpu_idle"); 137 ds_config->mutable_ftrace_config()->add_ftrace_events("cpu_frequency"); 138 ds_config->set_target_buffer(0); 139 test_config.SerializeToString(&trace_config_raw); 140 } else { 141 if (!base::ReadFile(optarg, &trace_config_raw)) { 142 PERFETTO_PLOG("Could not open %s", optarg); 143 return 1; 144 } 145 } 146 continue; 147 } 148 149 if (option == 'o') { 150 trace_out_path_ = optarg; 151 continue; 152 } 153 154 if (option == 'd') { 155 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) 156 dropbox_tag_ = optarg ? optarg : kDefaultDropBoxTag; 157 continue; 158 #else 159 PERFETTO_ELOG("DropBox is only supported with Android tree builds"); 160 return 1; 161 #endif 162 } 163 164 if (option == 'b') { 165 background = true; 166 continue; 167 } 168 169 if (option == 'n') { 170 ignore_guardrails = true; 171 continue; 172 } 173 174 if (option == OPT_ALERT_ID) { 175 statsd_metadata.set_triggering_alert_id(atoll(optarg)); 176 continue; 177 } 178 179 if (option == OPT_CONFIG_ID) { 180 statsd_metadata.set_triggering_config_id(atoll(optarg)); 181 continue; 182 } 183 184 if (option == OPT_CONFIG_UID) { 185 statsd_metadata.set_triggering_config_uid(atoi(optarg)); 186 continue; 187 } 188 189 return PrintUsage(argv[0]); 190 } 191 192 if (!trace_out_path_.empty() && !dropbox_tag_.empty()) { 193 PERFETTO_ELOG( 194 "Can't log to a file (--out) and DropBox (--dropbox) at the same " 195 "time"); 196 return 1; 197 } 198 199 if (trace_out_path_.empty() && dropbox_tag_.empty()) { 200 return PrintUsage(argv[0]); 201 } 202 203 if (trace_config_raw.empty()) { 204 PERFETTO_ELOG("The TraceConfig is empty"); 205 return 1; 206 } 207 208 perfetto::protos::TraceConfig trace_config_proto; 209 PERFETTO_DLOG("Parsing TraceConfig, %zu bytes", trace_config_raw.size()); 210 bool parsed = trace_config_proto.ParseFromString(trace_config_raw); 211 if (!parsed) { 212 PERFETTO_ELOG("Could not parse TraceConfig proto from stdin"); 213 return 1; 214 } 215 *trace_config_proto.mutable_statsd_metadata() = std::move(statsd_metadata); 216 trace_config_.reset(new TraceConfig()); 217 trace_config_->FromProto(trace_config_proto); 218 trace_config_raw.clear(); 219 220 if (!OpenOutputFile()) 221 return 1; 222 223 if (background) { 224 PERFETTO_CHECK(daemon(0 /*nochdir*/, 0 /*noclose*/) == 0); 225 PERFETTO_DLOG("Continuing in background"); 226 } 227 228 RateLimiter limiter; 229 RateLimiter::Args args{}; 230 args.is_dropbox = !dropbox_tag_.empty(); 231 args.current_time = base::GetWallTimeS(); 232 args.ignore_guardrails = ignore_guardrails; 233 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_USERDEBUG_BUILD) 234 args.max_upload_bytes_override = 235 trace_config_->guardrail_overrides().max_upload_per_day_bytes(); 236 #endif 237 if (!limiter.ShouldTrace(args)) 238 return 1; 239 240 consumer_endpoint_ = 241 ConsumerIPCClient::Connect(GetConsumerSocket(), this, &task_runner_); 242 SetupCtrlCSignalHandler(); 243 task_runner_.Run(); 244 245 return limiter.OnTraceDone(args, did_process_full_trace_, 246 bytes_uploaded_to_dropbox_) 247 ? 0 248 : 1; 249 } 250 251 void PerfettoCmd::OnConnect() { 252 PERFETTO_LOG( 253 "Connected to the Perfetto traced service, starting tracing for %d ms", 254 trace_config_->duration_ms()); 255 PERFETTO_DCHECK(trace_config_); 256 trace_config_->set_enable_extra_guardrails(!dropbox_tag_.empty()); 257 258 base::ScopedFile optional_fd; 259 if (trace_config_->write_into_file()) 260 optional_fd.reset(dup(fileno(*trace_out_stream_))); 261 262 consumer_endpoint_->EnableTracing(*trace_config_, std::move(optional_fd)); 263 264 // Failsafe mechanism to avoid waiting indefinitely if the service hangs. 265 if (trace_config_->duration_ms()) { 266 task_runner_.PostDelayedTask(std::bind(&PerfettoCmd::OnTimeout, this), 267 trace_config_->duration_ms() + 10000); 268 } 269 } 270 271 void PerfettoCmd::OnDisconnect() { 272 PERFETTO_LOG("Disconnected from the Perfetto traced service"); 273 task_runner_.Quit(); 274 } 275 276 void PerfettoCmd::OnTimeout() { 277 PERFETTO_ELOG("Timed out while waiting for trace from the service, aborting"); 278 task_runner_.Quit(); 279 } 280 281 void PerfettoCmd::OnTraceData(std::vector<TracePacket> packets, bool has_more) { 282 for (TracePacket& packet : packets) { 283 uint8_t preamble[16]; 284 uint8_t* pos = preamble; 285 // ID of the |packet| field in trace.proto. Hardcoded as this we not depend 286 // on protos/trace:lite for binary size saving reasons. 287 static constexpr uint32_t kPacketFieldNumber = 1; 288 pos = WriteVarInt(MakeTagLengthDelimited(kPacketFieldNumber), pos); 289 pos = WriteVarInt(static_cast<uint32_t>(packet.size()), pos); 290 fwrite(reinterpret_cast<const char*>(preamble), 291 static_cast<size_t>(pos - preamble), 1, trace_out_stream_.get()); 292 for (const Slice& slice : packet.slices()) { 293 fwrite(reinterpret_cast<const char*>(slice.start), slice.size, 1, 294 trace_out_stream_.get()); 295 } 296 } 297 298 if (!has_more) 299 FinalizeTraceAndExit(); // Reached end of trace. 300 } 301 302 void PerfettoCmd::OnTracingDisabled() { 303 if (trace_config_->write_into_file()) { 304 // If write_into_file == true, at this point the passed file contains 305 // already all the packets. 306 return FinalizeTraceAndExit(); 307 } 308 // This will cause a bunch of OnTraceData callbacks. The last one will 309 // save the file and exit. 310 consumer_endpoint_->ReadBuffers(); 311 } 312 313 void PerfettoCmd::FinalizeTraceAndExit() { 314 fflush(*trace_out_stream_); 315 fseek(*trace_out_stream_, 0, SEEK_END); 316 long bytes_written = ftell(*trace_out_stream_); 317 if (dropbox_tag_.empty()) { 318 trace_out_stream_.reset(); 319 did_process_full_trace_ = true; 320 PERFETTO_ILOG("Wrote %ld bytes into %s", bytes_written, 321 trace_out_path_.c_str()); 322 } else { 323 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) 324 android::sp<android::os::DropBoxManager> dropbox = 325 new android::os::DropBoxManager(); 326 fseek(*trace_out_stream_, 0, SEEK_SET); 327 // DropBox takes ownership of the file descriptor, so give it a duplicate. 328 // Also we need to give it a read-only copy of the fd or will hit a SELinux 329 // violation (about system_server ending up with a writable FD to our dir). 330 char fdpath[64]; 331 sprintf(fdpath, "/proc/self/fd/%d", fileno(*trace_out_stream_)); 332 base::ScopedFile read_only_fd(open(fdpath, O_RDONLY)); 333 PERFETTO_CHECK(read_only_fd); 334 trace_out_stream_.reset(); 335 android::binder::Status status = 336 dropbox->addFile(android::String16(dropbox_tag_.c_str()), 337 read_only_fd.release(), 0 /* flags */); 338 if (status.isOk()) { 339 // TODO(hjd): Account for compression. 340 did_process_full_trace_ = true; 341 bytes_uploaded_to_dropbox_ = bytes_written; 342 PERFETTO_ILOG("Uploaded %ld bytes into DropBox with tag %s", 343 bytes_written, dropbox_tag_.c_str()); 344 } else { 345 PERFETTO_ELOG("DropBox upload failed: %s", status.toString8().c_str()); 346 } 347 #endif // PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) 348 } 349 task_runner_.Quit(); 350 } 351 352 bool PerfettoCmd::OpenOutputFile() { 353 base::ScopedFile fd; 354 if (!dropbox_tag_.empty()) { 355 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) 356 // If we are tracing to DropBox, there's no need to make a 357 // filesystem-visible temporary file. 358 // TODO(skyostil): Fall back to base::TempFile for older devices. 359 fd.reset(open(kTempDropBoxTraceDir, O_TMPFILE | O_RDWR, 0600)); 360 if (!fd) { 361 PERFETTO_ELOG("Could not create a temporary trace file in %s", 362 kTempDropBoxTraceDir); 363 return false; 364 } 365 #else 366 PERFETTO_FATAL("Tracing to Dropbox requires the Android build."); 367 #endif 368 } else { 369 fd.reset(open(trace_out_path_.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0600)); 370 } 371 trace_out_stream_.reset(fdopen(fd.release(), "wb")); 372 PERFETTO_CHECK(trace_out_stream_); 373 return true; 374 } 375 376 void PerfettoCmd::SetupCtrlCSignalHandler() { 377 // Setup the pipe used to deliver the CTRL-C notification from signal handler. 378 int pipe_fds[2]; 379 PERFETTO_CHECK(pipe(pipe_fds) == 0); 380 ctrl_c_pipe_rd_.reset(pipe_fds[0]); 381 ctrl_c_pipe_wr_.reset(pipe_fds[1]); 382 383 // Setup signal handler. 384 struct sigaction sa {}; 385 386 // Glibc headers for sa_sigaction trigger this. 387 #pragma GCC diagnostic push 388 #if defined(__clang__) 389 #pragma GCC diagnostic ignored "-Wdisabled-macro-expansion" 390 #endif 391 sa.sa_handler = [](int) { 392 PERFETTO_LOG("SIGINT received: disabling tracing"); 393 char one = '1'; 394 PERFETTO_CHECK(PERFETTO_EINTR(write(g_consumer_cmd->ctrl_c_pipe_wr(), &one, 395 sizeof(one))) == 1); 396 }; 397 sa.sa_flags = static_cast<decltype(sa.sa_flags)>(SA_RESETHAND | SA_RESTART); 398 #pragma GCC diagnostic pop 399 sigaction(SIGINT, &sa, nullptr); 400 401 task_runner_.AddFileDescriptorWatch( 402 *ctrl_c_pipe_rd_, [this] { consumer_endpoint_->DisableTracing(); }); 403 } 404 405 int __attribute__((visibility("default"))) 406 PerfettoCmdMain(int argc, char** argv) { 407 g_consumer_cmd = new perfetto::PerfettoCmd(); 408 return g_consumer_cmd->Main(argc, argv); 409 } 410 411 } // namespace perfetto 412