OSDN Git Service

am 46b4f7d4: Merge "Perf profile collection daemon."
[android-x86/system-extras.git] / perfprofd / perfprofdcore.cc
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 <assert.h>
19 #include <dirent.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <time.h>
29 #include <unistd.h>
30 #include <string>
31 #include <map>
32 #include <cctype>
33
34 #include <cutils/properties.h>
35
36 #include "perfprofdcore.h"
37 #include "perfprofdutils.h"
38 #include "perf_data_converter.h"
39
40 //
41 // Perf profiling daemon -- collects system-wide profiles using
42 //
43 //       /system/bin/perf record -a
44 //
45 // and encodes them so that they can be uploaded by a separate service.
46 //
47
48 //......................................................................
49
50 //
51 // Output file from 'perf record'. The linux 'perf' tool by default
52 // creates a file with this name.
53 //
54 #define PERF_OUTPUT "perf.data"
55
56 //
57 // This enum holds the results of the "should we profile" configuration check.
58 //
59 typedef enum {
60
61   // All systems go for profile collection.
62   DO_COLLECT_PROFILE,
63
64   // The destination directory selected in the conf file doesn't exist. Most
65   // likely this is due to a missing or out-of-date version of the uploading
66   // service in GMS core.
67   DONT_PROFILE_MISSING_DESTINATION_DIR,
68
69   // Destination directory does not contain the semaphore file that
70   // the perf profile uploading service creates when it determines
71   // that the user has opted "in" for usage data collection. No
72   // semaphore -> no user approval -> no profiling.
73   DONT_PROFILE_MISSING_SEMAPHORE,
74
75   // No perf executable present
76   DONT_PROFILE_MISSING_PERF_EXECUTABLE,
77
78   // We're running in the emulator, perf won't be able to do much
79   DONT_PROFILE_RUNNING_IN_EMULATOR
80
81 } CKPROFILE_RESULT;
82
83 //
84 // Are we running in the emulator? If so, stub out profile collection
85 // Starts as uninitialized (-1), then set to 1 or 0 at init time.
86 //
87 static int running_in_emulator = -1;
88
89 //
90 // Is this a debug build ('userdebug' or 'eng')?
91 // Starts as uninitialized (-1), then set to 1 or 0 at init time.
92 //
93 static int is_debug_build = -1;
94
95 //
96 // Random number generator seed (set at startup time).
97 //
98 static unsigned short random_seed[3];
99
100 //
101 // Config file path. May be overridden with -c command line option
102 //
103 static const char *config_file_path = "/system/etc/perfprofd.conf";
104
105 //
106 // Set by SIGHUP signal handler
107 //
108 volatile unsigned please_reread_config_file = 0;
109
110 //
111 // This table describes the config file syntax in terms of key/value pairs.
112 // Values come in two flavors: strings, or unsigned integers. In the latter
113 // case the reader sets allowable minimum/maximum for the setting.
114 //
115 class ConfigReader {
116
117  public:
118   ConfigReader();
119   ~ConfigReader();
120
121   // Ask for the current setting of a config item
122   unsigned getUnsignedValue(const char *key);
123   std::string getStringValue(const char *key);
124
125   // read the specified config file, applying any settings it contains
126   void readFile(const char *configFilePath);
127
128  private:
129   void addUnsignedEntry(const char *key,
130                         unsigned default_value,
131                         unsigned min_value,
132                         unsigned max_value);
133   void addStringEntry(const char *key, const char *default_value);
134   void addDefaultEntries();
135   void parseLine(const char *key, const char *value, unsigned linecount);
136
137   typedef struct { unsigned minv, maxv; } values;
138   std::map<std::string, values> u_info;
139   std::map<std::string, unsigned> u_entries;
140   std::map<std::string, std::string> s_entries;
141   bool trace_config_read;
142 };
143
144 ConfigReader::ConfigReader()
145     : trace_config_read(false)
146 {
147   addDefaultEntries();
148 }
149
150 ConfigReader::~ConfigReader()
151 {
152 }
153
154 //
155 // Populate the reader with the set of allowable entries
156 //
157 void ConfigReader::addDefaultEntries()
158 {
159   // Average number of seconds between perf profile collections (if
160   // set to 100, then over time we want to see a perf profile
161   // collected every 100 seconds). The actual time within the interval
162   // for the collection is chosen randomly.
163   addUnsignedEntry("collection_interval", 901, 100, UINT32_MAX);
164
165   // Use the specified fixed seed for random number generation (unit
166   // testing)
167   addUnsignedEntry("use_fixed_seed", 0, 0, UINT32_MAX);
168
169   // For testing purposes, number of times to iterate through main
170   // loop.  Value of zero indicates that we should loop forever.
171   addUnsignedEntry("main_loop_iterations", 0, 0, UINT32_MAX);
172
173   // Destination directory (where to write profiles). This location
174   // chosen since it is accessible to the uploader service.
175   addStringEntry("destination_directory",
176                  "/data/data/com.google.android.gms/files");
177
178   // Path to 'perf' executable.
179   addStringEntry("perf_path", "/system/bin/perf");
180
181   // Desired sampling period (passed to perf -c option). Small
182   // sampling periods can perturb the collected profiles, so enforce
183   // min/max.
184   addUnsignedEntry("sampling_period", 500000, 5000, UINT32_MAX);
185
186   // Length of time to collect samples (number of seconds for 'perf
187   // record -a' run).
188   addUnsignedEntry("sample_duration", 3, 2, 600);
189
190   // If this parameter is non-zero it will cause perfprofd to
191   // exit immediately if the build type is not userdebug or eng.
192   // Currently defaults to 1 (true).
193   addUnsignedEntry("only_debug_build", 1, 0, 1);
194
195   // If set to 1, pass the -g option when invoking perf (requests
196   // stack traces as opposed to flat profile).
197   addUnsignedEntry("stack_profile", 0, 0, 1);
198
199   // For unit testing only: if set to 1, emit info messages on config
200   // file parsing.
201   addUnsignedEntry("trace_config_read", 0, 0, 1);
202 }
203
204 void ConfigReader::addUnsignedEntry(const char *key,
205                                     unsigned default_value,
206                                     unsigned min_value,
207                                     unsigned max_value)
208 {
209   std::string ks(key);
210   if (u_entries.find(ks) != u_entries.end() ||
211       s_entries.find(ks) != s_entries.end()) {
212     W_ALOGE("internal error -- duplicate entry for key %s", key);
213     exit(9);
214   }
215   values vals;
216   vals.minv = min_value;
217   vals.maxv = max_value;
218   u_info[ks] = vals;
219   u_entries[ks] = default_value;
220 }
221
222 void ConfigReader::addStringEntry(const char *key, const char *default_value)
223 {
224   std::string ks(key);
225   if (u_entries.find(ks) != u_entries.end() ||
226       s_entries.find(ks) != s_entries.end()) {
227     W_ALOGE("internal error -- duplicate entry for key %s", key);
228     exit(9);
229   }
230   if (! default_value) {
231     W_ALOGE("internal error -- bad default value for key %s", key);
232     exit(9);
233   }
234   s_entries[ks] = std::string(default_value);
235 }
236
237 unsigned ConfigReader::getUnsignedValue(const char *key)
238 {
239   std::string ks(key);
240   auto it = u_entries.find(ks);
241   assert(it != u_entries.end());
242   return it->second;
243 }
244
245 std::string ConfigReader::getStringValue(const char *key)
246 {
247   std::string ks(key);
248   auto it = s_entries.find(ks);
249   assert(it != s_entries.end());
250   return it->second;
251 }
252
253 //
254 // Parse a key=value pair read from the config file. This will issue
255 // warnings or errors to the system logs if the line can't be
256 // interpreted properly.
257 //
258 void ConfigReader::parseLine(const char *key,
259                              const char *value,
260                              unsigned linecount)
261 {
262   assert(key);
263   assert(value);
264
265   auto uit = u_entries.find(key);
266   if (uit != u_entries.end()) {
267     unsigned uvalue = 0;
268     if (! isdigit(value[0]) || sscanf(value, "%u", &uvalue) != 1) {
269       W_ALOGW("line %d: malformed unsigned value (ignored)", linecount);
270     } else {
271       values vals;
272       auto iit = u_info.find(key);
273       assert(iit != u_info.end());
274       vals = iit->second;
275       if (uvalue < vals.minv || uvalue > vals.maxv) {
276         W_ALOGW("line %d: specified value %u for '%s' "
277                 "outside permitted range [%u %u] (ignored)",
278                 linecount, uvalue, key, vals.minv, vals.maxv);
279       } else {
280         if (trace_config_read) {
281           W_ALOGI("option %s set to %u", key, uvalue);
282         }
283         uit->second = uvalue;
284       }
285     }
286     trace_config_read = (getUnsignedValue("trace_config_read") != 0);
287     return;
288   }
289
290   auto sit = s_entries.find(key);
291   if (sit != s_entries.end()) {
292     if (trace_config_read) {
293       W_ALOGI("option %s set to %s", key, value);
294     }
295     sit->second = std::string(value);
296     return;
297   }
298
299   W_ALOGW("line %d: unknown option '%s' ignored", linecount, key);
300 }
301
302 static bool isblank(const std::string &line)
303 {
304   for (std::string::const_iterator it = line.begin(); it != line.end(); ++it)
305   {
306     if (! isspace(*it)) {
307       return false;
308     }
309   }
310   return true;
311 }
312
313 void ConfigReader::readFile(const char *configFilePath)
314 {
315   FILE *fp = fopen(configFilePath, "r");
316   if (!fp) {
317     W_ALOGE("unable to open configuration file %s", config_file_path);
318     return;
319   }
320
321   char *linebuf = NULL;
322   size_t line_length = 0;
323   for (unsigned linecount = 1;
324        getline(&linebuf, &line_length, fp) != -1;
325        ++linecount) {
326     char *eq = 0;
327     char *key, *value;
328
329     // comment line?
330     if (linebuf[0] == '#') {
331       continue;
332     }
333
334     // blank line?
335     if (isblank(linebuf)) {
336       continue;
337     }
338
339     // look for X=Y assignment
340     eq = strchr(linebuf, '=');
341     if (!eq) {
342       W_ALOGW("line %d: line malformed (no '=' found)", linecount);
343       continue;
344     }
345
346     *eq = '\0';
347     key = linebuf;
348     value = eq+1;
349     char *ln = strrchr(value, '\n');
350     if (ln) { *ln = '\0'; }
351
352     parseLine(key, value, linecount);
353   }
354   free(linebuf);
355   fclose(fp);
356 }
357
358 //
359 // Parse command line args. Currently you can supply "-c P" to set
360 // the path of the config file to P.
361 //
362 static void parse_args(int argc, char** argv)
363 {
364   int ac;
365
366   for (ac = 1; ac < argc; ++ac) {
367     if (!strcmp(argv[ac], "-c")) {
368       if (ac >= argc-1) {
369         W_ALOGE("malformed command line: -c option requires argument)");
370         continue;
371       }
372       config_file_path = strdup(argv[ac+1]);
373       W_ALOGI("config file path set to %s", config_file_path);
374       ++ac;
375     } else {
376       W_ALOGE("malformed command line: unknown option or arg %s)", argv[ac]);
377       continue;
378     }
379   }
380 }
381
382 //
383 // Convert a CKPROFILE_RESULT to a string
384 //
385 const char *ckprofile_result_to_string(CKPROFILE_RESULT result)
386 {
387   switch(result) {
388     case DO_COLLECT_PROFILE:
389       return "DO_COLLECT_PROFILE";
390     case DONT_PROFILE_MISSING_DESTINATION_DIR:
391       return "missing destination directory";
392     case DONT_PROFILE_MISSING_SEMAPHORE:
393       return "missing semaphore file";
394     case DONT_PROFILE_MISSING_PERF_EXECUTABLE:
395       return "missing 'perf' executable";
396     case DONT_PROFILE_RUNNING_IN_EMULATOR:
397       return "running in emulator";
398     default: return "unknown";
399   }
400   return "notreached";
401 }
402
403 //
404 // Convert a PROFILE_RESULT to a string
405 //
406 const char *profile_result_to_string(PROFILE_RESULT result)
407 {
408   switch(result) {
409     case OK_PROFILE_COLLECTION:
410       return "profile collection succeeded";
411     case ERR_FORK_FAILED:
412       return "fork() system call failed";
413     case ERR_PERF_RECORD_FAILED:
414       return "perf record returned bad exit status";
415     case ERR_PERF_ENCODE_FAILED:
416       return "failure encoding perf.data to protobuf";
417     case ERR_OPEN_ENCODED_FILE_FAILED:
418       return "failed to open encoded perf file";
419     case ERR_WRITE_ENCODED_FILE_FAILED:
420       return "write to encoded perf file failed";
421     default: return "unknown";
422   }
423   return "notreached";
424 }
425
426 //
427 // The daemon does a read of the main config file on startup, however
428 // if the destination directory also contains a configf file, then we
429 // read parameters from that as well. This provides a mechanism for
430 // changing/controlling the behavior of the daemon via the settings
431 // established in the uploader service (which may be easier to update
432 // than the daemon).
433 //
434 static void read_aux_config(ConfigReader &config)
435 {
436   std::string destConfig(config.getStringValue("destination_directory"));
437   destConfig += "/perfprofd.conf";
438   FILE *fp = fopen(destConfig.c_str(), "r");
439   if (fp) {
440     fclose(fp);
441     bool trace_config_read =
442         (config.getUnsignedValue("trace_config_read") != 0);
443     if (trace_config_read) {
444       W_ALOGI("reading auxiliary config file %s", destConfig.c_str());
445     }
446     config.readFile(destConfig.c_str());
447   }
448 }
449
450 //
451 // Check to see whether we should perform a profile collection
452 //
453 static CKPROFILE_RESULT check_profiling_enabled(ConfigReader &config)
454 {
455   //
456   // Profile collection in the emulator doesn't make sense
457   //
458   assert(running_in_emulator != -1);
459   if (running_in_emulator) {
460     return DONT_PROFILE_RUNNING_IN_EMULATOR;
461   }
462
463   //
464   // Check for the existence of the destination directory
465   //
466   std::string destdir = config.getStringValue("destination_directory");
467   DIR* dir = opendir(destdir.c_str());
468   if (!dir) {
469     W_ALOGW("unable to open destination directory %s: (%s)",
470             destdir.c_str(), strerror(errno));
471     return DONT_PROFILE_MISSING_DESTINATION_DIR;
472   }
473
474   // Reread aux config file -- it may have changed
475   read_aux_config(config);
476
477   // Check for existence of perf executable
478   std::string pp = config.getStringValue("perf_path");
479   if (access(pp.c_str(), R_OK|X_OK) == -1) {
480     W_ALOGW("unable to access/execute %s", pp.c_str());
481     closedir(dir);
482     return DONT_PROFILE_MISSING_PERF_EXECUTABLE;
483   }
484
485   // Check for existence of semaphore file
486   unsigned found = 0;
487   struct dirent* e;
488   while ((e = readdir(dir)) != 0) {
489     if (!strcmp(e->d_name, SEMAPHORE_FILENAME)) {
490       found = 1;
491       break;
492     }
493   }
494   closedir(dir);
495   if (!found) {
496     return DONT_PROFILE_MISSING_SEMAPHORE;
497   }
498
499   //
500   // We are good to go
501   //
502   return DO_COLLECT_PROFILE;
503 }
504
505 inline char* string_as_array(std::string* str) {
506   return str->empty() ? NULL : &*str->begin();
507 }
508
509 PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
510                                const std::string &encoded_file_path)
511 {
512   //
513   // Open and read perf.data file
514   //
515   const wireless_android_play_playlog::AndroidPerfProfile &encodedProfile =
516       wireless_android_logging_awp::RawPerfDataToAndroidPerfProfile(data_file_path);
517
518   fprintf(stderr, "data file path is %s\n", data_file_path.c_str());
519   fprintf(stderr, "encoded file path is %s\n", encoded_file_path.c_str());
520
521   //
522   // Issue error if no samples
523   //
524   if (encodedProfile.programs().size() == 0) {
525     return ERR_PERF_ENCODE_FAILED;
526   }
527
528
529   //
530   // Serialize protobuf to array
531   //
532   int size = encodedProfile.ByteSize();
533   std::string data;
534   data.resize(size);
535   ::google::protobuf::uint8* dtarget =
536         reinterpret_cast<::google::protobuf::uint8*>(string_as_array(&data));
537   encodedProfile.SerializeWithCachedSizesToArray(dtarget);
538
539   //
540   // Open file and write encoded data to it
541   //
542   FILE *fp = fopen(encoded_file_path.c_str(), "w");
543   if (!fp) {
544     return ERR_OPEN_ENCODED_FILE_FAILED;
545   }
546   size_t fsiz = size;
547   if (fwrite(dtarget, fsiz, 1, fp) != 1) {
548     fclose(fp);
549     return ERR_WRITE_ENCODED_FILE_FAILED;
550   }
551   fclose(fp);
552
553   return OK_PROFILE_COLLECTION;
554 }
555
556 //
557 // Collect a perf profile. Steps for this operation are:
558 // - kick off 'perf record'
559 // - read perf.data, convert to protocol buf
560 //
561 static PROFILE_RESULT collect_profile(ConfigReader &config)
562 {
563   //
564   // Form perf.data file name, perf error output file name
565   //
566   std::string destdir = config.getStringValue("destination_directory");
567   std::string data_file_path(destdir);
568   data_file_path += "/";
569   data_file_path += PERF_OUTPUT;
570   std::string perf_stderr_path(destdir);
571   perf_stderr_path += "/perferr.txt";
572
573   //
574   // Remove any existing perf.data file -- if we don't do this, perf
575   // will rename the old file and we'll have extra cruft lying around.
576   //
577   struct stat statb;
578   if (stat(data_file_path.c_str(), &statb) == 0) { // if file exists...
579     if (unlink(data_file_path.c_str())) {          // then try to remove
580       W_ALOGW("unable to unlink previous perf.data file");
581     }
582   }
583
584   //
585   // NB: it would probably be better to use explicit fork/exec with an
586   // alarm timeout in case of funny business. For now, call system().
587   //
588   std::string perf_path = config.getStringValue("perf_path");
589   unsigned duration = config.getUnsignedValue("sample_duration");
590   unsigned period = config.getUnsignedValue("sampling_period");
591   const char *gopt = (config.getUnsignedValue("stack_profile") != 0 ? "-g" : "");
592   char cmd[8192];
593   snprintf(cmd, 8192, "%s record %s -c %u -o %s -a -- sleep %d 1> %s 2>&1 ",
594            perf_path.c_str(), gopt, period, data_file_path.c_str(), duration,
595            perf_stderr_path.c_str());
596   int rc = system(cmd);
597   if (rc == -1) {
598     return ERR_FORK_FAILED;
599   }
600   if (rc != 0) {
601     return ERR_PERF_RECORD_FAILED;
602   }
603
604   //
605   // Read the resulting perf.data file, encode into protocol buffer, then write
606   // the result to a *.pdb file
607   //
608   std::string encoded_file_path(data_file_path);
609   encoded_file_path += ".encoded";
610   return encode_to_proto(data_file_path, encoded_file_path);
611 }
612
613 //
614 // SIGHUP handler. Sets a flag to indicate that we should reread the
615 // config file
616 //
617 static void sig_hup(int /* signum */)
618 {
619   please_reread_config_file = 1;
620 }
621
622 //
623 // Assuming that we want to collect a profile every N seconds,
624 // randomly partition N into two sub-intervals.
625 //
626 static void determine_before_after(unsigned &sleep_before_collect,
627                                    unsigned &sleep_after_collect,
628                                    unsigned collection_interval)
629 {
630   double frac = erand48(random_seed);
631   sleep_before_collect = (unsigned) (((double)collection_interval) * frac);
632   assert(sleep_before_collect <= collection_interval);
633   sleep_after_collect = collection_interval - sleep_before_collect;
634 }
635
636 //
637 // Set random number generator seed
638 //
639 static void set_seed(ConfigReader &config)
640 {
641   unsigned seed = 0;
642   unsigned use_fixed_seed = config.getUnsignedValue("use_fixed_seed");
643   if (use_fixed_seed) {
644     //
645     // Use fixed user-specified seed
646     //
647     seed = use_fixed_seed;
648   } else {
649     //
650     // Randomized seed
651     //
652     seed = arc4random();
653   }
654   W_ALOGI("random seed set to %u", seed);
655   // Distribute the 32-bit seed into the three 16-bit array
656   // elements. The specific values being written do not especially
657   // matter as long as we are setting them to something based on the seed.
658   random_seed[0] = seed & 0xffff;
659   random_seed[1] = (seed >> 16);
660   random_seed[2] = (random_seed[0] ^ random_seed[1]);
661 }
662
663 //
664 // Initialization
665 //
666 static void init(ConfigReader &config)
667 {
668   config.readFile(config_file_path);
669   set_seed(config);
670
671   char propBuf[PROPERTY_VALUE_MAX];
672   propBuf[0] = '\0';
673   property_get("ro.kernel.qemu", propBuf, "");
674   running_in_emulator = (propBuf[0] == '1');
675   property_get("ro.debuggable", propBuf, "");
676   is_debug_build = (propBuf[0] == '1');
677
678   signal(SIGHUP, sig_hup);
679 }
680
681 //
682 // Main routine:
683 // 1. parse cmd line args
684 // 2. read config file
685 // 3. loop: {
686 //       sleep for a while
687 //       perform a profile collection
688 //    }
689 //
690 int perfprofd_main(int argc, char** argv)
691 {
692   ConfigReader config;
693
694   W_ALOGI("starting Android Wide Profiling daemon");
695
696   parse_args(argc, argv);
697   init(config);
698   read_aux_config(config);
699
700   // Early exit if we're not supposed to run on this build flavor
701   if (is_debug_build != 1 &&
702       config.getUnsignedValue("only_debug_build") == 1) {
703     W_ALOGI("early exit due to inappropriate build type");
704     return 0;
705   }
706
707   unsigned iterations = 0;
708   while(!config.getUnsignedValue("main_loop_iterations") ||
709         iterations < config.getUnsignedValue("main_loop_iterations")) {
710
711     // Figure out where in the collection interval we're going to actually
712     // run perf
713     unsigned sleep_before_collect = 0;
714     unsigned sleep_after_collect = 0;
715     determine_before_after(sleep_before_collect, sleep_after_collect,
716                            config.getUnsignedValue("collection_interval"));
717     perfprofd_sleep(sleep_before_collect);
718
719     // Reread config file if someone sent a SIGHUP
720     if (please_reread_config_file) {
721       config.readFile(config_file_path);
722       please_reread_config_file = 0;
723     }
724
725     // Check for profiling enabled...
726     CKPROFILE_RESULT ckresult = check_profiling_enabled(config);
727     if (ckresult != DO_COLLECT_PROFILE) {
728       W_ALOGI("profile collection skipped (%s)",
729               ckprofile_result_to_string(ckresult));
730     } else {
731       // Kick off the profiling run...
732       W_ALOGI("initiating profile collection");
733       PROFILE_RESULT result = collect_profile(config);
734       if (result != OK_PROFILE_COLLECTION) {
735         W_ALOGI("profile collection failed (%s)",
736                 profile_result_to_string(result));
737       } else {
738         W_ALOGI("profile collection complete");
739       }
740     }
741     perfprofd_sleep(sleep_after_collect);
742     iterations += 1;
743   }
744
745   W_ALOGI("finishing Android Wide Profiling daemon");
746   return 0;
747 }