Home | History | Annotate | Download | only in profiling
      1 /*
      2  *
      3  * Copyright 2015 gRPC authors.
      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 
     19 #include <grpc/support/port_platform.h>
     20 
     21 #include "src/core/lib/profiling/timers.h"
     22 
     23 #ifdef GRPC_BASIC_PROFILER
     24 
     25 #include <grpc/support/alloc.h>
     26 #include <grpc/support/log.h>
     27 #include <grpc/support/sync.h>
     28 #include <grpc/support/time.h>
     29 #include <inttypes.h>
     30 #include <pthread.h>
     31 #include <stdio.h>
     32 #include <string.h>
     33 
     34 #include "src/core/lib/gpr/env.h"
     35 
     36 typedef enum { BEGIN = '{', END = '}', MARK = '.' } marker_type;
     37 
     38 typedef struct gpr_timer_entry {
     39   gpr_timespec tm;
     40   const char* tagstr;
     41   const char* file;
     42   short line;
     43   char type;
     44   uint8_t important;
     45   int thd;
     46 } gpr_timer_entry;
     47 
     48 #define MAX_COUNT 1000000
     49 
     50 typedef struct gpr_timer_log {
     51   size_t num_entries;
     52   struct gpr_timer_log* next;
     53   struct gpr_timer_log* prev;
     54   gpr_timer_entry log[MAX_COUNT];
     55 } gpr_timer_log;
     56 
     57 typedef struct gpr_timer_log_list {
     58   gpr_timer_log* head;
     59   /* valid iff head!=NULL */
     60   gpr_timer_log* tail;
     61 } gpr_timer_log_list;
     62 
     63 static __thread gpr_timer_log* g_thread_log;
     64 static gpr_once g_once_init = GPR_ONCE_INIT;
     65 static FILE* output_file;
     66 static const char* output_filename_or_null = NULL;
     67 static pthread_mutex_t g_mu;
     68 static pthread_cond_t g_cv;
     69 static gpr_timer_log_list g_in_progress_logs;
     70 static gpr_timer_log_list g_done_logs;
     71 static int g_shutdown;
     72 static pthread_t g_writing_thread;
     73 static __thread int g_thread_id;
     74 static int g_next_thread_id;
     75 static int g_writing_enabled = 1;
     76 
     77 static const char* output_filename() {
     78   if (output_filename_or_null == NULL) {
     79     output_filename_or_null = gpr_getenv("LATENCY_TRACE");
     80     if (output_filename_or_null == NULL ||
     81         strlen(output_filename_or_null) == 0) {
     82       output_filename_or_null = "latency_trace.txt";
     83     }
     84   }
     85   return output_filename_or_null;
     86 }
     87 
     88 static int timer_log_push_back(gpr_timer_log_list* list, gpr_timer_log* log) {
     89   if (list->head == NULL) {
     90     list->head = list->tail = log;
     91     log->next = log->prev = NULL;
     92     return 1;
     93   } else {
     94     log->prev = list->tail;
     95     log->next = NULL;
     96     list->tail->next = log;
     97     list->tail = log;
     98     return 0;
     99   }
    100 }
    101 
    102 static gpr_timer_log* timer_log_pop_front(gpr_timer_log_list* list) {
    103   gpr_timer_log* out = list->head;
    104   if (out != NULL) {
    105     list->head = out->next;
    106     if (list->head != NULL) {
    107       list->head->prev = NULL;
    108     } else {
    109       list->tail = NULL;
    110     }
    111   }
    112   return out;
    113 }
    114 
    115 static void timer_log_remove(gpr_timer_log_list* list, gpr_timer_log* log) {
    116   if (log->prev == NULL) {
    117     list->head = log->next;
    118     if (list->head != NULL) {
    119       list->head->prev = NULL;
    120     }
    121   } else {
    122     log->prev->next = log->next;
    123   }
    124   if (log->next == NULL) {
    125     list->tail = log->prev;
    126     if (list->tail != NULL) {
    127       list->tail->next = NULL;
    128     }
    129   } else {
    130     log->next->prev = log->prev;
    131   }
    132 }
    133 
    134 static void write_log(gpr_timer_log* log) {
    135   size_t i;
    136   if (output_file == NULL) {
    137     output_file = fopen(output_filename(), "w");
    138   }
    139   for (i = 0; i < log->num_entries; i++) {
    140     gpr_timer_entry* entry = &(log->log[i]);
    141     if (gpr_time_cmp(entry->tm, gpr_time_0(entry->tm.clock_type)) < 0) {
    142       entry->tm = gpr_time_0(entry->tm.clock_type);
    143     }
    144     fprintf(output_file,
    145             "{\"t\": %" PRId64
    146             ".%09d, \"thd\": \"%d\", \"type\": \"%c\", \"tag\": "
    147             "\"%s\", \"file\": \"%s\", \"line\": %d, \"imp\": %d}\n",
    148             entry->tm.tv_sec, entry->tm.tv_nsec, entry->thd, entry->type,
    149             entry->tagstr, entry->file, entry->line, entry->important);
    150   }
    151 }
    152 
    153 static void* writing_thread(void* unused) {
    154   gpr_timer_log* log;
    155   pthread_mutex_lock(&g_mu);
    156   for (;;) {
    157     while ((log = timer_log_pop_front(&g_done_logs)) == NULL && !g_shutdown) {
    158       pthread_cond_wait(&g_cv, &g_mu);
    159     }
    160     if (log != NULL) {
    161       pthread_mutex_unlock(&g_mu);
    162       write_log(log);
    163       free(log);
    164       pthread_mutex_lock(&g_mu);
    165     }
    166     if (g_shutdown) {
    167       pthread_mutex_unlock(&g_mu);
    168       return NULL;
    169     }
    170   }
    171 }
    172 
    173 static void flush_logs(gpr_timer_log_list* list) {
    174   gpr_timer_log* log;
    175   while ((log = timer_log_pop_front(list)) != NULL) {
    176     write_log(log);
    177     free(log);
    178   }
    179 }
    180 
    181 static void finish_writing(void) {
    182   pthread_mutex_lock(&g_mu);
    183   g_shutdown = 1;
    184   pthread_cond_signal(&g_cv);
    185   pthread_mutex_unlock(&g_mu);
    186   pthread_join(g_writing_thread, NULL);
    187 
    188   gpr_log(GPR_INFO, "flushing logs");
    189 
    190   pthread_mutex_lock(&g_mu);
    191   flush_logs(&g_done_logs);
    192   flush_logs(&g_in_progress_logs);
    193   pthread_mutex_unlock(&g_mu);
    194 
    195   if (output_file) {
    196     fclose(output_file);
    197   }
    198 }
    199 
    200 void gpr_timers_set_log_filename(const char* filename) {
    201   output_filename_or_null = filename;
    202 }
    203 
    204 static void init_output() {
    205   pthread_attr_t attr;
    206   pthread_attr_init(&attr);
    207   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    208   pthread_create(&g_writing_thread, &attr, &writing_thread, NULL);
    209   pthread_attr_destroy(&attr);
    210 
    211   atexit(finish_writing);
    212 }
    213 
    214 static void rotate_log() {
    215   /* Using malloc here, as this code could end up being called by gpr_malloc */
    216   gpr_timer_log* log = static_cast<gpr_timer_log*>(malloc(sizeof(*log)));
    217   gpr_once_init(&g_once_init, init_output);
    218   log->num_entries = 0;
    219   pthread_mutex_lock(&g_mu);
    220   if (g_thread_log != NULL) {
    221     timer_log_remove(&g_in_progress_logs, g_thread_log);
    222     if (timer_log_push_back(&g_done_logs, g_thread_log)) {
    223       pthread_cond_signal(&g_cv);
    224     }
    225   } else {
    226     g_thread_id = g_next_thread_id++;
    227   }
    228   timer_log_push_back(&g_in_progress_logs, log);
    229   pthread_mutex_unlock(&g_mu);
    230   g_thread_log = log;
    231 }
    232 
    233 static void gpr_timers_log_add(const char* tagstr, marker_type type,
    234                                int important, const char* file, int line) {
    235   gpr_timer_entry* entry;
    236 
    237   if (!g_writing_enabled) {
    238     return;
    239   }
    240 
    241   if (g_thread_log == NULL || g_thread_log->num_entries == MAX_COUNT) {
    242     rotate_log();
    243   }
    244 
    245   entry = &g_thread_log->log[g_thread_log->num_entries++];
    246 
    247   entry->tm = gpr_now(GPR_CLOCK_PRECISE);
    248   entry->tagstr = tagstr;
    249   entry->type = type;
    250   entry->file = file;
    251   entry->line = (short)line;
    252   entry->important = important != 0;
    253   entry->thd = g_thread_id;
    254 }
    255 
    256 /* Latency profiler API implementation. */
    257 void gpr_timer_add_mark(const char* tagstr, int important, const char* file,
    258                         int line) {
    259   gpr_timers_log_add(tagstr, MARK, important, file, line);
    260 }
    261 
    262 void gpr_timer_begin(const char* tagstr, int important, const char* file,
    263                      int line) {
    264   gpr_timers_log_add(tagstr, BEGIN, important, file, line);
    265 }
    266 
    267 void gpr_timer_end(const char* tagstr, int important, const char* file,
    268                    int line) {
    269   gpr_timers_log_add(tagstr, END, important, file, line);
    270 }
    271 
    272 void gpr_timer_set_enabled(int enabled) { g_writing_enabled = enabled; }
    273 
    274 /* Basic profiler specific API functions. */
    275 void gpr_timers_global_init(void) {}
    276 
    277 void gpr_timers_global_destroy(void) {}
    278 
    279 #else  /* !GRPC_BASIC_PROFILER */
    280 void gpr_timers_global_init(void) {}
    281 
    282 void gpr_timers_global_destroy(void) {}
    283 
    284 void gpr_timers_set_log_filename(const char* filename) {}
    285 
    286 void gpr_timer_set_enabled(int enabled) {}
    287 #endif /* GRPC_BASIC_PROFILER */
    288