1 /*
2 *
3 * Copyright 2015, The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #include "perfprofd_cmdline.h"
19
20 #include <fcntl.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 #include <unistd.h>
28
29 #include <set>
30 #include <string>
31
32 #include <android-base/logging.h>
33 #include <android-base/macros.h>
34 #include <android-base/stringprintf.h>
35
36 #include "perfprofd_record.pb.h"
37
38 #include "configreader.h"
39 #include "dropbox.h"
40 #include "perfprofdcore.h"
41 #include "perfprofd_io.h"
42
43 //
44 // Perf profiling daemon -- collects system-wide profiles using
45 //
46 // simpleperf record -a
47 //
48 // and encodes them so that they can be uploaded by a separate service.
49 //
50
51 //
52
53 //
54 // Output file from 'perf record'.
55 //
56 #define PERF_OUTPUT "perf.data"
57
58 //
59 // Path to the perf file to convert and exit? Empty value is the default, daemon mode.
60 //
61 static std::string perf_file_to_convert = "";
62
63 //
64 // SIGHUP handler. Sending SIGHUP to the daemon can be used to break it
65 // out of a sleep() call so as to trigger a new collection (debugging)
66 //
sig_hup(int)67 static void sig_hup(int /* signum */)
68 {
69 LOG(WARNING) << "SIGHUP received";
70 }
71
72 //
73 // Parse command line args. Currently supported flags:
74 // * "-c PATH" sets the path of the config file to PATH.
75 // * "-x PATH" reads PATH as a perf data file and saves it as a file in
76 // perf_profile.proto format. ".encoded" suffix is appended to PATH to form
77 // the output file path.
78 //
parse_args(int argc,char ** argv)79 static void parse_args(int argc, char** argv)
80 {
81 int ac;
82
83 for (ac = 1; ac < argc; ++ac) {
84 if (!strcmp(argv[ac], "-c")) {
85 if (ac >= argc-1) {
86 LOG(ERROR) << "malformed command line: -c option requires argument)";
87 continue;
88 }
89 ConfigReader::setConfigFilePath(argv[ac+1]);
90 ++ac;
91 } else if (!strcmp(argv[ac], "-x")) {
92 if (ac >= argc-1) {
93 LOG(ERROR) << "malformed command line: -x option requires argument)";
94 continue;
95 }
96 perf_file_to_convert = argv[ac+1];
97 ++ac;
98 } else {
99 LOG(ERROR) << "malformed command line: unknown option or arg " << argv[ac] << ")";
100 continue;
101 }
102 }
103 }
104
105 //
106 // Post-processes after profile is collected and converted to protobuf.
107 // * GMS core stores processed file sequence numbers in
108 // /data/data/com.google.android.gms/files/perfprofd_processed.txt
109 // * Update /data/misc/perfprofd/perfprofd_produced.txt to remove the sequence
110 // numbers that have been processed and append the current seq number
111 // Returns true if the current_seq should increment.
112 //
post_process(const Config & config,int current_seq)113 static bool post_process(const Config& config, int current_seq)
114 {
115 const std::string& dest_dir = config.destination_directory;
116 std::string processed_file_path =
117 config.config_directory + "/" + PROCESSED_FILENAME;
118 std::string produced_file_path = dest_dir + "/" + PRODUCED_FILENAME;
119
120
121 std::set<int> processed;
122 FILE *fp = fopen(processed_file_path.c_str(), "r");
123 if (fp != NULL) {
124 int seq;
125 while(fscanf(fp, "%d\n", &seq) > 0) {
126 if (remove(android::base::StringPrintf(
127 "%s/perf.data.encoded.%d", dest_dir.c_str(),seq).c_str()) == 0) {
128 processed.insert(seq);
129 }
130 }
131 fclose(fp);
132 }
133
134 std::set<int> produced;
135 fp = fopen(produced_file_path.c_str(), "r");
136 if (fp != NULL) {
137 int seq;
138 while(fscanf(fp, "%d\n", &seq) > 0) {
139 if (processed.find(seq) == processed.end()) {
140 produced.insert(seq);
141 }
142 }
143 fclose(fp);
144 }
145
146 uint32_t maxLive = config.max_unprocessed_profiles;
147 if (produced.size() >= maxLive) {
148 return false;
149 }
150
151 produced.insert(current_seq);
152 fp = fopen(produced_file_path.c_str(), "w");
153 if (fp == NULL) {
154 PLOG(WARNING) << "Cannot write " << produced_file_path;
155 return false;
156 }
157 for (std::set<int>::const_iterator iter = produced.begin();
158 iter != produced.end(); ++iter) {
159 fprintf(fp, "%d\n", *iter);
160 }
161 fclose(fp);
162 chmod(produced_file_path.c_str(),
163 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
164 return true;
165 }
166
167 //
168 // Initialization
169 //
170
init(ConfigReader & config)171 static void init(ConfigReader &config)
172 {
173 if (!config.readFile()) {
174 LOG(ERROR) << "unable to open configuration file " << config.getConfigFilePath();
175 }
176
177 CommonInit(static_cast<uint32_t>(config.getUnsignedValue("use_fixed_seed")),
178 config.getStringValue("destination_directory").c_str());
179
180 signal(SIGHUP, sig_hup);
181 }
182
183 //
184 // Main routine:
185 // 1. parse cmd line args
186 // 2. read config file
187 // 3. loop: {
188 // sleep for a while
189 // perform a profile collection
190 // }
191 //
perfprofd_main(int argc,char ** argv,Config * config)192 int perfprofd_main(int argc, char** argv, Config* config)
193 {
194 LOG(INFO) << "starting Android Wide Profiling daemon";
195
196 parse_args(argc, argv);
197 {
198 ConfigReader config_reader;
199 init(config_reader);
200 config_reader.FillConfig(config);
201 }
202 GlobalInit(config->perf_path);
203
204 if (!perf_file_to_convert.empty()) {
205 std::string encoded_path = perf_file_to_convert + ".encoded";
206 encode_to_proto(perf_file_to_convert, encoded_path.c_str(), *config, 0, nullptr);
207 return 0;
208 }
209
210 // Early exit if we're not supposed to run on this build flavor
211 if (!IsDebugBuild() && config->only_debug_build) {
212 LOG(INFO) << "early exit due to inappropriate build type";
213 return 0;
214 }
215
216 auto config_fn = [config]() {
217 return config;
218 };
219 auto reread_config = [config]() {
220 // Reread config file -- the uploader may have rewritten it.
221 ConfigReader config_reader;
222 if (config_reader.readFile()) {
223 config_reader.FillConfig(config);
224 }
225 };
226 int seq = 0;
227 auto handler = [&seq](android::perfprofd::PerfprofdRecord* proto, Config* handler_config) {
228 if (proto == nullptr) {
229 return false;
230 }
231 if (handler_config->send_to_dropbox) {
232 std::string error_msg;
233 if (!android::perfprofd::dropbox::SendToDropbox(proto,
234 handler_config->destination_directory,
235 &error_msg)) {
236 LOG(ERROR) << "Failed dropbox submission: " << error_msg;
237 return false;
238 }
239 } else {
240 std::string data_file_path(handler_config->destination_directory);
241 data_file_path += "/";
242 data_file_path += PERF_OUTPUT;
243 std::string path = android::base::StringPrintf("%s.encoded.%d", data_file_path.c_str(), seq);
244 if (!android::perfprofd::SerializeProtobuf(proto, path.c_str(), handler_config->compress)) {
245 return false;
246 }
247 if (!post_process(*handler_config, seq)) {
248 return false;
249 }
250 }
251 seq++;
252 return true;
253 };
254 ProfilingLoop(config_fn, reread_config, handler);
255
256 LOG(INFO) << "finishing Android Wide Profiling daemon";
257 return 0;
258 }
259