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 #define LOG_TAG "pwrstats_util"
17 
18 #include <android-base/logging.h>
19 #include <fcntl.h>
20 #include <getopt.h>
21 #include <sys/resource.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 
25 #include <algorithm>
26 #include <chrono>
27 #include <csignal>
28 #include <fstream>
29 #include <iostream>
30 
31 #include <pwrstatsutil.pb.h>
32 #include "PowerStatsCollector.h"
33 
34 namespace {
35 volatile std::sig_atomic_t gSignalStatus;
36 }
37 
signalHandler(int signal)38 static void signalHandler(int signal) {
39     gSignalStatus = signal;
40 }
41 
42 class Options {
43   public:
44     bool humanReadable;
45     bool daemonMode;
46     std::string filePath;
47 };
48 
parseArgs(int argc,char ** argv)49 static Options parseArgs(int argc, char** argv) {
50     Options opt = {
51             .humanReadable = false,
52             .daemonMode = false,
53     };
54 
55     static struct option long_options[] = {/* These options set a flag. */
56                                            {"human-readable", no_argument, 0, 0},
57                                            {"daemon", required_argument, 0, 'd'},
58                                            {0, 0, 0, 0}};
59 
60     // getopt_long stores the option index here
61     int option_index = 0;
62 
63     int c;
64     while ((c = getopt_long(argc, argv, "d:", long_options, &option_index)) != -1) {
65         switch (c) {
66             case 0:
67                 if ("human-readable" == std::string(long_options[option_index].name)) {
68                     opt.humanReadable = true;
69                 }
70                 break;
71             case 'd':
72                 opt.daemonMode = true;
73                 opt.filePath = std::string(optarg);
74                 break;
75             default: /* '?' */
76                 std::cerr << "pwrstats_util: Prints out device power stats." << std::endl
77                           << "--human-readable: human-readable format" << std::endl
78                           << "--daemon <path/to/file>, -d <path/to/file>: daemon mode. Spawns a "
79                              "daemon process and prints out its <pid>. kill -INT <pid> will "
80                              "trigger a write to specified file."
81                           << std::endl;
82                 exit(EXIT_FAILURE);
83         }
84     }
85     return opt;
86 }
87 
snapshot(const Options & opt,const PowerStatsCollector & collector)88 static void snapshot(const Options& opt, const PowerStatsCollector& collector) {
89     std::vector<PowerStatistic> stats;
90     int ret = collector.get(&stats);
91     if (ret) {
92         exit(EXIT_FAILURE);
93     }
94 
95     if (opt.humanReadable) {
96         collector.dump(stats, &std::cout);
97     } else {
98         for (const auto& stat : stats) {
99             stat.SerializeToOstream(&std::cout);
100         }
101     }
102 
103     exit(EXIT_SUCCESS);
104 }
105 
daemon(const Options & opt,const PowerStatsCollector & collector)106 static void daemon(const Options& opt, const PowerStatsCollector& collector) {
107     // Following a subset of steps outlined in http://man7.org/linux/man-pages/man7/daemon.7.html
108 
109     // Call fork to create child process
110     pid_t pid;
111     if ((pid = fork()) < 0) {
112         LOG(ERROR) << "can't fork" << std::endl;
113         exit(EXIT_FAILURE);
114     } else if (pid != 0) {
115         std::cout << "pid = " << pid << std::endl;
116         exit(EXIT_SUCCESS);
117     }
118     // Daemon process:
119 
120     // Get maximum number of file descriptors
121     struct rlimit rl;
122     if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
123         LOG(ERROR) << "can't get file limit" << std::endl;
124         exit(EXIT_FAILURE);
125     }
126 
127     // Close all open file descriptors
128     if (rl.rlim_max == RLIM_INFINITY) {
129         rl.rlim_max = 1024;
130     }
131     for (int i = 0; i < rl.rlim_max; i++) {
132         close(i);
133     }
134 
135     // Detach from any terminal and create an independent session
136     if (setsid() < 0) {
137         LOG(ERROR) << "SID creation failed";
138         exit(EXIT_FAILURE);
139     }
140 
141     // connect /dev/null to standard input, output, and error.
142     int devnull = open("/dev/null", O_RDWR | O_CLOEXEC);
143     dup2(devnull, STDIN_FILENO);
144     dup2(devnull, STDOUT_FILENO);
145     dup2(devnull, STDERR_FILENO);
146 
147     // Reset the umask to 0
148     umask(0);
149 
150     // Change the current directory to the root
151     // directory (/), in order to avoid that the daemon involuntarily
152     // blocks mount points from being unmounted
153     if (chdir("/") < 0) {
154         LOG(ERROR) << "can't change directory to /" << std::endl;
155         exit(EXIT_FAILURE);
156     }
157 
158     // Install a signal handler
159     std::signal(SIGINT, signalHandler);
160 
161     // get the start_data
162     auto start_time = std::chrono::system_clock::now();
163 
164     std::vector<PowerStatistic> start_stats;
165     int ret = collector.get(&start_stats);
166     if (ret) {
167         LOG(ERROR) << "failed to get start stats";
168         exit(EXIT_FAILURE);
169     }
170 
171     // Wait for INT signal
172     while (gSignalStatus != SIGINT) {
173         pause();
174     }
175 
176     // get the end data
177     std::vector<PowerStatistic> interval_stats;
178     ret = collector.get(start_stats, &interval_stats);
179     if (ret) {
180         LOG(ERROR) << "failed to get interval stats";
181         exit(EXIT_FAILURE);
182     }
183     auto end_time = std::chrono::system_clock::now();
184 
185     std::chrono::duration<double> elapsed_seconds = end_time - start_time;
186 
187     // Write data to file
188     std::ofstream myfile(opt.filePath, std::ios::out | std::ios::binary);
189     if (!myfile.is_open()) {
190         LOG(ERROR) << "failed to open file";
191         exit(EXIT_FAILURE);
192     }
193     myfile << "elapsed time: " << elapsed_seconds.count() << "s" << std::endl;
194     if (opt.humanReadable) {
195         collector.dump(interval_stats, &myfile);
196     } else {
197         for (const auto& stat : interval_stats) {
198             stat.SerializeToOstream(&myfile);
199         }
200     }
201 
202     myfile.close();
203 
204     exit(EXIT_SUCCESS);
205 }
206 
runWithOptions(const Options & opt,const PowerStatsCollector & collector)207 static void runWithOptions(const Options& opt, const PowerStatsCollector& collector) {
208     if (opt.daemonMode) {
209         daemon(opt, collector);
210     } else {
211         snapshot(opt, collector);
212     }
213 }
214 
run(int argc,char ** argv,const PowerStatsCollector & collector)215 int run(int argc, char** argv, const PowerStatsCollector& collector) {
216     Options opt = parseArgs(argc, argv);
217 
218     runWithOptions(opt, collector);
219 
220     return 0;
221 }
222