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
+}