diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-03-09 13:19:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-03-09 13:20:02 +0000 |
commit | 58daab21cd043e1dc37024a7f99b396788372918 (patch) | |
tree | 96771e43bb69f7c1c2b0b4f7374cb74d7866d0cb /web/server/h2o/libh2o/lib/handler/status | |
parent | Releasing debian version 1.43.2-1. (diff) | |
download | netdata-58daab21cd043e1dc37024a7f99b396788372918.tar.xz netdata-58daab21cd043e1dc37024a7f99b396788372918.zip |
Merging upstream version 1.44.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | web/server/h2o/libh2o/lib/handler/status.c | 270 | ||||
-rw-r--r-- | web/server/h2o/libh2o/lib/handler/status/durations.c | 207 | ||||
-rw-r--r-- | web/server/h2o/libh2o/lib/handler/status/events.c | 112 | ||||
-rw-r--r-- | web/server/h2o/libh2o/lib/handler/status/requests.c | 151 |
4 files changed, 740 insertions, 0 deletions
diff --git a/web/server/h2o/libh2o/lib/handler/status.c b/web/server/h2o/libh2o/lib/handler/status.c new file mode 100644 index 000000000..93befed3b --- /dev/null +++ b/web/server/h2o/libh2o/lib/handler/status.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "h2o.h" + +extern h2o_status_handler_t events_status_handler; +extern h2o_status_handler_t requests_status_handler; +extern h2o_status_handler_t durations_status_handler; + +struct st_h2o_status_logger_t { + h2o_logger_t super; +}; + +struct st_h2o_root_status_handler_t { + h2o_handler_t super; + H2O_VECTOR(h2o_multithread_receiver_t *) receivers; +}; + +struct st_h2o_status_context_t { + h2o_context_t *ctx; + h2o_multithread_receiver_t receiver; +}; + +struct st_status_ctx_t { + int active; + void *ctx; +}; +struct st_h2o_status_collector_t { + struct { + h2o_req_t *req; + h2o_multithread_receiver_t *receiver; + } src; + size_t num_remaining_threads_atomic; + H2O_VECTOR(struct st_status_ctx_t) status_ctx; +}; + +struct st_h2o_status_message_t { + h2o_multithread_message_t super; + struct st_h2o_status_collector_t *collector; +}; + +static void collect_reqs_of_context(struct st_h2o_status_collector_t *collector, h2o_context_t *ctx) +{ + int i; + + for (i = 0; i < ctx->globalconf->statuses.size; i++) { + struct st_status_ctx_t *sc = collector->status_ctx.entries + i; + h2o_status_handler_t *sh = ctx->globalconf->statuses.entries + i; + if (sc->active && sh->per_thread != NULL) + sh->per_thread(sc->ctx, ctx); + } + + if (__sync_sub_and_fetch(&collector->num_remaining_threads_atomic, 1) == 0) { + struct st_h2o_status_message_t *message = h2o_mem_alloc(sizeof(*message)); + message->super = (h2o_multithread_message_t){{NULL}}; + message->collector = collector; + h2o_multithread_send_message(collector->src.receiver, &message->super); + } +} + +static void send_response(struct st_h2o_status_collector_t *collector) +{ + static h2o_generator_t generator = {NULL, NULL}; + h2o_req_t *req; + size_t nr_statuses; + int i; + int cur_resp = 0; + + req = collector->src.req; + if (!req) { + h2o_mem_release_shared(collector); + return; + } + + nr_statuses = req->conn->ctx->globalconf->statuses.size; + size_t nr_resp = nr_statuses + 2; // 2 for the footer and header + h2o_iovec_t resp[nr_resp]; + + memset(resp, 0, sizeof(resp[0]) * nr_resp); + resp[cur_resp++] = (h2o_iovec_t){H2O_STRLIT("{\n")}; + + int coma_removed = 0; + for (i = 0; i < req->conn->ctx->globalconf->statuses.size; i++) { + h2o_status_handler_t *sh = &req->conn->ctx->globalconf->statuses.entries[i]; + if (!collector->status_ctx.entries[i].active) { + continue; + } + resp[cur_resp++] = sh->final(collector->status_ctx.entries[i].ctx, req->conn->ctx->globalconf, req); + if (resp[cur_resp - 1].len > 0 && !coma_removed) { + /* requests come in with a leading coma, replace if with a space */ + resp[cur_resp - 1].base[0] = ' '; + coma_removed = 1; + } + } + resp[cur_resp++] = (h2o_iovec_t){H2O_STRLIT("\n}\n")}; + + req->res.status = 200; + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, H2O_STRLIT("text/plain; charset=utf-8")); + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, NULL, H2O_STRLIT("no-cache, no-store")); + h2o_start_response(req, &generator); + h2o_send(req, resp, h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD")) ? 0 : nr_resp, + H2O_SEND_STATE_FINAL); + h2o_mem_release_shared(collector); +} + +static void on_collect_notify(h2o_multithread_receiver_t *receiver, h2o_linklist_t *messages) +{ + struct st_h2o_status_context_t *status_ctx = H2O_STRUCT_FROM_MEMBER(struct st_h2o_status_context_t, receiver, receiver); + + while (!h2o_linklist_is_empty(messages)) { + struct st_h2o_status_message_t *message = H2O_STRUCT_FROM_MEMBER(struct st_h2o_status_message_t, super, messages->next); + struct st_h2o_status_collector_t *collector = message->collector; + h2o_linklist_unlink(&message->super.link); + free(message); + + if (__sync_add_and_fetch(&collector->num_remaining_threads_atomic, 0) != 0) { + collect_reqs_of_context(collector, status_ctx->ctx); + } else { + send_response(collector); + } + } +} + +static void on_collector_dispose(void *_collector) +{ +} + +static void on_req_close(void *p) +{ + struct st_h2o_status_collector_t *collector = *(void **)p; + collector->src.req = NULL; + h2o_mem_release_shared(collector); +} + +static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req, h2o_iovec_t status_list) +{ + { /* construct collector and send request to every thread */ + struct st_h2o_status_context_t *status_ctx = h2o_context_get_handler_context(req->conn->ctx, &self->super); + struct st_h2o_status_collector_t *collector = h2o_mem_alloc_shared(NULL, sizeof(*collector), on_collector_dispose); + size_t i; + + memset(collector, 0, sizeof(*collector)); + for (i = 0; i < req->conn->ctx->globalconf->statuses.size; i++) { + h2o_status_handler_t *sh; + + h2o_vector_reserve(&req->pool, &collector->status_ctx, collector->status_ctx.size + 1); + sh = &req->conn->ctx->globalconf->statuses.entries[i]; + + if (status_list.base) { + if (!h2o_contains_token(status_list.base, status_list.len, sh->name.base, sh->name.len, ',')) { + collector->status_ctx.entries[collector->status_ctx.size].active = 0; + goto Skip; + } + } + if (sh->init) { + collector->status_ctx.entries[collector->status_ctx.size].ctx = sh->init(); + } + collector->status_ctx.entries[collector->status_ctx.size].active = 1; + Skip: + collector->status_ctx.size++; + } + collector->src.req = req; + collector->src.receiver = &status_ctx->receiver; + collector->num_remaining_threads_atomic = self->receivers.size; + + for (i = 0; i != self->receivers.size; ++i) { + struct st_h2o_status_message_t *message = h2o_mem_alloc(sizeof(*message)); + *message = (struct st_h2o_status_message_t){{{NULL}}, collector}; + h2o_multithread_send_message(self->receivers.entries[i], &message->super); + } + + /* collector is also retained by the on_req_close callback */ + *(struct st_h2o_status_collector_t **)h2o_mem_alloc_shared(&req->pool, sizeof(collector), on_req_close) = collector; + h2o_mem_addref_shared(collector); + } + + return 0; +} + +static int on_req(h2o_handler_t *_self, h2o_req_t *req) +{ + struct st_h2o_root_status_handler_t *self = (void *)_self; + size_t prefix_len = req->pathconf->path.len - (req->pathconf->path.base[req->pathconf->path.len - 1] == '/'); + h2o_iovec_t local_path = h2o_iovec_init(req->path_normalized.base + prefix_len, req->path_normalized.len - prefix_len); + + if (local_path.len == 0 || h2o_memis(local_path.base, local_path.len, H2O_STRLIT("/"))) { + /* root of the handler returns HTML that renders the status */ + h2o_iovec_t fn; + const char *root = getenv("H2O_ROOT"); + if (root == NULL) + root = H2O_TO_STR(H2O_ROOT); + fn = h2o_concat(&req->pool, h2o_iovec_init(root, strlen(root)), h2o_iovec_init(H2O_STRLIT("/share/h2o/status/index.html"))); + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, NULL, H2O_STRLIT("no-cache")); + return h2o_file_send(req, 200, "OK", fn.base, h2o_iovec_init(H2O_STRLIT("text/html; charset=utf-8")), 0); + } else if (h2o_memis(local_path.base, local_path.len, H2O_STRLIT("/json"))) { + int ret; + /* "/json" maps to the JSON API */ + h2o_iovec_t status_list = {NULL, 0}; /* NULL means we'll show all statuses */ + if (req->query_at != SIZE_MAX && (req->path.len - req->query_at > 6)) { + if (h2o_memis(&req->path.base[req->query_at], 6, "?show=", 6)) { + status_list = h2o_iovec_init(&req->path.base[req->query_at + 6], req->path.len - req->query_at - 6); + } + } + ret = on_req_json(self, req, status_list); + return ret; + } + + return -1; +} + +static void on_context_init(h2o_handler_t *_self, h2o_context_t *ctx) +{ + struct st_h2o_root_status_handler_t *self = (void *)_self; + struct st_h2o_status_context_t *status_ctx = h2o_mem_alloc(sizeof(*status_ctx)); + + status_ctx->ctx = ctx; + h2o_multithread_register_receiver(ctx->queue, &status_ctx->receiver, on_collect_notify); + + h2o_vector_reserve(NULL, &self->receivers, self->receivers.size + 1); + self->receivers.entries[self->receivers.size++] = &status_ctx->receiver; + + h2o_context_set_handler_context(ctx, &self->super, status_ctx); +} + +static void on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx) +{ + struct st_h2o_root_status_handler_t *self = (void *)_self; + struct st_h2o_status_context_t *status_ctx = h2o_context_get_handler_context(ctx, &self->super); + size_t i; + + for (i = 0; i != self->receivers.size; ++i) + if (self->receivers.entries[i] == &status_ctx->receiver) + break; + assert(i != self->receivers.size); + memmove(self->receivers.entries + i + 1, self->receivers.entries + i, self->receivers.size - i - 1); + --self->receivers.size; + + h2o_multithread_unregister_receiver(ctx->queue, &status_ctx->receiver); + + free(status_ctx); +} + +void h2o_status_register(h2o_pathconf_t *conf) +{ + struct st_h2o_root_status_handler_t *self = (void *)h2o_create_handler(conf, sizeof(*self)); + self->super.on_context_init = on_context_init; + self->super.on_context_dispose = on_context_dispose; + self->super.on_req = on_req; + h2o_config_register_status_handler(conf->global, requests_status_handler); + h2o_config_register_status_handler(conf->global, events_status_handler); + h2o_config_register_status_handler(conf->global, durations_status_handler); +} diff --git a/web/server/h2o/libh2o/lib/handler/status/durations.c b/web/server/h2o/libh2o/lib/handler/status/durations.c new file mode 100644 index 000000000..f011107bf --- /dev/null +++ b/web/server/h2o/libh2o/lib/handler/status/durations.c @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2016 Fastly + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "h2o.h" +#include "gkc.h" +#include <inttypes.h> +#include <pthread.h> + +#define GK_EPSILON 0.01 + +struct st_duration_stats_t { + struct gkc_summary *connect_time; + struct gkc_summary *header_time; + struct gkc_summary *body_time; + struct gkc_summary *request_total_time; + struct gkc_summary *process_time; + struct gkc_summary *response_time; + struct gkc_summary *duration; +}; + +struct st_duration_agg_stats_t { + struct st_duration_stats_t stats; + pthread_mutex_t mutex; +}; + +static h2o_logger_t *durations_logger; +static void durations_status_per_thread(void *priv, h2o_context_t *ctx) +{ + struct st_duration_agg_stats_t *agg_stats = priv; + if (durations_logger) { + struct st_duration_stats_t *ctx_stats = h2o_context_get_logger_context(ctx, durations_logger); + pthread_mutex_lock(&agg_stats->mutex); +#define ADD_DURATION(x) \ + do { \ + struct gkc_summary *tmp; \ + tmp = gkc_combine(agg_stats->stats.x, ctx_stats->x); \ + gkc_summary_free(agg_stats->stats.x); \ + agg_stats->stats.x = tmp; \ + } while (0) + ADD_DURATION(connect_time); + ADD_DURATION(header_time); + ADD_DURATION(body_time); + ADD_DURATION(request_total_time); + ADD_DURATION(process_time); + ADD_DURATION(response_time); + ADD_DURATION(duration); +#undef ADD_DURATION + pthread_mutex_unlock(&agg_stats->mutex); + } +} + +static void duration_stats_init(struct st_duration_stats_t *stats) +{ + stats->connect_time = gkc_summary_alloc(GK_EPSILON); + stats->header_time = gkc_summary_alloc(GK_EPSILON); + stats->body_time = gkc_summary_alloc(GK_EPSILON); + stats->request_total_time = gkc_summary_alloc(GK_EPSILON); + stats->process_time = gkc_summary_alloc(GK_EPSILON); + stats->response_time = gkc_summary_alloc(GK_EPSILON); + stats->duration = gkc_summary_alloc(GK_EPSILON); +} + +static void *durations_status_init(void) +{ + struct st_duration_agg_stats_t *agg_stats; + + agg_stats = h2o_mem_alloc(sizeof(*agg_stats)); + + duration_stats_init(&agg_stats->stats); + pthread_mutex_init(&agg_stats->mutex, NULL); + + return agg_stats; +} + +static void duration_stats_free(struct st_duration_stats_t *stats) +{ + gkc_summary_free(stats->connect_time); + gkc_summary_free(stats->header_time); + gkc_summary_free(stats->body_time); + gkc_summary_free(stats->request_total_time); + gkc_summary_free(stats->process_time); + gkc_summary_free(stats->response_time); + gkc_summary_free(stats->duration); +} + +static h2o_iovec_t durations_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) +{ + struct st_duration_agg_stats_t *agg_stats = priv; + h2o_iovec_t ret; + +#define BUFSIZE 16384 +#define DURATION_FMT(x) \ + " \"" x "-0\": %lu,\n" \ + " \"" x "-25\": %lu,\n" \ + " \"" x "-50\": %lu,\n" \ + " \"" x "-75\": %lu,\n" \ + " \"" x "-99\": %lu\n" +#define DURATION_VALS(x) \ + gkc_query(agg_stats->stats.x, 0), gkc_query(agg_stats->stats.x, 0.25), gkc_query(agg_stats->stats.x, 0.5), \ + gkc_query(agg_stats->stats.x, 0.75), gkc_query(agg_stats->stats.x, 0.99) + + ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE); + ret.len = snprintf( + ret.base, BUFSIZE, + ",\n" DURATION_FMT("connect-time") "," DURATION_FMT("header-time") "," DURATION_FMT("body-time") "," DURATION_FMT( + "request-total-time") "," DURATION_FMT("process-time") "," DURATION_FMT("response-time") "," DURATION_FMT("duration"), + DURATION_VALS(connect_time), DURATION_VALS(header_time), DURATION_VALS(body_time), DURATION_VALS(request_total_time), + DURATION_VALS(process_time), DURATION_VALS(response_time), DURATION_VALS(duration)); + +#undef BUFSIZE +#undef DURATION_FMT +#undef DURATION_VALS + + duration_stats_free(&agg_stats->stats); + pthread_mutex_destroy(&agg_stats->mutex); + + free(agg_stats); + return ret; +} + +static void stat_access(h2o_logger_t *_self, h2o_req_t *req) +{ + struct st_duration_stats_t *ctx_stats = h2o_context_get_logger_context(req->conn->ctx, _self); +#define ADD_OBSERVATION(x, from, until) \ + do { \ + int64_t dur; \ + if (h2o_time_compute_##x(req, &dur)) { \ + gkc_insert_value(ctx_stats->x, dur); \ + } \ + } while (0) + + ADD_OBSERVATION(connect_time, &req->conn->connected_at, &req->timestamps.request_begin_at); + ADD_OBSERVATION(header_time, &req->timestamps.request_begin_at, h2o_timeval_is_null(&req->timestamps.request_body_begin_at) + ? &req->processed_at.at + : &req->timestamps.request_body_begin_at); + ADD_OBSERVATION(body_time, h2o_timeval_is_null(&req->timestamps.request_body_begin_at) ? &req->processed_at.at + : &req->timestamps.request_body_begin_at, + &req->processed_at.at); + ADD_OBSERVATION(request_total_time, &req->timestamps.request_begin_at, &req->processed_at.at); + ADD_OBSERVATION(process_time, &req->processed_at.at, &req->timestamps.response_start_at); + ADD_OBSERVATION(response_time, &req->timestamps.response_start_at, &req->timestamps.response_end_at); + ADD_OBSERVATION(duration, &req->timestamps.request_begin_at, &req->timestamps.response_end_at); +#undef ADD_OBSERVATION +} + +void on_context_init(struct st_h2o_logger_t *self, h2o_context_t *ctx) +{ + struct st_duration_stats_t *duration_stats = h2o_mem_alloc(sizeof(struct st_duration_stats_t)); + duration_stats_init(duration_stats); + h2o_context_set_logger_context(ctx, self, duration_stats); +} + +void on_context_dispose(struct st_h2o_logger_t *self, h2o_context_t *ctx) +{ + struct st_duration_stats_t *duration_stats; + duration_stats = h2o_context_get_logger_context(ctx, self); + duration_stats_free(duration_stats); +} + +void h2o_duration_stats_register(h2o_globalconf_t *conf) +{ + int i, k; + h2o_logger_t *logger; + h2o_hostconf_t *hconf; + + durations_logger = logger = h2o_mem_alloc(sizeof(*logger)); + memset(logger, 0, sizeof(*logger)); + logger->_config_slot = conf->_num_config_slots++; + logger->log_access = stat_access; + logger->on_context_init = on_context_init; + logger->on_context_dispose = on_context_dispose; + + for (k = 0; conf->hosts[k]; k++) { + hconf = conf->hosts[k]; + for (i = 0; i < hconf->paths.size; i++) { + int j; + for (j = 0; j < hconf->paths.entries[i].handlers.size; j++) { + h2o_pathconf_t *pathconf = &hconf->paths.entries[i]; + h2o_vector_reserve(NULL, &pathconf->loggers, pathconf->loggers.size + 1); + pathconf->loggers.entries[pathconf->loggers.size++] = (void *)logger; + } + } + } +} + +h2o_status_handler_t durations_status_handler = { + {H2O_STRLIT("durations")}, durations_status_init, durations_status_per_thread, durations_status_final, +}; diff --git a/web/server/h2o/libh2o/lib/handler/status/events.c b/web/server/h2o/libh2o/lib/handler/status/events.c new file mode 100644 index 000000000..e6ed0b7c6 --- /dev/null +++ b/web/server/h2o/libh2o/lib/handler/status/events.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016 Fastly + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "h2o.h" +#include <inttypes.h> + +struct st_events_status_ctx_t { + uint64_t emitted_status_errors[H2O_STATUS_ERROR_MAX]; + uint64_t h2_protocol_level_errors[H2O_HTTP2_ERROR_MAX]; + uint64_t h2_read_closed; + uint64_t h2_write_closed; + pthread_mutex_t mutex; +}; + +static void events_status_per_thread(void *priv, h2o_context_t *ctx) +{ + size_t i; + struct st_events_status_ctx_t *esc = priv; + + pthread_mutex_lock(&esc->mutex); + + for (i = 0; i < H2O_STATUS_ERROR_MAX; i++) { + esc->emitted_status_errors[i] += ctx->emitted_error_status[i]; + } + for (i = 0; i < H2O_HTTP2_ERROR_MAX; i++) { + esc->h2_protocol_level_errors[i] += ctx->http2.events.protocol_level_errors[i]; + } + esc->h2_read_closed += ctx->http2.events.read_closed; + esc->h2_write_closed += ctx->http2.events.write_closed; + + pthread_mutex_unlock(&esc->mutex); +} + +static void *events_status_init(void) +{ + struct st_events_status_ctx_t *ret; + + ret = h2o_mem_alloc(sizeof(*ret)); + memset(ret, 0, sizeof(*ret)); + pthread_mutex_init(&ret->mutex, NULL); + + return ret; +} + +static h2o_iovec_t events_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) +{ + struct st_events_status_ctx_t *esc = priv; + h2o_iovec_t ret; + +#define H1_AGG_ERR(status_) esc->emitted_status_errors[H2O_STATUS_ERROR_##status_] +#define H2_AGG_ERR(err_) esc->h2_protocol_level_errors[-H2O_HTTP2_ERROR_##err_] +#define BUFSIZE (2 * 1024) + ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE); + ret.len = snprintf(ret.base, BUFSIZE, ",\n" + " \"status-errors.400\": %" PRIu64 ",\n" + " \"status-errors.403\": %" PRIu64 ",\n" + " \"status-errors.404\": %" PRIu64 ",\n" + " \"status-errors.405\": %" PRIu64 ",\n" + " \"status-errors.416\": %" PRIu64 ",\n" + " \"status-errors.417\": %" PRIu64 ",\n" + " \"status-errors.500\": %" PRIu64 ",\n" + " \"status-errors.502\": %" PRIu64 ",\n" + " \"status-errors.503\": %" PRIu64 ",\n" + " \"http2-errors.protocol\": %" PRIu64 ", \n" + " \"http2-errors.internal\": %" PRIu64 ", \n" + " \"http2-errors.flow-control\": %" PRIu64 ", \n" + " \"http2-errors.settings-timeout\": %" PRIu64 ", \n" + " \"http2-errors.stream-closed\": %" PRIu64 ", \n" + " \"http2-errors.frame-size\": %" PRIu64 ", \n" + " \"http2-errors.refused-stream\": %" PRIu64 ", \n" + " \"http2-errors.cancel\": %" PRIu64 ", \n" + " \"http2-errors.compression\": %" PRIu64 ", \n" + " \"http2-errors.connect\": %" PRIu64 ", \n" + " \"http2-errors.enhance-your-calm\": %" PRIu64 ", \n" + " \"http2-errors.inadequate-security\": %" PRIu64 ", \n" + " \"http2.read-closed\": %" PRIu64 ", \n" + " \"http2.write-closed\": %" PRIu64 "\n", + H1_AGG_ERR(400), H1_AGG_ERR(403), H1_AGG_ERR(404), H1_AGG_ERR(405), H1_AGG_ERR(416), H1_AGG_ERR(417), + H1_AGG_ERR(500), H1_AGG_ERR(502), H1_AGG_ERR(503), H2_AGG_ERR(PROTOCOL), H2_AGG_ERR(INTERNAL), + H2_AGG_ERR(FLOW_CONTROL), H2_AGG_ERR(SETTINGS_TIMEOUT), H2_AGG_ERR(STREAM_CLOSED), H2_AGG_ERR(FRAME_SIZE), + H2_AGG_ERR(REFUSED_STREAM), H2_AGG_ERR(CANCEL), H2_AGG_ERR(COMPRESSION), H2_AGG_ERR(CONNECT), + H2_AGG_ERR(ENHANCE_YOUR_CALM), H2_AGG_ERR(INADEQUATE_SECURITY), esc->h2_read_closed, esc->h2_write_closed); + pthread_mutex_destroy(&esc->mutex); + free(esc); + return ret; +#undef BUFSIZE +#undef H1_AGG_ERR +#undef H2_AGG_ERR +} + +h2o_status_handler_t events_status_handler = { + {H2O_STRLIT("events")}, events_status_init, events_status_per_thread, events_status_final, +}; diff --git a/web/server/h2o/libh2o/lib/handler/status/requests.c b/web/server/h2o/libh2o/lib/handler/status/requests.c new file mode 100644 index 000000000..4854e4a1f --- /dev/null +++ b/web/server/h2o/libh2o/lib/handler/status/requests.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "h2o.h" + +struct st_requests_status_ctx_t { + h2o_logconf_t *logconf; + h2o_iovec_t req_data; + pthread_mutex_t mutex; +}; + +struct st_collect_req_status_cbdata_t { + h2o_logconf_t *logconf; + h2o_buffer_t *buffer; +}; + +static int collect_req_status(h2o_req_t *req, void *_cbdata) +{ + struct st_collect_req_status_cbdata_t *cbdata = _cbdata; + + /* collect log */ + char buf[4096]; + size_t len = sizeof(buf); + char *logline = h2o_log_request(cbdata->logconf, req, &len, buf); + assert(len != 0); + --len; /* omit trailing LF */ + + /* append to buffer */ + h2o_buffer_reserve(&cbdata->buffer, len + 3); + memcpy(cbdata->buffer->bytes + cbdata->buffer->size, logline, len); + cbdata->buffer->size += len; + + if (logline != buf) + free(logline); + + return 0; +} + +static void requests_status_per_thread(void *priv, h2o_context_t *ctx) +{ + struct st_requests_status_ctx_t *rsc = priv; + struct st_collect_req_status_cbdata_t cbdata = {rsc->logconf}; + + /* we encountered an error at init() time, return early */ + if (rsc->logconf == NULL) + return; + + h2o_buffer_init(&cbdata.buffer, &h2o_socket_buffer_prototype); + ctx->globalconf->http1.callbacks.foreach_request(ctx, collect_req_status, &cbdata); + ctx->globalconf->http2.callbacks.foreach_request(ctx, collect_req_status, &cbdata); + + /* concat JSON elements */ + if (cbdata.buffer->size != 0) { + pthread_mutex_lock(&rsc->mutex); + if (rsc->req_data.len == 0) + h2o_buffer_consume(&cbdata.buffer, 1); /* skip preceeding comma */ + rsc->req_data.base = h2o_mem_realloc(rsc->req_data.base, rsc->req_data.len + cbdata.buffer->size); + memcpy(rsc->req_data.base + rsc->req_data.len, cbdata.buffer->bytes, cbdata.buffer->size); + rsc->req_data.len += cbdata.buffer->size; + pthread_mutex_unlock(&rsc->mutex); + } + + h2o_buffer_dispose(&cbdata.buffer); +} + +static void *requests_status_init(void) +{ + struct st_requests_status_ctx_t *rsc = h2o_mem_alloc(sizeof(*rsc)); + char errbuf[256]; + +#define ELEMENT(key, expr) "\"" key "\": \"" expr "\"" +#define X_ELEMENT(id) ELEMENT(id, "%{" id "}x") +#define SEPARATOR ", " + const char *fmt = ",\n {" + /* combined_log */ + ELEMENT("host", "%h") SEPARATOR ELEMENT("user", "%u") SEPARATOR ELEMENT("at", "%{%Y%m%dT%H%M%S}t.%{usec_frac}t%{%z}t") + SEPARATOR ELEMENT("method", "%m") SEPARATOR ELEMENT("path", "%U") SEPARATOR ELEMENT("query", "%q") + SEPARATOR ELEMENT("protocol", "%H") SEPARATOR ELEMENT("referer", "%{Referer}i") + SEPARATOR ELEMENT("user-agent", "%{User-agent}i") SEPARATOR + /* time */ + X_ELEMENT("connect-time") SEPARATOR X_ELEMENT("request-header-time") SEPARATOR X_ELEMENT("request-body-time") + SEPARATOR X_ELEMENT("request-total-time") SEPARATOR X_ELEMENT("process-time") SEPARATOR X_ELEMENT("response-time") + SEPARATOR + /* connection */ + X_ELEMENT("connection-id") SEPARATOR X_ELEMENT("ssl.protocol-version") SEPARATOR X_ELEMENT("ssl.session-reused") + SEPARATOR X_ELEMENT("ssl.cipher") SEPARATOR X_ELEMENT("ssl.cipher-bits") SEPARATOR X_ELEMENT("ssl.session-ticket") + SEPARATOR + /* http1 */ + X_ELEMENT("http1.request-index") SEPARATOR + /* http2 */ + X_ELEMENT("http2.stream-id") SEPARATOR X_ELEMENT("http2.priority.received.exclusive") + SEPARATOR X_ELEMENT("http2.priority.received.parent") SEPARATOR X_ELEMENT("http2.priority.received.weight") + SEPARATOR X_ELEMENT("http2.priority.actual.parent") SEPARATOR X_ELEMENT("http2.priority.actual.weight") SEPARATOR + /* misc */ + ELEMENT("authority", "%V") + /* end */ + "}"; +#undef ELEMENT +#undef X_ELEMENT +#undef SEPARATOR + + /* compile logconf */ + if ((rsc->logconf = h2o_logconf_compile(fmt, H2O_LOGCONF_ESCAPE_JSON, errbuf)) == NULL) + /* log format compilation error is an internal logic flaw, therefore we need not send the details to the client */ + fprintf(stderr, "[lib/handler/status/requests.c] failed to compile log format: %s", errbuf); + + rsc->req_data = (h2o_iovec_t){NULL}; + pthread_mutex_init(&rsc->mutex, NULL); + + return rsc; +} + +static h2o_iovec_t requests_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) +{ + h2o_iovec_t ret = {NULL}; + struct st_requests_status_ctx_t *rsc = priv; + + if (rsc->logconf != NULL) { + ret = h2o_concat(&req->pool, h2o_iovec_init(H2O_STRLIT(",\n \"requests\": [")), rsc->req_data, + h2o_iovec_init(H2O_STRLIT("\n ]"))); + h2o_logconf_dispose(rsc->logconf); + } + free(rsc->req_data.base); + pthread_mutex_destroy(&rsc->mutex); + + free(rsc); + return ret; +} + +h2o_status_handler_t requests_status_handler = { + {H2O_STRLIT("requests")}, requests_status_init, requests_status_per_thread, requests_status_final, +}; |