1 /*
2  * Copyright (C) 2019 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 <stdio.h>
18 
19 #include <memory>
20 #include <string>
21 #include <thread>
22 #include <vector>
23 
24 #include <android-base/file.h>
25 #include <android-base/logging.h>
26 #include <android-base/parseint.h>
27 #include <android-base/strings.h>
28 #include <android-base/unique_fd.h>
29 #include <ziparchive/zip_writer.h>
30 
31 #include "command.h"
32 #include "event_type.h"
33 #include "environment.h"
34 #include "utils.h"
35 #include "workload.h"
36 
37 namespace {
38 const std::string SIMPLEPERF_DATA_DIR = "simpleperf_data";
39 
40 class PrepareCommand : public Command {
41  public:
PrepareCommand()42   PrepareCommand()
43       : Command("api-prepare", "Prepare recording via app api",
44                 "Usage: simpleperf api-prepare\n"
45                 ) {}
46   bool Run(const std::vector<std::string>& args);
47 };
48 
Run(const std::vector<std::string> &)49 bool PrepareCommand::Run(const std::vector<std::string>&) {
50   // Enable profiling.
51   if (!CheckPerfEventLimit()) {
52     return false;
53   }
54   // Create tracepoint_events file.
55   if (!android::base::WriteStringToFile(GetTracepointEvents(),
56                                         "/data/local/tmp/tracepoint_events")) {
57     PLOG(ERROR) << "failed to write tracepoint_events file";
58     return false;
59   }
60   return true;
61 }
62 
63 class CollectCommand : public Command {
64  public:
CollectCommand()65   CollectCommand()
66       : Command("api-collect", "Collect recording data generated by app api",
67                 // clang-format off
68 "Usage: simpleperf api-collect [options]\n"
69 "--app <package_name>    the android application having recording data\n"
70 "-o record_zipfile_path  the path to store recording data\n"
71 "                        Default is simpleperf_data.zip.\n"
72 #if 0
73 // Below options are only used internally and shouldn't be visible to the public.
74 "--in-app               We are already running in the app's context.\n"
75 "--out-fd <fd>          Write output to a file descriptor.\n"
76 "--stop-signal-fd <fd>  Stop recording when fd is readable.\n"
77 #endif
78                 // clang-format on
79                 ) {}
80   bool Run(const std::vector<std::string>& args);
81 
82  private:
83   bool ParseOptions(const std::vector<std::string>& args);
84   void HandleStopSignal();
85   bool CollectRecordingData();
86   bool RemoveRecordingData();
87 
88   std::string app_name_;
89   std::string output_filepath_ = "simpleperf_data.zip";
90   bool in_app_context_ = false;
91   android::base::unique_fd out_fd_;
92   android::base::unique_fd stop_signal_fd_;
93 };
94 
Run(const std::vector<std::string> & args)95 bool CollectCommand::Run(const std::vector<std::string>& args) {
96   if (!ParseOptions(args)) {
97     return false;
98   }
99   if (in_app_context_) {
100     HandleStopSignal();
101     return CollectRecordingData() && RemoveRecordingData();
102   }
103   return RunInAppContext(app_name_, Name(), args, 0, output_filepath_, false);
104 }
105 
ParseOptions(const std::vector<std::string> & args)106 bool CollectCommand::ParseOptions(const std::vector<std::string>& args) {
107   for (size_t i = 0; i < args.size(); ++i) {
108     if (args[i] == "--app") {
109       if (!NextArgumentOrError(args, &i)) {
110         return false;
111       }
112       app_name_ = args[i];
113     } else if (args[i] == "--in-app") {
114       in_app_context_ = true;
115     } else if (args[i] == "-o") {
116       if (!NextArgumentOrError(args, &i)) {
117         return false;
118       }
119       output_filepath_ = args[i];
120     } else if (args[i] == "--out-fd") {
121       int fd;
122       if (!GetUintOption(args, &i, &fd)) {
123         return false;
124       }
125       out_fd_.reset(fd);
126     } else if (args[i] == "--stop-signal-fd") {
127       int fd;
128       if (!GetUintOption(args, &i, &fd)) {
129         return false;
130       }
131       stop_signal_fd_.reset(fd);
132     } else {
133       ReportUnknownOption(args, i);
134       return false;
135     }
136   }
137   if (!in_app_context_) {
138     if (app_name_.empty()) {
139       LOG(ERROR) << "--app is missing";
140       return false;
141     }
142   }
143   return true;
144 }
145 
HandleStopSignal()146 void CollectCommand::HandleStopSignal() {
147   int fd = stop_signal_fd_.release();
148   std::thread thread([fd]() {
149     char c;
150     static_cast<void>(read(fd, &c, 1));
151     exit(1);
152   });
153   thread.detach();
154 }
155 
CollectRecordingData()156 bool CollectCommand::CollectRecordingData() {
157   std::unique_ptr<FILE, decltype(&fclose)>  fp(android::base::Fdopen(std::move(out_fd_), "w"),
158                                                fclose);
159   if (fp == nullptr) {
160     PLOG(ERROR) << "failed to call fdopen";
161     return false;
162   }
163   std::vector<char> buffer(64 * 1024);
164   ZipWriter zip_writer(fp.get());
165   for (const auto& name : GetEntriesInDir(SIMPLEPERF_DATA_DIR)) {
166     // No need to collect temporary files.
167     const std::string path = SIMPLEPERF_DATA_DIR + "/" + name;
168     if (android::base::StartsWith(name, "TemporaryFile-") || !IsRegularFile(path)) {
169       continue;
170     }
171     int result = zip_writer.StartEntry(name.c_str(), ZipWriter::kCompress);
172     if (result != 0) {
173       LOG(ERROR) << "failed to start zip entry " << name << ": "
174                  << zip_writer.ErrorCodeString(result);
175       return false;
176     }
177     android::base::unique_fd in_fd(FileHelper::OpenReadOnly(path));
178     if (in_fd == -1) {
179       PLOG(ERROR) << "failed to open " << path;
180       return false;
181     }
182     while (true) {
183       ssize_t nread = TEMP_FAILURE_RETRY(read(in_fd, buffer.data(), buffer.size()));
184       if (nread < 0) {
185         PLOG(ERROR) << "failed to read " << path;
186         return false;
187       }
188       if (nread == 0) {
189         break;
190       }
191       result = zip_writer.WriteBytes(buffer.data(), nread);
192       if (result != 0) {
193         LOG(ERROR) << "failed to write zip entry " << name << ": "
194                    << zip_writer.ErrorCodeString(result);
195         return false;
196       }
197     }
198     result = zip_writer.FinishEntry();
199     if (result != 0) {
200       LOG(ERROR) << "failed to finish zip entry " << name << ": "
201                  << zip_writer.ErrorCodeString(result);
202       return false;
203     }
204   }
205   int result = zip_writer.Finish();
206   if (result != 0) {
207     LOG(ERROR) << "failed to finish zip writer: " << zip_writer.ErrorCodeString(result);
208     return false;
209   }
210   return true;
211 }
212 
RemoveRecordingData()213 bool CollectCommand::RemoveRecordingData() {
214   return Workload::RunCmd({"rm", "-rf", SIMPLEPERF_DATA_DIR});
215 }
216 }  // namespace
217 
RegisterAPICommands()218 void RegisterAPICommands() {
219   RegisterCommand("api-prepare",
220                   []{ return std::unique_ptr<Command>(new PrepareCommand()); });
221   RegisterCommand("api-collect",
222                   []{ return std::unique_ptr<Command>(new CollectCommand()); });
223 }
224