summaryrefslogtreecommitdiffstats
path: root/web/server/h2o/libh2o/lib/handler/status
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--web/server/h2o/libh2o/lib/handler/status.c270
-rw-r--r--web/server/h2o/libh2o/lib/handler/status/durations.c207
-rw-r--r--web/server/h2o/libh2o/lib/handler/status/events.c112
-rw-r--r--web/server/h2o/libh2o/lib/handler/status/requests.c151
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 00000000..93befed3
--- /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 00000000..f011107b
--- /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 00000000..e6ed0b7c
--- /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 00000000..4854e4a1
--- /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,
+};