From 2a036ee30b845942da6532d98ff946e182bfcd95 Mon Sep 17 00:00:00 2001 From: Giacomo Licari Date: Thu, 2 Nov 2023 12:43:04 +0100 Subject: [PATCH 1/3] Parse hex values, add docker CI Signed-off-by: Giacomo Licari --- .github/workflows/publish.yml | 43 ++++++++++++++++++++++++++++++++++ Dockerfile | 44 ++++++++++++++++++++++++++++------- exporter/util.go | 7 ++++++ 3 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..f5994254 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,43 @@ +# Taken from https://docs.github.com/en/actions/publishing-packages/publishing-docker-images +name: Create and publish a Docker image + +on: + push: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + + # Sets the permissions granted to the GITHUB_TOKEN for the actions in this job. + permissions: + contents: read + packages: write + + steps: + - name: "Checkout repository" + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 58c9c4b8..85946aa2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,40 @@ -ARG ARCH="amd64" -ARG OS="linux" -FROM quay.io/prometheus/busybox-${OS}-${ARCH}:glibc -LABEL maintainer="The Prometheus Authors " +# ARG ARCH="arm64" +# ARG OS="linux" +# FROM quay.io/prometheus/busybox-${OS}-${ARCH}:glibc +# LABEL maintainer="The Prometheus Authors " -ARG ARCH="amd64" -ARG OS="linux" -COPY .build/${OS}-${ARCH}/json_exporter /bin/json_exporter +# ARG ARCH="arm64" +# ARG OS="linux" +# COPY .build/${OS}-${ARCH}/json_exporter /bin/json_exporter + +FROM golang:1.19 as builder + +# Create and change to the app directory. +WORKDIR /app + +# Retrieve application dependencies. +# This allows the container build to reuse cached dependencies. +# Expecting to copy go.mod and if present go.sum. +COPY go.* ./ +RUN go mod download + +# Copy local code to the container image. +COPY . ./ + +# Build the binary. +RUN go build -v -o json_exporter + +# Use the official Debian slim image for a lean production container. +# https://hub.docker.com/_/debian +# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds +FROM debian:buster-slim +RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates && \ + rm -rf /var/lib/apt/lists/* + +# Copy the binary to the production image from the builder stage. +COPY --from=builder /app/json_exporter /app/json_exporter EXPOSE 7979 USER nobody -ENTRYPOINT [ "/bin/json_exporter" ] +ENTRYPOINT [ "/app/json_exporter" ] diff --git a/exporter/util.go b/exporter/util.go index 8374ddce..eb492e27 100644 --- a/exporter/util.go +++ b/exporter/util.go @@ -40,6 +40,7 @@ func MakeMetricName(parts ...string) string { func SanitizeValue(s string) (float64, error) { var err error var value float64 + var hexValue int64 var resultErr string if value, err = strconv.ParseFloat(s, 64); err == nil { @@ -47,6 +48,12 @@ func SanitizeValue(s string) (float64, error) { } resultErr = fmt.Sprintf("%s", err) + // Check if value is an hex integer, e.g. 0x1d54c54 => 30755924 + if hexValue, err = strconv.ParseInt(s, 16, 64); err == nil { + return float64(hexValue), nil + } + resultErr = resultErr + "; " + fmt.Sprintf("%s", err) + if boolValue, err := strconv.ParseBool(s); err == nil { if boolValue { return 1.0, nil From 1b73e0bfb8b7ee3b9c646b2f5983828aa77ed618 Mon Sep 17 00:00:00 2001 From: Giacomo Licari Date: Thu, 2 Nov 2023 14:20:15 +0100 Subject: [PATCH 2/3] Add additional step to scrape hex data Signed-off-by: Giacomo Licari --- exporter/collector.go | 23 +++++++++++++++++------ exporter/util.go | 21 ++++++++++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/exporter/collector.go b/exporter/collector.go index 4effc10f..316c7c6d 100644 --- a/exporter/collector.go +++ b/exporter/collector.go @@ -57,19 +57,30 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { continue } - if floatValue, err := SanitizeValue(value); err == nil { - metric := prometheus.MustNewConstMetric( + floatValue, err := SanitizeValue(value) + if err == nil { + ch <- prometheus.MustNewConstMetric( m.Desc, m.ValueType, floatValue, extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., ) - ch <- timestampMetric(mc.Logger, m, mc.Data, metric) - } else { - level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.KeyJSONPath, "value", value, "err", err, "metric", m.Desc) - continue } + intValue, err := SanitizeHexIntValue(value) + if err == nil { + ch <- prometheus.MustNewConstMetric( + m.Desc, + prometheus.UntypedValue, + float64(intValue), + extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., + ) + } + + + level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.KeyJSONPath, "value", value, "err", err, "metric", m.Desc) + continue + case config.ObjectScrape: values, err := extractValue(mc.Logger, mc.Data, m.KeyJSONPath, true) if err != nil { diff --git a/exporter/util.go b/exporter/util.go index eb492e27..2bb42f4e 100644 --- a/exporter/util.go +++ b/exporter/util.go @@ -40,7 +40,6 @@ func MakeMetricName(parts ...string) string { func SanitizeValue(s string) (float64, error) { var err error var value float64 - var hexValue int64 var resultErr string if value, err = strconv.ParseFloat(s, 64); err == nil { @@ -48,12 +47,6 @@ func SanitizeValue(s string) (float64, error) { } resultErr = fmt.Sprintf("%s", err) - // Check if value is an hex integer, e.g. 0x1d54c54 => 30755924 - if hexValue, err = strconv.ParseInt(s, 16, 64); err == nil { - return float64(hexValue), nil - } - resultErr = resultErr + "; " + fmt.Sprintf("%s", err) - if boolValue, err := strconv.ParseBool(s); err == nil { if boolValue { return 1.0, nil @@ -81,6 +74,20 @@ func SanitizeIntValue(s string) (int64, error) { return value, fmt.Errorf(resultErr) } +func SanitizeHexIntValue(s string) (int64, error) { + var err error + var value int64 + var resultErr string + + // Check if value is an hex integer, e.g. 0x1d54c54 => 30755924 + if value, err = strconv.ParseInt(s, 16, 64); err == nil { + return value, nil + } + resultErr = fmt.Sprintf("%s", err) + + return value, fmt.Errorf(resultErr) +} + func CreateMetricsList(c config.Module) ([]JSONMetric, error) { var ( metrics []JSONMetric From 56f3fefe970f5cf01b63d811c2220b4a699a63e7 Mon Sep 17 00:00:00 2001 From: Giacomo Licari Date: Thu, 2 Nov 2023 14:37:43 +0100 Subject: [PATCH 3/3] Fix parseInt, add tests Signed-off-by: Giacomo Licari --- exporter/collector.go | 44 ++++++++++++++++++++++++++----------------- exporter/util.go | 7 +++++-- exporter/util_test.go | 21 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/exporter/collector.go b/exporter/collector.go index 316c7c6d..080f5dda 100644 --- a/exporter/collector.go +++ b/exporter/collector.go @@ -65,22 +65,21 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { floatValue, extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., ) + } else { + intValue, err := SanitizeHexIntValue(value) + if err == nil { + ch <- prometheus.MustNewConstMetric( + m.Desc, + m.ValueType, + float64(intValue), + extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., + ) + } else { + level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.KeyJSONPath, "value", value, "err", err, "metric", m.Desc) + continue + } } - intValue, err := SanitizeHexIntValue(value) - if err == nil { - ch <- prometheus.MustNewConstMetric( - m.Desc, - prometheus.UntypedValue, - float64(intValue), - extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., - ) - } - - - level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.KeyJSONPath, "value", value, "err", err, "metric", m.Desc) - continue - case config.ObjectScrape: values, err := extractValue(mc.Logger, mc.Data, m.KeyJSONPath, true) if err != nil { @@ -102,7 +101,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { continue } - if floatValue, err := SanitizeValue(value); err == nil { + floatValue, err := SanitizeValue(value) + if err == nil { metric := prometheus.MustNewConstMetric( m.Desc, m.ValueType, @@ -111,8 +111,18 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { ) ch <- timestampMetric(mc.Logger, m, jdata, metric) } else { - level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.ValueJSONPath, "value", value, "err", err, "metric", m.Desc) - continue + intValue, err := SanitizeHexIntValue(value) + if err == nil { + ch <- prometheus.MustNewConstMetric( + m.Desc, + m.ValueType, + float64(intValue), + extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., + ) + } else { + level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.ValueJSONPath, "value", value, "err", err, "metric", m.Desc) + continue + } } } } else { diff --git a/exporter/util.go b/exporter/util.go index 2bb42f4e..32571a56 100644 --- a/exporter/util.go +++ b/exporter/util.go @@ -79,8 +79,11 @@ func SanitizeHexIntValue(s string) (int64, error) { var value int64 var resultErr string - // Check if value is an hex integer, e.g. 0x1d54c54 => 30755924 - if value, err = strconv.ParseInt(s, 16, 64); err == nil { + // remove 0x suffix if found in the input string + cleaned := strings.Replace(s, "0x", "", -1) + cleaned = strings.Replace(cleaned, "\"", "", -1) + + if value, err = strconv.ParseInt(cleaned, 16, 64); err == nil { return value, nil } resultErr = fmt.Sprintf("%s", err) diff --git a/exporter/util_test.go b/exporter/util_test.go index 90392849..3688d193 100644 --- a/exporter/util_test.go +++ b/exporter/util_test.go @@ -48,6 +48,27 @@ func TestSanitizeValue(t *testing.T) { } } +func TestSanitizeValueHex(t *testing.T) { + tests := []struct { + Input string + ExpectedOutput int64 + ShouldSucceed bool + }{ + {"0x1d55195", 30757269, true}, + {"\"0x1d55195\"", 30757269, true}, + } + + for i, test := range tests { + actualOutput, err := SanitizeHexIntValue(test.Input) + if err != nil && test.ShouldSucceed { + t.Fatalf("Value snitization test %d failed with an unexpected error.\nINPUT:\n%q\nERR:\n%s", i, test.Input, err) + } + if test.ShouldSucceed && actualOutput != test.ExpectedOutput { + t.Fatalf("Value sanitization test %d fails unexpectedly.\nGOT:\n%d\nEXPECTED:\n%d", i, actualOutput, test.ExpectedOutput) + } + } +} + func TestSanitizeValueNaN(t *testing.T) { actualOutput, err := SanitizeValue("") if err != nil {