/src/h2o/lib/handler/status/durations.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2016 Fastly |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | |
23 | | #include "h2o.h" |
24 | | #include "gkc.h" |
25 | | #include <inttypes.h> |
26 | | #include <pthread.h> |
27 | | |
28 | 0 | #define GK_EPSILON 0.01 |
29 | | |
30 | | struct st_duration_stats_t { |
31 | | struct gkc_summary *connect_time; |
32 | | struct gkc_summary *header_time; |
33 | | struct gkc_summary *body_time; |
34 | | struct gkc_summary *request_total_time; |
35 | | struct gkc_summary *process_time; |
36 | | struct gkc_summary *response_time; |
37 | | struct gkc_summary *total_time; |
38 | | |
39 | | /** |
40 | | * average event loop latency per worker thread |
41 | | */ |
42 | | H2O_VECTOR(uint64_t) evloop_latency_nanosec; |
43 | | }; |
44 | | |
45 | | struct st_duration_agg_stats_t { |
46 | | struct st_duration_stats_t stats; |
47 | | pthread_mutex_t mutex; |
48 | | }; |
49 | | |
50 | | static h2o_logger_t *durations_logger; |
51 | | static void durations_status_per_thread(void *priv, h2o_context_t *ctx) |
52 | 0 | { |
53 | 0 | struct st_duration_agg_stats_t *agg_stats = priv; |
54 | 0 | if (durations_logger) { |
55 | 0 | struct st_duration_stats_t *ctx_stats = h2o_context_get_logger_context(ctx, durations_logger); |
56 | 0 | pthread_mutex_lock(&agg_stats->mutex); |
57 | 0 | #define ADD_DURATION(x) \ |
58 | 0 | do { \ |
59 | 0 | struct gkc_summary *tmp; \ |
60 | 0 | tmp = gkc_combine(agg_stats->stats.x, ctx_stats->x); \ |
61 | 0 | gkc_summary_free(agg_stats->stats.x); \ |
62 | 0 | agg_stats->stats.x = tmp; \ |
63 | 0 | } while (0) |
64 | 0 | ADD_DURATION(connect_time); |
65 | 0 | ADD_DURATION(header_time); |
66 | 0 | ADD_DURATION(body_time); |
67 | 0 | ADD_DURATION(request_total_time); |
68 | 0 | ADD_DURATION(process_time); |
69 | 0 | ADD_DURATION(response_time); |
70 | 0 | ADD_DURATION(total_time); |
71 | 0 | #undef ADD_DURATION |
72 | |
|
73 | 0 | #if !H2O_USE_LIBUV |
74 | 0 | h2o_vector_reserve(NULL, &agg_stats->stats.evloop_latency_nanosec, agg_stats->stats.evloop_latency_nanosec.size + 1); |
75 | 0 | agg_stats->stats.evloop_latency_nanosec.entries[agg_stats->stats.evloop_latency_nanosec.size] = |
76 | 0 | h2o_evloop_get_execution_time_nanosec(ctx->loop); |
77 | 0 | agg_stats->stats.evloop_latency_nanosec.size++; |
78 | 0 | #endif |
79 | 0 | pthread_mutex_unlock(&agg_stats->mutex); |
80 | 0 | } |
81 | 0 | } |
82 | | |
83 | | static void duration_stats_init(struct st_duration_stats_t *stats) |
84 | 0 | { |
85 | 0 | stats->connect_time = gkc_summary_alloc(GK_EPSILON); |
86 | 0 | stats->header_time = gkc_summary_alloc(GK_EPSILON); |
87 | 0 | stats->body_time = gkc_summary_alloc(GK_EPSILON); |
88 | 0 | stats->request_total_time = gkc_summary_alloc(GK_EPSILON); |
89 | 0 | stats->process_time = gkc_summary_alloc(GK_EPSILON); |
90 | 0 | stats->response_time = gkc_summary_alloc(GK_EPSILON); |
91 | 0 | stats->total_time = gkc_summary_alloc(GK_EPSILON); |
92 | 0 | memset(&stats->evloop_latency_nanosec, 0, sizeof(stats->evloop_latency_nanosec)); |
93 | 0 | } |
94 | | |
95 | | static void *durations_status_init(void) |
96 | 0 | { |
97 | 0 | struct st_duration_agg_stats_t *agg_stats; |
98 | |
|
99 | 0 | agg_stats = h2o_mem_alloc(sizeof(*agg_stats)); |
100 | |
|
101 | 0 | duration_stats_init(&agg_stats->stats); |
102 | 0 | pthread_mutex_init(&agg_stats->mutex, NULL); |
103 | |
|
104 | 0 | return agg_stats; |
105 | 0 | } |
106 | | |
107 | | static void duration_stats_free(struct st_duration_stats_t *stats) |
108 | 0 | { |
109 | 0 | gkc_summary_free(stats->connect_time); |
110 | 0 | gkc_summary_free(stats->header_time); |
111 | 0 | gkc_summary_free(stats->body_time); |
112 | 0 | gkc_summary_free(stats->request_total_time); |
113 | 0 | gkc_summary_free(stats->process_time); |
114 | 0 | gkc_summary_free(stats->response_time); |
115 | 0 | gkc_summary_free(stats->total_time); |
116 | 0 | free(stats->evloop_latency_nanosec.entries); |
117 | 0 | } |
118 | | |
119 | | static h2o_iovec_t durations_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) |
120 | 0 | { |
121 | 0 | struct st_duration_agg_stats_t *agg_stats = priv; |
122 | 0 | h2o_iovec_t ret; |
123 | |
|
124 | 0 | #define BUFSIZE 16384 |
125 | 0 | #define DURATION_FMT(x) \ |
126 | 0 | " \"" x "-0\": %lu,\n" \ |
127 | 0 | " \"" x "-25\": %lu,\n" \ |
128 | 0 | " \"" x "-50\": %lu,\n" \ |
129 | 0 | " \"" x "-75\": %lu,\n" \ |
130 | 0 | " \"" x "-99\": %lu\n" |
131 | 0 | #define DURATION_VALS(x) \ |
132 | 0 | gkc_query(agg_stats->stats.x, 0), gkc_query(agg_stats->stats.x, 0.25), gkc_query(agg_stats->stats.x, 0.5), \ |
133 | 0 | gkc_query(agg_stats->stats.x, 0.75), gkc_query(agg_stats->stats.x, 0.99) |
134 | |
|
135 | 0 | ret.base = h2o_mem_alloc_pool(&req->pool, char, BUFSIZE); |
136 | 0 | ret.len = snprintf( |
137 | 0 | ret.base, BUFSIZE, |
138 | 0 | ",\n" DURATION_FMT("connect-time") "," DURATION_FMT("header-time") "," DURATION_FMT("body-time") "," DURATION_FMT( |
139 | 0 | "request-total-time") "," DURATION_FMT("process-time") "," DURATION_FMT("response-time") "," DURATION_FMT("duration"), |
140 | 0 | DURATION_VALS(connect_time), DURATION_VALS(header_time), DURATION_VALS(body_time), DURATION_VALS(request_total_time), |
141 | 0 | DURATION_VALS(process_time), DURATION_VALS(response_time), DURATION_VALS(total_time)); |
142 | 0 | #undef DURATION_FMT |
143 | 0 | #undef DURATION_VALS |
144 | 0 | char *delim = ""; |
145 | 0 | ret.len += sprintf(ret.base + ret.len, ",\n\"evloop-latency-nanosec\": ["); |
146 | 0 | size_t i; |
147 | 0 | for (i = 0; i < agg_stats->stats.evloop_latency_nanosec.size; i++) { |
148 | 0 | size_t len = snprintf(NULL, 0, "%s%" PRIu64, delim, agg_stats->stats.evloop_latency_nanosec.entries[i]); |
149 | | /* require that there's enough space for the closing "]\0" */ |
150 | 0 | if (ret.len + len + 1 >= BUFSIZE) |
151 | 0 | break; |
152 | 0 | ret.len += snprintf(ret.base + ret.len, BUFSIZE - ret.len, "%s%" PRIu64, delim, |
153 | 0 | agg_stats->stats.evloop_latency_nanosec.entries[i]); |
154 | 0 | delim = ","; |
155 | 0 | } |
156 | 0 | ret.len += snprintf(ret.base + ret.len, BUFSIZE - ret.len, "]"); |
157 | 0 | #undef BUFSIZE |
158 | 0 | duration_stats_free(&agg_stats->stats); |
159 | 0 | pthread_mutex_destroy(&agg_stats->mutex); |
160 | |
|
161 | 0 | free(agg_stats); |
162 | 0 | return ret; |
163 | 0 | } |
164 | | |
165 | | static void stat_access(h2o_logger_t *_self, h2o_req_t *req) |
166 | 0 | { |
167 | 0 | struct st_duration_stats_t *ctx_stats = h2o_context_get_logger_context(req->conn->ctx, _self); |
168 | 0 | #define ADD_OBSERVATION(x, from, until) \ |
169 | 0 | do { \ |
170 | 0 | int64_t dur; \ |
171 | 0 | if (h2o_time_compute_##x(req, &dur)) { \ |
172 | 0 | gkc_insert_value(ctx_stats->x, dur); \ |
173 | 0 | } \ |
174 | 0 | } while (0) |
175 | |
|
176 | 0 | ADD_OBSERVATION(connect_time, &req->conn->connected_at, &req->timestamps.request_begin_at); |
177 | 0 | ADD_OBSERVATION(header_time, &req->timestamps.request_begin_at, |
178 | 0 | h2o_timeval_is_null(&req->timestamps.request_body_begin_at) ? &req->processed_at.at |
179 | 0 | : &req->timestamps.request_body_begin_at); |
180 | 0 | ADD_OBSERVATION(body_time, |
181 | 0 | h2o_timeval_is_null(&req->timestamps.request_body_begin_at) ? &req->processed_at.at |
182 | 0 | : &req->timestamps.request_body_begin_at, |
183 | 0 | &req->processed_at.at); |
184 | 0 | ADD_OBSERVATION(request_total_time, &req->timestamps.request_begin_at, &req->processed_at.at); |
185 | 0 | ADD_OBSERVATION(process_time, &req->processed_at.at, &req->timestamps.response_start_at); |
186 | 0 | ADD_OBSERVATION(response_time, &req->timestamps.response_start_at, &req->timestamps.response_end_at); |
187 | 0 | ADD_OBSERVATION(total_time, &req->timestamps.request_begin_at, &req->timestamps.response_end_at); |
188 | 0 | #undef ADD_OBSERVATION |
189 | 0 | } |
190 | | |
191 | | static void on_context_init(struct st_h2o_logger_t *self, h2o_context_t *ctx) |
192 | 0 | { |
193 | 0 | struct st_duration_stats_t *duration_stats = h2o_mem_alloc(sizeof(struct st_duration_stats_t)); |
194 | 0 | duration_stats_init(duration_stats); |
195 | 0 | h2o_context_set_logger_context(ctx, self, duration_stats); |
196 | 0 | } |
197 | | |
198 | | static void on_context_dispose(struct st_h2o_logger_t *self, h2o_context_t *ctx) |
199 | 0 | { |
200 | 0 | struct st_duration_stats_t *duration_stats; |
201 | 0 | duration_stats = h2o_context_get_logger_context(ctx, self); |
202 | 0 | duration_stats_free(duration_stats); |
203 | 0 | } |
204 | | |
205 | | void h2o_duration_stats_register(h2o_globalconf_t *conf) |
206 | 0 | { |
207 | 0 | int i, k; |
208 | 0 | h2o_logger_t *logger; |
209 | 0 | h2o_hostconf_t *hconf; |
210 | |
|
211 | 0 | durations_logger = logger = h2o_mem_alloc(sizeof(*logger)); |
212 | 0 | memset(logger, 0, sizeof(*logger)); |
213 | 0 | logger->_config_slot = conf->_num_config_slots++; |
214 | 0 | logger->log_access = stat_access; |
215 | 0 | logger->on_context_init = on_context_init; |
216 | 0 | logger->on_context_dispose = on_context_dispose; |
217 | |
|
218 | 0 | for (k = 0; conf->hosts[k]; k++) { |
219 | 0 | hconf = conf->hosts[k]; |
220 | 0 | for (i = 0; i < hconf->paths.size; i++) { |
221 | 0 | int j; |
222 | 0 | for (j = 0; j < hconf->paths.entries[i]->handlers.size; j++) { |
223 | 0 | h2o_pathconf_t *pathconf = hconf->paths.entries[i]; |
224 | 0 | h2o_vector_reserve(NULL, &pathconf->_loggers, pathconf->_loggers.size + 1); |
225 | 0 | pathconf->_loggers.entries[pathconf->_loggers.size++] = (void *)logger; |
226 | 0 | } |
227 | 0 | } |
228 | 0 | } |
229 | 0 | } |
230 | | |
231 | | h2o_status_handler_t h2o_durations_status_handler = { |
232 | | {H2O_STRLIT("durations")}, durations_status_final, durations_status_init, durations_status_per_thread}; |