Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <mtg@gnu.org>2021-03-01 21:56:30 -0500
committerMike Gerwitz <mtg@gnu.org>2021-03-01 21:56:30 -0500
commit9c7961bb5c5f7a5a05d8cc981f22daffedd264c4 (patch)
treebd835002e532fc63d098f101e9635f33d4dab9b6
parenta3e4bc22c6967f179160d78fc74f78873bb56d69 (diff)
downloadpromscripts-9c7961bb5c5f7a5a05d8cc981f22daffedd264c4.tar.gz
promscripts-9c7961bb5c5f7a5a05d8cc981f22daffedd264c4.tar.bz2
promscripts-9c7961bb5c5f7a5a05d8cc981f22daffedd264c4.zip
prom.awk: New basic library
This introduces the beginning of a very basic library for generating Prometheus metrics with GNU Awk. It is not intended to be complete, and it'll evolve as I need it to. At the moment, there are no tests, because manual verification is trivial and I'm not yet sure how far I'll be going with this. If it evolves much further, though, I'll be adding a test suite.
-rw-r--r--printer/epson/et-2720/ink.awk15
-rw-r--r--printer/epson/et-2720/usage.awk19
-rw-r--r--prom.awk121
3 files changed, 141 insertions, 14 deletions
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: <img class='color' src='../../IMAGE/Ink_K.PNG' height='23' style=''>
/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 <http://www.gnu.org/licenses/>.
+#
+# 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
+}