}
/* ---------------------------------------------------------------------------
- * Format hashtable into HTML.
+ * Get an array of pointers to all the buckets in the hashtable,
+ * or NULL if the hashtable is NULL or empty.
+ * The returned pointer should be free'd by the caller.
*/
-static void
-format_table(struct str *buf, struct hashtable *ht, unsigned int start,
- const enum sort_dir sort, const int full)
+const struct bucket **
+hashtable_list_buckets(struct hashtable *ht)
{
const struct bucket **table;
- unsigned int i, pos, end;
- int alt = 0;
+ unsigned int i, pos;
if ((ht == NULL) || (ht->count == 0)) {
- str_append(buf, "<p>The table is empty.</p>\n");
- return;
+ return NULL;
}
/* Fill table with pointers to buckets in hashtable. */
}
}
assert(pos == ht->count);
+ return table;
+}
+
+typedef void (hashtable_foreach_func_t)(const struct bucket *, const void *);
+
+/* ---------------------------------------------------------------------------
+ * Loop over all buckets in the given hashtable, calling the supplied function
+ * with each bucket and the supplied user_data.
+ */
+static void
+hashtable_foreach(struct hashtable *ht,
+ hashtable_foreach_func_t *hashtable_foreach_func,
+ const void *user_data)
+{
+ const struct bucket **table;
+ unsigned int i;
+
+ table = hashtable_list_buckets(ht);
+ if (table == NULL)
+ return;
+
+ for (i = 0; i<ht->count; i++) {
+ const struct bucket *b = table[i];
+ (*hashtable_foreach_func)(b, user_data);
+ }
+ free(table);
+}
+
+/* ---------------------------------------------------------------------------
+ * Format hashtable into HTML.
+ */
+static void
+format_table(struct str *buf, struct hashtable *ht, unsigned int start,
+ const enum sort_dir sort, const int full)
+{
+ const struct bucket **table;
+ unsigned int i, end;
+ int alt = 0;
+
+ table = hashtable_list_buckets(ht);
+
+ if (table == NULL) {
+ str_append(buf, "<p>The table is empty.</p>\n");
+ return;
+ }
if (full) {
/* full report overrides start and end */
export_tag_host_ver3[] = {'H', 'S', 'T', 0x03},
export_tag_host_ver4[] = {'H', 'S', 'T', 0x04};
+static void text_metrics_counter(struct str *buf, const char *metric, const char *type, const char *help);
+static void text_metrics_format_host(const struct bucket *b, const void *user_data);
+
+/* ---------------------------------------------------------------------------
+ * Web interface: export stats in Prometheus text format on /metrics
+ */
+struct str *
+text_metrics()
+{
+ struct str *buf = str_make();
+
+ text_metrics_counter(buf,
+ "host_bytes_total",
+ "counter",
+ "Total number of network bytes by host and direction.");
+ hashtable_foreach(hosts_db, &text_metrics_format_host, (void *)buf);
+
+ return buf;
+}
+
+static void
+text_metrics_counter(struct str *buf,
+ const char *metric,
+ const char *type,
+ const char *help)
+{
+ str_appendf(buf, "# HELP %s %s\n", metric, help);
+ str_appendf(buf, "# TYPE %s %s\n", metric, type);
+}
+
+static void
+text_metrics_format_host_key(struct str *buf, const struct bucket *b) {
+ const char *ip = addr_to_str(&(b->u.host.addr));
+
+ str_appendf(buf,
+ "host_bytes_total{interface=\"%s\",ip=\"%s\"",
+ title_interfaces, ip);
+
+ if (hosts_db_show_macs)
+ str_appendf(buf, ",mac=\"%x:%x:%x:%x:%x:%x\"",
+ b->u.host.mac_addr[0],
+ b->u.host.mac_addr[1],
+ b->u.host.mac_addr[2],
+ b->u.host.mac_addr[3],
+ b->u.host.mac_addr[4],
+ b->u.host.mac_addr[5]);
+}
+
+static void
+text_metrics_format_host(const struct bucket *b,
+ const void *user_data)
+{
+ struct str *buf = (struct str *)user_data;
+
+ text_metrics_format_host_key(buf, b);
+ str_appendf(buf, ",dir=\"in\"} %qu\n", (qu)b->in);
+
+ text_metrics_format_host_key(buf, b);
+ str_appendf(buf, ",dir=\"out\"} %qu\n", (qu)b->out);
+}
+
/* ---------------------------------------------------------------------------
* Load a host's ip_proto table from a file.
* Returns 0 on failure, 1 on success.
static const char mime_type_xml[] = "text/xml";
static const char mime_type_html[] = "text/html; charset=us-ascii";
+static const char mime_type_text_prometheus[] = "text/plain; version=0.0.4";
static const char mime_type_css[] = "text/css";
static const char mime_type_js[] = "text/javascript";
static const char mime_type_png[] = "image/png";
/* hack around Opera caching the XML */
conn->header_extra = "Pragma: no-cache\r\n";
}
+ else if (str_starts_with(safe_url, "/metrics")) {
+ struct str *buf = text_metrics();
+ str_extract(buf, &(conn->reply_length), &(conn->reply));
+ conn->mime_type = mime_type_text_prometheus;
+ }
else if (strcmp(safe_url, "/style.css") == 0)
static_style_css(conn);
else if (strcmp(safe_url, "/graph.js") == 0)