#include <haproxy/proxy.h>
#include <haproxy/sample.h>
#include <haproxy/server.h>
-#include <haproxy/stats-t.h>
+#include <haproxy/stats.h>
#include <haproxy/stream_interface.h>
#include <haproxy/task.h>
#include <haproxy/tcp_rules.h>
static unsigned int resolution_uuid = 1;
unsigned int dns_failed_resolutions = 0;
+enum {
+ DNS_STAT_ID,
+ DNS_STAT_SND_ERROR,
+ DNS_STAT_VALID,
+ DNS_STAT_UPDATE,
+ DNS_STAT_CNAME,
+ DNS_STAT_CNAME_ERROR,
+ DNS_STAT_ANY_ERR,
+ DNS_STAT_NX,
+ DNS_STAT_TIMEOUT,
+ DNS_STAT_REFUSED,
+ DNS_STAT_OTHER,
+ DNS_STAT_INVALID,
+ DNS_STAT_TOO_BIG,
+ DNS_STAT_TRUNCATED,
+ DNS_STAT_OUTDATED,
+ DNS_STAT_END,
+};
+
+static struct name_desc dns_stats[] = {
+ [DNS_STAT_ID] = { .name = "id", .desc = "ID" },
+ [DNS_STAT_SND_ERROR] = { .name = "send_error", .desc = "Send error" },
+ [DNS_STAT_VALID] = { .name = "valid", .desc = "Valid" },
+ [DNS_STAT_UPDATE] = { .name = "update", .desc = "Update" },
+ [DNS_STAT_CNAME] = { .name = "cname", .desc = "CNAME" },
+ [DNS_STAT_CNAME_ERROR] = { .name = "cname_error", .desc = "CNAME error" },
+ [DNS_STAT_ANY_ERR] = { .name = "any_err", .desc = "Any errors" },
+ [DNS_STAT_NX] = { .name = "nx", .desc = "NX" },
+ [DNS_STAT_TIMEOUT] = { .name = "timeout", .desc = "Timeout" },
+ [DNS_STAT_REFUSED] = { .name = "refused", .desc = "Refused" },
+ [DNS_STAT_OTHER] = { .name = "other", .desc = "Other" },
+ [DNS_STAT_INVALID] = { .name = "invalid", .desc = "Invalid" },
+ [DNS_STAT_TOO_BIG] = { .name = "too_big", .desc = "Too big" },
+ [DNS_STAT_TRUNCATED] = { .name = "truncated", .desc = "Truncated" },
+ [DNS_STAT_OUTDATED] = { .name = "outdated", .desc = "Outdated" },
+};
+
+static struct dns_counters dns_counters;
+
+static void dns_fill_stats(void *d, struct field *stats)
+{
+ struct dns_counters *counters = d;
+ stats[DNS_STAT_ID] = mkf_str(FO_CONFIG, counters->id);
+ stats[DNS_STAT_SND_ERROR] = mkf_u64(FN_GAUGE, counters->snd_error);
+ stats[DNS_STAT_VALID] = mkf_u64(FN_GAUGE, counters->valid);
+ stats[DNS_STAT_UPDATE] = mkf_u64(FN_GAUGE, counters->update);
+ stats[DNS_STAT_CNAME] = mkf_u64(FN_GAUGE, counters->cname);
+ stats[DNS_STAT_CNAME_ERROR] = mkf_u64(FN_GAUGE, counters->cname_error);
+ stats[DNS_STAT_ANY_ERR] = mkf_u64(FN_GAUGE, counters->any_err);
+ stats[DNS_STAT_NX] = mkf_u64(FN_GAUGE, counters->nx);
+ stats[DNS_STAT_TIMEOUT] = mkf_u64(FN_GAUGE, counters->timeout);
+ stats[DNS_STAT_REFUSED] = mkf_u64(FN_GAUGE, counters->refused);
+ stats[DNS_STAT_OTHER] = mkf_u64(FN_GAUGE, counters->other);
+ stats[DNS_STAT_INVALID] = mkf_u64(FN_GAUGE, counters->invalid);
+ stats[DNS_STAT_TOO_BIG] = mkf_u64(FN_GAUGE, counters->too_big);
+ stats[DNS_STAT_TRUNCATED] = mkf_u64(FN_GAUGE, counters->truncated);
+ stats[DNS_STAT_OUTDATED] = mkf_u64(FN_GAUGE, counters->outdated);
+}
+
+static struct stats_module dns_stats_module = {
+ .name = "dns",
+ .domain_flags = STATS_DOMAIN_DNS << STATS_DOMAIN,
+ .fill_stats = dns_fill_stats,
+ .stats = dns_stats,
+ .stats_count = DNS_STAT_END,
+ .counters = &dns_counters,
+ .counters_size = sizeof(dns_counters),
+ .clearable = 0,
+};
+
+INITCALL1(STG_REGISTER, stats_register_module, &dns_stats_module);
+
/* Returns a pointer to the resolvers matching the id <id>. NULL is returned if
* no match is found.
*/
ret = send(fd, trash.area, len, 0);
if (ret == len) {
- ns->counters.sent++;
+ ns->counters->sent++;
resolution->nb_queries++;
continue;
}
}
snd_error:
- ns->counters.snd_error++;
+ ns->counters->snd_error++;
resolution->nb_queries++;
}
/* message too big */
if (buflen > resolvers->accepted_payload_size) {
- ns->counters.too_big++;
+ ns->counters->too_big++;
continue;
}
/* read the query id from the packet (16 bits) */
if (buf + 2 > bufend) {
- ns->counters.invalid++;
+ ns->counters->invalid++;
continue;
}
query_id = dns_response_get_query_id(buf);
eb = eb32_lookup(&resolvers->query_ids, query_id);
if (eb == NULL) {
/* unknown query id means an outdated response and can be safely ignored */
- ns->counters.outdated++;
+ ns->counters->outdated++;
continue;
}
case DNS_RESP_QUERY_COUNT_ERROR:
case DNS_RESP_WRONG_NAME:
res->status = RSLV_STATUS_INVALID;
- ns->counters.invalid++;
+ ns->counters->invalid++;
break;
case DNS_RESP_NX_DOMAIN:
res->status = RSLV_STATUS_NX;
- ns->counters.nx++;
+ ns->counters->nx++;
break;
case DNS_RESP_REFUSED:
res->status = RSLV_STATUS_REFUSED;
- ns->counters.refused++;
+ ns->counters->refused++;
break;
case DNS_RESP_ANCOUNT_ZERO:
res->status = RSLV_STATUS_OTHER;
- ns->counters.any_err++;
+ ns->counters->any_err++;
break;
case DNS_RESP_CNAME_ERROR:
res->status = RSLV_STATUS_OTHER;
- ns->counters.cname_error++;
+ ns->counters->cname_error++;
break;
case DNS_RESP_TRUNCATED:
res->status = RSLV_STATUS_OTHER;
- ns->counters.truncated++;
+ ns->counters->truncated++;
break;
case DNS_RESP_NO_EXPECTED_RECORD:
case DNS_RESP_ERROR:
case DNS_RESP_INTERNAL:
res->status = RSLV_STATUS_OTHER;
- ns->counters.other++;
+ ns->counters->other++;
break;
}
query = LIST_NEXT(&res->response.query_list, struct dns_query_item *, list);
if (query && dns_hostname_cmp(query->name, res->hostname_dn, res->hostname_dn_len) != 0) {
dns_resp = DNS_RESP_WRONG_NAME;
- ns->counters.other++;
+ ns->counters->other++;
goto report_res_error;
}
/* So the resolution succeeded */
res->status = RSLV_STATUS_VALID;
res->last_valid = now_ms;
- ns->counters.valid++;
+ ns->counters->valid++;
goto report_res_success;
report_res_error:
goto snd_error;
}
- ns->counters.sent++;
+ ns->counters->sent++;
+
res->nb_queries++;
continue;
snd_error:
- ns->counters.snd_error++;
+ ns->counters->snd_error++;
res->nb_queries++;
}
HA_SPIN_UNLOCK(DNS_LOCK, &resolvers->lock);
fd_delete(ns->dgram->t.sock.fd);
free(ns->dgram);
LIST_DEL(&ns->list);
+ EXTRA_COUNTERS_FREE(ns->extra_counters);
free(ns);
}
dgram->data = &resolve_dgram_cb;
dgram->t.sock.fd = -1;
ns->dgram = dgram;
+
+ /* Store the ns counters pointer */
+ if (ns->extra_counters) {
+ ns->counters = EXTRA_COUNTERS_GET(ns->extra_counters, &dns_stats_module);
+ ns->counters->id = ns->id;
+ }
}
/* Create the task associated to the resolvers section */
}
+static int stats_dump_dns_to_buffer(struct stream_interface *si,
+ struct dns_nameserver *ns,
+ struct field *stats, size_t stats_count,
+ struct list *stat_modules)
+{
+ struct appctx *appctx = __objt_appctx(si->end);
+ struct channel *rep = si_ic(si);
+ struct stats_module *mod;
+ size_t idx = 0;
+
+ memset(stats, 0, sizeof(struct field) * stats_count);
+
+ list_for_each_entry(mod, stat_modules, list) {
+ struct counters_node *counters = EXTRA_COUNTERS_GET(ns->extra_counters, mod);
+
+ mod->fill_stats(counters, stats + idx);
+ idx += mod->stats_count;
+ }
+
+ if (!stats_dump_one_line(stats, idx, appctx))
+ return 0;
+
+ if (!stats_putchk(rep, NULL, &trash))
+ goto full;
+
+ return 1;
+
+ full:
+ si_rx_room_rdy(si);
+ return 0;
+}
+
+/* Uses <appctx.ctx.stats.obj1> as a pointer to the current resolver and <obj2>
+ * as a pointer to the current nameserver.
+ */
+int stats_dump_dns(struct stream_interface *si,
+ struct field *stats, size_t stats_count,
+ struct list *stat_modules)
+{
+ struct appctx *appctx = __objt_appctx(si->end);
+ struct channel *rep = si_ic(si);
+ struct dns_resolvers *resolver = appctx->ctx.stats.obj1;
+ struct dns_nameserver *ns = appctx->ctx.stats.obj2;
+
+ if (!resolver)
+ resolver = LIST_NEXT(&dns_resolvers, struct dns_resolvers *, list);
+
+ /* dump resolvers */
+ list_for_each_entry_from(resolver, &dns_resolvers, list) {
+ appctx->ctx.stats.obj1 = resolver;
+
+ ns = appctx->ctx.stats.obj2 ?
+ appctx->ctx.stats.obj2 :
+ LIST_NEXT(&resolver->nameservers, struct dns_nameserver *, list);
+
+ list_for_each_entry_from(ns, &resolver->nameservers, list) {
+ appctx->ctx.stats.obj2 = ns;
+
+ if (buffer_almost_full(&rep->buf))
+ goto full;
+
+ if (!stats_dump_dns_to_buffer(si, ns,
+ stats, stats_count,
+ stat_modules)) {
+ return 0;
+ }
+ }
+
+ appctx->ctx.stats.obj2 = NULL;
+ }
+
+ return 1;
+
+ full:
+ si_rx_room_blk(si);
+ return 0;
+}
+
+void dns_stats_clear_counters(int clrall, struct list *stat_modules)
+{
+ struct dns_resolvers *resolvers;
+ struct dns_nameserver *ns;
+ struct stats_module *mod;
+ void *counters;
+
+ list_for_each_entry(mod, stat_modules, list) {
+ if (!mod->clearable && !clrall)
+ continue;
+
+ list_for_each_entry(resolvers, &dns_resolvers, list) {
+ list_for_each_entry(ns, &resolvers->nameservers, list) {
+ counters = EXTRA_COUNTERS_GET(ns->extra_counters, mod);
+ memcpy(counters, mod->counters, mod->counters_size);
+ }
+ }
+ }
+
+}
+
+int dns_allocate_counters(struct list *stat_modules)
+{
+ struct stats_module *mod;
+ struct dns_resolvers *resolvers;
+ struct dns_nameserver *ns;
+
+ list_for_each_entry(resolvers, &dns_resolvers, list) {
+ list_for_each_entry(ns, &resolvers->nameservers, list) {
+ EXTRA_COUNTERS_REGISTER(&ns->extra_counters, COUNTERS_DNS,
+ alloc_failed);
+
+ list_for_each_entry(mod, stat_modules, list) {
+ EXTRA_COUNTERS_ADD(mod,
+ ns->extra_counters,
+ mod->counters,
+ mod->counters_size);
+ }
+
+ EXTRA_COUNTERS_ALLOC(ns->extra_counters, alloc_failed);
+
+ list_for_each_entry(mod, stat_modules, list) {
+ memcpy(ns->extra_counters->data + mod->counters_off[ns->extra_counters->type],
+ mod->counters, mod->counters_size);
+
+ /* Store the ns counters pointer */
+ if (!strcmp(mod->name, "dns")) {
+ ns->counters = (struct dns_counters *)ns->extra_counters->data + mod->counters_off[COUNTERS_DNS];
+ ns->counters->id = ns->id;
+ }
+ }
+ }
+ }
+
+ return 1;
+
+alloc_failed:
+ return 0;
+}
+
/* if an arg is found, it sets the resolvers section pointer into cli.p0 */
static int cli_parse_stat_resolvers(char **args, char *payload, struct appctx *appctx, void *private)
{
chunk_appendf(&trash, "Resolvers section %s\n", resolvers->id);
list_for_each_entry(ns, &resolvers->nameservers, list) {
chunk_appendf(&trash, " nameserver %s:\n", ns->id);
- chunk_appendf(&trash, " sent: %lld\n", ns->counters.sent);
- chunk_appendf(&trash, " snd_error: %lld\n", ns->counters.snd_error);
- chunk_appendf(&trash, " valid: %lld\n", ns->counters.valid);
- chunk_appendf(&trash, " update: %lld\n", ns->counters.update);
- chunk_appendf(&trash, " cname: %lld\n", ns->counters.cname);
- chunk_appendf(&trash, " cname_error: %lld\n", ns->counters.cname_error);
- chunk_appendf(&trash, " any_err: %lld\n", ns->counters.any_err);
- chunk_appendf(&trash, " nx: %lld\n", ns->counters.nx);
- chunk_appendf(&trash, " timeout: %lld\n", ns->counters.timeout);
- chunk_appendf(&trash, " refused: %lld\n", ns->counters.refused);
- chunk_appendf(&trash, " other: %lld\n", ns->counters.other);
- chunk_appendf(&trash, " invalid: %lld\n", ns->counters.invalid);
- chunk_appendf(&trash, " too_big: %lld\n", ns->counters.too_big);
- chunk_appendf(&trash, " truncated: %lld\n", ns->counters.truncated);
- chunk_appendf(&trash, " outdated: %lld\n", ns->counters.outdated);
+ chunk_appendf(&trash, " sent: %lld\n", ns->counters->sent);
+ chunk_appendf(&trash, " snd_error: %lld\n", ns->counters->snd_error);
+ chunk_appendf(&trash, " valid: %lld\n", ns->counters->valid);
+ chunk_appendf(&trash, " update: %lld\n", ns->counters->update);
+ chunk_appendf(&trash, " cname: %lld\n", ns->counters->cname);
+ chunk_appendf(&trash, " cname_error: %lld\n", ns->counters->cname_error);
+ chunk_appendf(&trash, " any_err: %lld\n", ns->counters->any_err);
+ chunk_appendf(&trash, " nx: %lld\n", ns->counters->nx);
+ chunk_appendf(&trash, " timeout: %lld\n", ns->counters->timeout);
+ chunk_appendf(&trash, " refused: %lld\n", ns->counters->refused);
+ chunk_appendf(&trash, " other: %lld\n", ns->counters->other);
+ chunk_appendf(&trash, " invalid: %lld\n", ns->counters->invalid);
+ chunk_appendf(&trash, " too_big: %lld\n", ns->counters->too_big);
+ chunk_appendf(&trash, " truncated: %lld\n", ns->counters->truncated);
+ chunk_appendf(&trash, " outdated: %lld\n", ns->counters->outdated);
}
chunk_appendf(&trash, "\n");
}
/* list of all registered stats module */
static struct list stats_module_list[STATS_DOMAIN_COUNT] = {
LIST_HEAD_INIT(stats_module_list[STATS_DOMAIN_PROXY]),
+ LIST_HEAD_INIT(stats_module_list[STATS_DOMAIN_DNS]),
};
static inline uint8_t stats_get_domain(uint32_t domain)
stats[ST_F_PID].u.u32);
break;
+ case STATS_DOMAIN_DNS:
+ chunk_appendf(out, "D.%d.%s:", field,
+ stat_f[domain][field].name);
+ break;
+
default:
break;
}
obj_type, iid, sid, pos, name, pid);
}
+static void stats_print_dns_field_json(struct buffer *out,
+ const struct field *stat,
+ const char *name,
+ int pos)
+{
+ chunk_appendf(out,
+ "{"
+ "\"field\":{\"pos\":%d,\"name\":\"%s\"},",
+ pos, name);
+}
+
+
/* Dump all fields from <stats> into <out> using a typed "field:desc:type:value" format */
static int stats_dump_fields_json(struct buffer *out,
const struct field *stats, size_t stats_count,
stats[ST_F_IID].u.u32,
stats[ST_F_SID].u.u32,
stats[ST_F_PID].u.u32);
+ } else if (domain == STATS_DOMAIN_DNS) {
+ stats_print_dns_field_json(out, &stats[field],
+ stat_f[domain][field].name,
+ field);
}
if (old_len == out->data)
{
struct appctx *appctx = __objt_appctx(si->end);
struct channel *rep = si_ic(si);
+ enum stats_domain domain = appctx->ctx.stats.domain;
chunk_reset(&trash);
goto full;
}
- appctx->ctx.stats.obj1 = proxies_list;
+ if (domain == STATS_DOMAIN_PROXY)
+ appctx->ctx.stats.obj1 = proxies_list;
+
appctx->ctx.stats.px_st = STAT_PX_ST_INIT;
appctx->st2 = STAT_ST_LIST;
/* fall through */
case STAT_ST_LIST:
- /* dump proxies */
- if (!stats_dump_proxies(si, htx, uri))
- return 0;
+ switch (domain) {
+ case STATS_DOMAIN_DNS:
+ if (!stats_dump_dns(si, stat_l[domain],
+ stat_count[domain],
+ &stats_module_list[domain])) {
+ return 0;
+ }
+ break;
+
+ case STATS_DOMAIN_PROXY:
+ default:
+ /* dump proxies */
+ if (!stats_dump_proxies(si, htx, uri))
+ return 0;
+ break;
+ }
appctx->st2 = STAT_ST_END;
/* fall through */
}
}
+ dns_stats_clear_counters(clrall, &stats_module_list[STATS_DOMAIN_DNS]);
+
memset(activity, 0, sizeof(activity));
return 1;
}
if (!strcmp(args[arg], "domain")) {
++args;
- if (!strcmp(args[arg], "proxy"))
+ if (!strcmp(args[arg], "proxy")) {
++args;
- else
+ } else if (!strcmp(args[arg], "dns")) {
+ appctx->ctx.stats.domain = STATS_DOMAIN_DNS;
+ ++args;
+ } else {
return cli_err(appctx, "Invalid statistics domain.\n");
+ }
}
if (appctx->ctx.stats.domain == STATS_DOMAIN_PROXY
REGISTER_CONFIG_POSTPARSER("allocate-stats-px", allocate_stats_px_postcheck);
+static int allocate_stats_dns_postcheck(void)
+{
+ struct stats_module *mod;
+ size_t i = 0;
+ int err_code = 0;
+
+ stat_f[STATS_DOMAIN_DNS] = malloc(stat_count[STATS_DOMAIN_DNS] * sizeof(struct name_desc));
+ if (!stat_f[STATS_DOMAIN_DNS]) {
+ ha_alert("stats: cannot allocate all fields for dns statistics\n");
+ err_code |= ERR_ALERT | ERR_FATAL;
+ return err_code;
+ }
+
+ list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_DNS], list) {
+ memcpy(stat_f[STATS_DOMAIN_DNS] + i,
+ mod->stats,
+ mod->stats_count * sizeof(struct name_desc));
+ i += mod->stats_count;
+ }
+
+ if (!dns_allocate_counters(&stats_module_list[STATS_DOMAIN_DNS])) {
+ ha_alert("stats: cannot allocate all counters for dns statistics\n");
+ err_code |= ERR_ALERT | ERR_FATAL;
+ return err_code;
+ }
+
+ stat_l[STATS_DOMAIN_DNS] = malloc(stat_count[STATS_DOMAIN_DNS] * sizeof(struct field));
+ if (!stat_l[STATS_DOMAIN_DNS]) {
+ ha_alert("stats: cannot allocate a line for dns statistics\n");
+ err_code |= ERR_ALERT | ERR_FATAL;
+ return err_code;
+ }
+
+ return err_code;
+}
+
+REGISTER_CONFIG_POSTPARSER("allocate-stats-dns", allocate_stats_dns_postcheck);
+
/* register cli keywords */
static struct cli_kw_list cli_kws = {{ },{
{ { "clear", "counters", NULL }, "clear counters : clear max statistics counters (add 'all' for all counters)", cli_parse_clear_counters, NULL, NULL },