| /* |
| * |
| * Copyright 2015, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "perfprofd_cmdline.h" |
| |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <set> |
| #include <string> |
| |
| #include <android-base/logging.h> |
| #include <android-base/macros.h> |
| #include <android-base/stringprintf.h> |
| |
| #include "perfprofd_record.pb.h" |
| |
| #include "configreader.h" |
| #include "perfprofdcore.h" |
| #include "perfprofd_io.h" |
| |
| // |
| // Perf profiling daemon -- collects system-wide profiles using |
| // |
| // simpleperf record -a |
| // |
| // and encodes them so that they can be uploaded by a separate service. |
| // |
| |
| // |
| |
| // |
| // Output file from 'perf record'. |
| // |
| #define PERF_OUTPUT "perf.data" |
| |
| // |
| // Path to the perf file to convert and exit? Empty value is the default, daemon mode. |
| // |
| static std::string perf_file_to_convert = ""; |
| |
| // |
| // SIGHUP handler. Sending SIGHUP to the daemon can be used to break it |
| // out of a sleep() call so as to trigger a new collection (debugging) |
| // |
| static void sig_hup(int /* signum */) |
| { |
| LOG(WARNING) << "SIGHUP received"; |
| } |
| |
| // |
| // Parse command line args. Currently supported flags: |
| // * "-c PATH" sets the path of the config file to PATH. |
| // * "-x PATH" reads PATH as a perf data file and saves it as a file in |
| // perf_profile.proto format. ".encoded" suffix is appended to PATH to form |
| // the output file path. |
| // |
| static void parse_args(int argc, char** argv) |
| { |
| int ac; |
| |
| for (ac = 1; ac < argc; ++ac) { |
| if (!strcmp(argv[ac], "-c")) { |
| if (ac >= argc-1) { |
| LOG(ERROR) << "malformed command line: -c option requires argument)"; |
| continue; |
| } |
| ConfigReader::setConfigFilePath(argv[ac+1]); |
| ++ac; |
| } else if (!strcmp(argv[ac], "-x")) { |
| if (ac >= argc-1) { |
| LOG(ERROR) << "malformed command line: -x option requires argument)"; |
| continue; |
| } |
| perf_file_to_convert = argv[ac+1]; |
| ++ac; |
| } else { |
| LOG(ERROR) << "malformed command line: unknown option or arg " << argv[ac] << ")"; |
| continue; |
| } |
| } |
| } |
| |
| // |
| // Post-processes after profile is collected and converted to protobuf. |
| // * GMS core stores processed file sequence numbers in |
| // /data/data/com.google.android.gms/files/perfprofd_processed.txt |
| // * Update /data/misc/perfprofd/perfprofd_produced.txt to remove the sequence |
| // numbers that have been processed and append the current seq number |
| // Returns true if the current_seq should increment. |
| // |
| static bool post_process(const Config& config, int current_seq) |
| { |
| const std::string& dest_dir = config.destination_directory; |
| std::string processed_file_path = |
| config.config_directory + "/" + PROCESSED_FILENAME; |
| std::string produced_file_path = dest_dir + "/" + PRODUCED_FILENAME; |
| |
| |
| std::set<int> processed; |
| FILE *fp = fopen(processed_file_path.c_str(), "r"); |
| if (fp != NULL) { |
| int seq; |
| while(fscanf(fp, "%d\n", &seq) > 0) { |
| if (remove(android::base::StringPrintf( |
| "%s/perf.data.encoded.%d", dest_dir.c_str(),seq).c_str()) == 0) { |
| processed.insert(seq); |
| } |
| } |
| fclose(fp); |
| } |
| |
| std::set<int> produced; |
| fp = fopen(produced_file_path.c_str(), "r"); |
| if (fp != NULL) { |
| int seq; |
| while(fscanf(fp, "%d\n", &seq) > 0) { |
| if (processed.find(seq) == processed.end()) { |
| produced.insert(seq); |
| } |
| } |
| fclose(fp); |
| } |
| |
| uint32_t maxLive = config.max_unprocessed_profiles; |
| if (produced.size() >= maxLive) { |
| return false; |
| } |
| |
| produced.insert(current_seq); |
| fp = fopen(produced_file_path.c_str(), "w"); |
| if (fp == NULL) { |
| PLOG(WARNING) << "Cannot write " << produced_file_path; |
| return false; |
| } |
| for (std::set<int>::const_iterator iter = produced.begin(); |
| iter != produced.end(); ++iter) { |
| fprintf(fp, "%d\n", *iter); |
| } |
| fclose(fp); |
| chmod(produced_file_path.c_str(), |
| S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); |
| return true; |
| } |
| |
| // |
| // Initialization |
| // |
| |
| static void init(ConfigReader &config) |
| { |
| if (!config.readFile()) { |
| LOG(ERROR) << "unable to open configuration file " << config.getConfigFilePath(); |
| } |
| |
| CommonInit(static_cast<uint32_t>(config.getUnsignedValue("use_fixed_seed")), |
| config.getStringValue("destination_directory").c_str()); |
| |
| signal(SIGHUP, sig_hup); |
| } |
| |
| // |
| // Main routine: |
| // 1. parse cmd line args |
| // 2. read config file |
| // 3. loop: { |
| // sleep for a while |
| // perform a profile collection |
| // } |
| // |
| int perfprofd_main(int argc, char** argv, Config* config) |
| { |
| ConfigReader config_reader; |
| |
| LOG(INFO) << "starting Android Wide Profiling daemon"; |
| |
| parse_args(argc, argv); |
| init(config_reader); |
| config_reader.FillConfig(config); |
| |
| if (!perf_file_to_convert.empty()) { |
| std::string encoded_path = perf_file_to_convert + ".encoded"; |
| encode_to_proto(perf_file_to_convert, encoded_path.c_str(), *config, 0, nullptr); |
| return 0; |
| } |
| |
| // Early exit if we're not supposed to run on this build flavor |
| if (!IsDebugBuild() && config->only_debug_build) { |
| LOG(INFO) << "early exit due to inappropriate build type"; |
| return 0; |
| } |
| |
| auto config_fn = [config]() { |
| return config; |
| }; |
| auto reread_config = [&config_reader, config]() { |
| // Reread config file -- the uploader may have rewritten it as a result |
| // of a gservices change |
| config_reader.readFile(); |
| config_reader.FillConfig(config); |
| }; |
| int seq = 0; |
| auto handler = [&seq](android::perfprofd::PerfprofdRecord* proto, Config* handler_config) { |
| if (proto == nullptr) { |
| return false; |
| } |
| std::string data_file_path(handler_config->destination_directory); |
| data_file_path += "/"; |
| data_file_path += PERF_OUTPUT; |
| std::string path = android::base::StringPrintf("%s.encoded.%d", data_file_path.c_str(), seq); |
| if (!android::perfprofd::SerializeProtobuf(proto, path.c_str(), handler_config->compress)) { |
| return false; |
| } |
| |
| if (!post_process(*handler_config, seq)) { |
| return false; |
| } |
| seq++; |
| return true; |
| }; |
| ProfilingLoop(config_fn, reread_config, handler); |
| |
| LOG(INFO) << "finishing Android Wide Profiling daemon"; |
| return 0; |
| } |