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