OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * cygprofile_android.cc - CygProfiler runtime functions. |
| 3 * |
| 4 * Michal Ludvig <michal@logix.cz> |
| 5 * http://www.logix.cz/michal/devel |
| 6 * |
| 7 * cygprofile_android.cc |
| 8 * - Compile your program with -finstrument-functions and link |
| 9 * together with this code. |
| 10 * - Logging is enabled as soon as your program calls |
| 11 * cygprofile_enable() and disabled with cygprofile_disable(). |
| 12 * - Before logging was enabled you can change the name |
| 13 * of a logfile by calling cygprofile_setfilename(). |
| 14 * |
| 15 * This implementation has been modified in the following ways: |
| 16 * 1) Upon profile enabling, the virtual address is logged as the |
| 17 * first line to assist in symbolizing logged virutal addresses. |
| 18 * 2) Only function entry for the first time a particular function |
| 19 * is entered is logged. This is for profiling for code layout. |
| 20 * 3) The implementation is made thread-safe. |
| 21 * 4) The implementation logs the timestamp (seconds and microsecs). |
| 22 * 5) The implementation adds function cygprofile_start(filename) which |
| 23 * calls cyg_setfilename and enables profiling and cygprofile_end() |
| 24 * to guarantee log buffer is flushed and logfile is closed. |
| 25 */ |
| 26 |
| 27 #include <errno.h> |
| 28 #include <pthread.h> |
| 29 #include <stdint.h> |
| 30 #include <string.h> |
| 31 #include <stdlib.h> |
| 32 #include <sys/time.h> |
| 33 #include <time.h> |
| 34 #include <unistd.h> |
| 35 |
| 36 #include <fstream> |
| 37 #include <set> |
| 38 |
| 39 #include "cygprofile_android.h" |
| 40 |
| 41 namespace cygprofile { |
| 42 |
| 43 const int kMaxFileNameSizeSize = 100; |
| 44 |
| 45 // On Android, put the default location for the profile files on the |
| 46 // SD Card partition for easy retrieval and ensuring a fixed absolute |
| 47 // paths for all output. |
| 48 #ifdef __ANDROID__ |
| 49 static const char kDefaultFileName[] = "/sdcard/cyglog.%d"; |
| 50 #else |
| 51 const char* kDefaultFileName = "cyglog.%d"; |
| 52 #endif |
| 53 const int kMaxLineSize = 512; |
| 54 |
| 55 static FILE* gLogFile = NULL; |
| 56 static volatile bool gCygProfileEnabled = false; |
| 57 static char gCygProfileFileName[kMaxFileNameSizeSize + 1]; |
| 58 |
| 59 // Keeps track of all functions that have been logged. |
| 60 static std::set<void*>* gFunctionsCalled = NULL; |
| 61 |
| 62 |
| 63 // Return the current timestamp in seconds and micro-seconds |
| 64 // On Linux-based systems, use the raw monotonic clock to ensure that |
| 65 // the timestamps are not affected by NTP adjustments. Otherwise simply |
| 66 // use gettimeofday() |
| 67 #ifdef __linux__ |
| 68 |
| 69 // We use CLOCK_MONOTONIC_RAW on Linux, because CLOCK_MONOTONIC is broken |
| 70 // and is subject to NTP adjustments since Linux 2.6.28. This bug has been |
| 71 // fixed in 2.6.32 which not all devices run yet. |
| 72 // |
| 73 // See http://stackoverflow.com/questions/3657289/linux-clock-gettimeclock-monot
onic-strange-non-monotonic-behavior |
| 74 // |
| 75 |
| 76 // The Android Bionic headers don't necessarily define the constant. |
| 77 #ifndef CLOCK_MONOTONIC_RAW |
| 78 #define CLOCK_MONOTONIC_RAW 4 |
| 79 #endif |
| 80 |
| 81 static void |
| 82 get_timestamp(time_t *seconds, long *usecs) |
| 83 { |
| 84 struct timespec ts; |
| 85 clock_gettime(CLOCK_MONOTONIC_RAW, &ts); |
| 86 *seconds = ts.tv_sec; |
| 87 *usecs = ts.tv_nsec / 1000; |
| 88 } |
| 89 |
| 90 #else // !__linux__ |
| 91 |
| 92 static void |
| 93 get_timestamp(time_t *seconds, long *usecs) |
| 94 { |
| 95 struct timeval tv; |
| 96 gettimeofday(&tv, NULL); |
| 97 *seconds = tv.tv_sec; |
| 98 *usecs = tv.tv_usec; |
| 99 } |
| 100 |
| 101 #endif // !__linux__ |
| 102 |
| 103 #ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER |
| 104 # ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP |
| 105 # define PTHREAD_RECURSIVE_MUTEX_INITIALIZER PTHREAD_RECURSIVE_MUTEX_INITIALI
ZER_NP |
| 106 # else |
| 107 # error MISSING PTHREAD_RECURSIVE_MUTEX_INITIALIZER DEFINITION! |
| 108 # endif |
| 109 #endif |
| 110 |
| 111 // Ensure thread safety (see __cyg_profile_func_enter) |
| 112 static pthread_mutex_t gMutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; |
| 113 static unsigned int gDepth = 0; |
| 114 |
| 115 #ifdef __cplusplus |
| 116 extern "C" { |
| 117 #endif |
| 118 |
| 119 FILE* cygprofile_openlogfile(const char* filename) |
| 120 __attribute__((no_instrument_function)); |
| 121 void cygprofile_closelogfile(void) |
| 122 __attribute__((no_instrument_function)); |
| 123 |
| 124 // Note that these are linked internally by the compiler. |
| 125 // Don't call them directly! |
| 126 void __cyg_profile_func_enter(void* this_fn, void* call_site) |
| 127 __attribute__((no_instrument_function)); |
| 128 void __cyg_profile_func_exit(void* this_fn, void* call_site) |
| 129 __attribute__((no_instrument_function)); |
| 130 |
| 131 #ifdef __cplusplus |
| 132 }; |
| 133 #endif |
| 134 |
| 135 // Called internally by instrumentation inserted by the compiler upon entering |
| 136 // a function. Instrumentation is inserted by the compiler for all functions |
| 137 // whose source is compiled with the -finstrument-functions CFLAG. The code |
| 138 // logs the call along with the timestamp, process id and thread id. |
| 139 void __cyg_profile_func_enter(void* this_fn, void* call_site) { |
| 140 pthread_mutex_lock(&gMutex); |
| 141 // avoid recursive calls |
| 142 if (gDepth == 0) { |
| 143 gDepth += 1; |
| 144 if (!gCygProfileEnabled) { |
| 145 cygprofile_enable(); |
| 146 } |
| 147 if (gCygProfileEnabled && gLogFile) { |
| 148 // gFunctionsCalled is initialized in cygprofile_enable() |
| 149 if (gFunctionsCalled && gFunctionsCalled->find(this_fn) == |
| 150 gFunctionsCalled->end()) { |
| 151 time_t seconds; |
| 152 long usecs; |
| 153 get_timestamp(&seconds, &usecs); |
| 154 fprintf(gLogFile, "%ld %ld\t%d:%ld\t%p\n", |
| 155 seconds, usecs, getpid(), pthread_self(), this_fn); |
| 156 fflush(gLogFile); |
| 157 gFunctionsCalled->insert(this_fn); |
| 158 } |
| 159 } |
| 160 gDepth -= 1; |
| 161 } |
| 162 pthread_mutex_unlock(&gMutex); |
| 163 } |
| 164 |
| 165 // Called internally by instrumentation inserted by the compiler upon exiting |
| 166 // a function. Instrumentation is inserted by the compiler for all functions |
| 167 // whose source is compiled with the -finstrument-functions CFLAG. The exit |
| 168 // of a function is ignored because only enter is useful for order profiling. |
| 169 void __cyg_profile_func_exit(void* this_fn, void* call_site) { |
| 170 // Do not do anything on function exit. |
| 171 } |
| 172 |
| 173 // Enables profiling and writes the first lines to the log file. The first |
| 174 // line(s) report any code segments that are currently mapped in memory by |
| 175 // reading the /proc/self/maps file and searching for "r-xp" access permission. |
| 176 // The address of these sections are later used to symbolize the logged |
| 177 // addresses of called functions. The format of the /proc/self/maps file is: |
| 178 // 00008000-0001f000 r-xp 00000000 b3:01 246 /system/bin/toolbox |
| 179 // 0001f000-00021000 rw-p 00017000 b3:01 246 /system/bin/toolbox |
| 180 // 00021000-00029000 rw-p 00000000 00:00 0 [heap] |
| 181 // 40007000-40015000 r-xp 00000000 b3:01 574 /system/lib/libcutils.so |
| 182 // 40015000-40016000 rw-p 0000e000 b3:01 574 /system/lib/libcutils.so |
| 183 // 40016000-40025000 rw-p 00000000 00:00 0 |
| 184 // 4002c000-4002f000 r-xp 00000000 b3:01 613 /system/lib/liblog.so |
| 185 // 4002f000-40030000 rw-p 00003000 b3:01 613 /system/lib/liblog.so |
| 186 // It also prints column headers and START on a new line to indicate that |
| 187 // logging is starting. If the name of the log file has not yet been set, it |
| 188 // sets the name of the log file to the default name (i.e. kDefaultFileName). |
| 189 void cygprofile_enable(void) { |
| 190 char line[kMaxLineSize]; |
| 191 void (*this_fn)(void) = &cygprofile_enable; |
| 192 char* p; |
| 193 char* q; |
| 194 std::ifstream mapsfile; |
| 195 |
| 196 // Return if profiling is already enabled |
| 197 if (gCygProfileEnabled) |
| 198 return; |
| 199 |
| 200 gFunctionsCalled = new std::set<void*>(); |
| 201 |
| 202 if (!gCygProfileFileName[0]) |
| 203 cygprofile_setfilename(kDefaultFileName); |
| 204 if (!cygprofile_openlogfile(gCygProfileFileName)) |
| 205 return; |
| 206 |
| 207 mapsfile.open("/proc/self/maps"); |
| 208 if (mapsfile.good()) { |
| 209 while (mapsfile.getline(line, kMaxLineSize)) { |
| 210 std::string str_line = std::string(line); |
| 211 int permindex = str_line.find("r-xp"); |
| 212 if (permindex != std::string::npos) { |
| 213 int dashindex = str_line.find("-"); |
| 214 int spaceindex = str_line.find(" "); |
| 215 void* start = reinterpret_cast<void*> |
| 216 (strtol((str_line.substr(0, dashindex)).c_str(), |
| 217 &p, 16)); |
| 218 if (*p != 0) |
| 219 fprintf(gLogFile, |
| 220 "cyg-profile.cc: ERROR: could not determine start: %s.\n", |
| 221 (str_line.substr(0, dashindex)).c_str()); |
| 222 void* end = reinterpret_cast<void*> |
| 223 (strtol((str_line.substr(dashindex + 1, |
| 224 spaceindex - dashindex - 1)).c_str(), |
| 225 &q, 16)); |
| 226 if (*q != 0) |
| 227 fprintf(gLogFile, |
| 228 "cyg-profile.cc: ERROR: could not determine end: %s.\n", |
| 229 (str_line.substr(dashindex + 1, |
| 230 spaceindex - dashindex - 1)).c_str()); |
| 231 if (this_fn >= start && this_fn < end) |
| 232 fprintf(gLogFile, "%s\n", str_line.c_str()); |
| 233 } |
| 234 } |
| 235 } else { |
| 236 fprintf(gLogFile, "cyg-profile.cc: ERROR: Can't open maps file."); |
| 237 } |
| 238 mapsfile.close(); |
| 239 fprintf(gLogFile, "secs msecs\tpid:threadid\tfunc\nSTART\n"); |
| 240 gCygProfileEnabled = true; |
| 241 } |
| 242 |
| 243 // Disables profiling. |
| 244 void cygprofile_disable(void) { |
| 245 gCygProfileEnabled = false; |
| 246 pthread_mutex_lock(&gMutex); |
| 247 delete gFunctionsCalled; |
| 248 gFunctionsCalled = NULL; |
| 249 pthread_mutex_unlock(&gMutex); |
| 250 } |
| 251 |
| 252 // Returns true if profiling is enabled. |
| 253 bool cygprofile_isenabled(void) { |
| 254 return gCygProfileEnabled; |
| 255 } |
| 256 |
| 257 // Sets the name of the log file. Returns -1 without setting the name if |
| 258 // profiling has already been enabled, because enabling profiling before |
| 259 // setting the file name will set the name of the log file to the default |
| 260 // name. If the file name is greater than the maximum size (kMaxFileNameSizeSiz
e) |
| 261 // returns -2 without setting the file name. |
| 262 int cygprofile_setfilename(const char* filename) { |
| 263 if (cygprofile_isenabled()) |
| 264 return -1; |
| 265 |
| 266 if (strlen(filename) > kMaxFileNameSizeSize) |
| 267 return -2; |
| 268 |
| 269 const char* ptr = strstr(filename, "%d"); |
| 270 if (ptr) { |
| 271 size_t len; |
| 272 len = ptr - filename; |
| 273 snprintf(gCygProfileFileName, len+1, "%s", filename); |
| 274 snprintf(&gCygProfileFileName[len], kMaxFileNameSizeSize - len, |
| 275 "%d", getpid()); |
| 276 len = strlen(gCygProfileFileName); |
| 277 snprintf(&gCygProfileFileName[len], kMaxFileNameSizeSize - len, |
| 278 "%s", ptr + 2); |
| 279 } else { |
| 280 snprintf(gCygProfileFileName, kMaxFileNameSizeSize, "%s", filename); |
| 281 } |
| 282 |
| 283 if (gLogFile) |
| 284 cygprofile_closelogfile(); |
| 285 |
| 286 return 0; |
| 287 } |
| 288 |
| 289 // Returns the log file name. If it has yet to be set, it is first |
| 290 // set to the default file name, kDefaultFileName. |
| 291 char* cygprofile_getfilename(void) { |
| 292 if (!strlen(gCygProfileFileName)) |
| 293 cygprofile_setfilename(kDefaultFileName); |
| 294 return gCygProfileFileName; |
| 295 } |
| 296 |
| 297 // Opens the file name specified and returns a pointer to the file. |
| 298 FILE* cygprofile_openlogfile(const char* filename) { |
| 299 static int complained = 0; |
| 300 FILE* file; |
| 301 |
| 302 if (complained) |
| 303 return NULL; |
| 304 |
| 305 if (gLogFile) |
| 306 return gLogFile; |
| 307 |
| 308 file = fopen(filename, "w"); |
| 309 if (!file) { |
| 310 fprintf(stderr, "WARNING: Can't open gLogFile '%s': %s\n", |
| 311 filename, strerror(errno)); |
| 312 complained = 1; |
| 313 return NULL; |
| 314 } |
| 315 |
| 316 setlinebuf(file); |
| 317 gLogFile = file; |
| 318 |
| 319 return file; |
| 320 } |
| 321 |
| 322 // Closes the log file (i.e. gLogFile). |
| 323 void cygprofile_closelogfile(void) { |
| 324 if (gLogFile) |
| 325 fclose(gLogFile); |
| 326 } |
| 327 |
| 328 // Given a file name, sets the name of the log file and then enables profiling. |
| 329 void cygprofile_start(const char* filename) { |
| 330 cygprofile_setfilename(filename); |
| 331 cygprofile_enable(); |
| 332 } |
| 333 |
| 334 // Indicates the end of profiling. Disables profiling, writes "END" on a |
| 335 // new line to indicate no more profiling, flushes the gLogFile and finally |
| 336 // closes the log file. |
| 337 void cygprofile_end(void) { |
| 338 cygprofile_disable(); |
| 339 fprintf(gLogFile, "END\n"); |
| 340 fflush(gLogFile); |
| 341 cygprofile_closelogfile(); |
| 342 } |
| 343 |
| 344 } // namespace cygprofile |
OLD | NEW |