diff --git a/printer/epson/et-2720/ink.awk b/printer/epson/et-2720/ink.awk index 17d5aad..a4782a0 100644 --- a/printer/epson/et-2720/ink.awk +++ b/printer/epson/et-2720/ink.awk @@ -30,25 +30,28 @@ # container is 50px, so we take the height of the image and simply double it # to get the ink level of that tank. +@include "../../../prom.awk" + BEGIN { FS = "[ _.']" count = 0 expected_count = 4 - print "# HELP printer_ink_level Percentage of each ink tank remaining." - print "# TYPE printer_ink_level gauge" + prom_declare_gauge("printer_ink_level", + "Percentage of each ink tank remaining.") + prom_declare_gauge("printer_ink_level_success", + "Whether scraping was successful.") } # ex: /IMAGE\/Ink_.\.PNG/ { - printf "printer_ink_level{color=\"%s\"} %d\n", $11, ($15 * 2) + L["color"] = $11 + prom_metric("printer_ink_level", ($15 * 2), L) count++ } END { - print "# HELP printer_ink_level_success Whether scraping was successful." - print "# TYPE printer_ink_level_success gauge" - printf "printer_ink_level_success{} %d\n", (count == expected_count) + prom_metric("printer_ink_level_success", (count == expected_count)) } diff --git a/printer/epson/et-2720/usage.awk b/printer/epson/et-2720/usage.awk index ae23ec9..391a02b 100644 --- a/printer/epson/et-2720/usage.awk +++ b/printer/epson/et-2720/usage.awk @@ -25,6 +25,8 @@ # Consequently, you may find that this script does not work for your # printer, even if it is the same model. +@include "../../../prom.awk" + # Rather than parsing start and end tags, we'll set the field separator to # any opening angled bracket and the record separator to any closing. This # leaves us with the node names, attributes, and cdata. @@ -35,8 +37,10 @@ BEGIN { count = 0 expected_count = 8 - print "# HELP printer_pages_count Number of pages." - print "# TYPE printer_pages_count counter" + prom_declare_counter("printer_pages_count", + "Number of pages."); + prom_declare_gauge("printer_pages_count_success", + "Whether scraping was successful.") } # Simplify matching. @@ -73,14 +77,13 @@ BEGIN { # Assume that the next line beginning with a number is the value. while (!($1 ~ /^[0-9]/)) getline - printf "printer_pages_count{color=\"%s\", source=\"%s\"} %d\n", \ - color, source, $1 + L["color"] = color + L["source"] = source + prom_metric("printer_pages_count", $1, L) + count++ } END { - print "# HELP printer_pages_count_success Whether scraping was successful." - print "# TYPE printer_pages_count_success gauge" - printf "printer_pages_count_success{} %d\n", (count == expected_count) + prom_metric("printer_pages_count_success", (count == expected_count)) } - diff --git a/prom.awk b/prom.awk new file mode 100644 index 0000000..6a5ef39 --- /dev/null +++ b/prom.awk @@ -0,0 +1,121 @@ +# 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 +}