# Library for generation of Prometheus metrics in GNU Awk # # Copyright (C) 2021 Mike Gerwitz # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # This library is not written for my particular uses (and style) and is not # comprehensive. # Declare a counter of the name NAME with the help string HELP. function prom_declare_counter(name, help) { _prom_declare_metric(name, "counter", help) } # Declare a gauge of the name NAME with the help string HELP. function prom_declare_gauge(name, help) { _prom_declare_metric(name, "gauge", help) } # Declare a metric of the name NAME with the type TYPE and the help string # HELP. # # Prometheus metric names (NAME) must follow the pattern # `[a-zA-Z_:][a-zA-Z0-9_:]*`, but colons are reserved for user-defined # metrics (e.g. rules), and should not be used by exporters (like us). function _prom_declare_metric(name, type, help) { # Omit colons (see docblock) when verifying name. if (!(name ~ /^[a-zA_Z_][a-zA-Z0-9_]*/)) { _prom_fatal("invalid metric name: " name) } PROM_TYPE[name] = type PROM_HELP[name] = help # Whether we've output this metric yet (so we can determine whether to # output the HELP and TYPE lines) PROM_OUT[name] = 0 } # Output a metric NAME with the numeric value VALUE and labels specified by # the associative array LABELS. # # The metric must have previously been declared to ensure that its `HELP` # and `TYPE` lines will be properly output the first time this function is # invoked for that NAME. # # The keys of LABELS ought to be hard-coded to ensure that they are both # valid and bounded. function prom_metric(name, value, labels) { if (PROM_OUT[name] != 1) { printf "# HELP %s %s\n", name, PROM_HELP[name] printf "# TYPE %s %s\n", name, PROM_TYPE[name] PROM_OUT[name] = 1 } printf "%s{%s} %d\n", name, _prom_labels(labels), value } # Serialize and escape associative array of labels LABELS. # # Prometheus label names must be of the form `[a-zA-Z_][a-zA-Z0-9_]*`, with # a leading `__` reserved for internal use (though this does not bother # checking for leading underscores). Labels not matching that pattern will # be rejected in error rather than reformatting, since it surely represents # a bug and I don't need my database polluted with garbage values. function _prom_labels(labels, _delim, _result) { _delim="" for (name in labels) { _result = _result sprintf("%s%s=\"%s\"", _delim, _prom_assert_valid_label(name), _prom_label_escape(labels[name])) _delim=", " } return _result } # Exit with non-zero status and error message if label name NAME is not # valid (see `_prom_labels`). function _prom_assert_valid_label(name) { if (!(name ~ /^[a-zA-Z_][a-zA-Z0-9_]*/)) { _prom_fatal("invalid label name: " name) } return name } # Escape double quotes in label value VALUE and return the result. function _prom_label_escape(value) { return gensub(/"/, "\\\\\"", "g", value) } # Display error message MSG and exit with non-zero status STATUS # (defaultĀ 1). function _prom_fatal(msg, status) { printf "error: prom: %s\n", msg > "/dev/stderr" exit status ? status : 1 }