From 50f67d721671ae373428d1c6cca220c5b1bca96b Mon Sep 17 00:00:00 2001 From: Greg Look Date: Sat, 27 Oct 2018 19:02:23 -0700 Subject: [PATCH 01/12] Bump snapshot version. --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 1138be2..448cc52 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject mvxcvi/solanum "3.0.0" +(defproject mvxcvi/solanum "3.0.1-SNAPSHOT" :description "Local host monitoring daemon." :url "https://github.com/greglook/solanum" :license {:name "Public Domain" From b054d7bb8d5b29385d56042682d38a0f07a16a50 Mon Sep 17 00:00:00 2001 From: Greg Look Date: Sat, 27 Oct 2018 19:06:04 -0700 Subject: [PATCH 02/12] Update README. --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ff0e8a1..a2b10c1 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,9 @@ gem installs. ## Installation -Releases are published on Clojars and GitHub. To use the latest version with -Leiningen, add the following dependency to your project definition: - -[![Clojars Project](http://clojars.org/mvxcvi/solanum/latest-version.svg)](http://clojars.org/mvxcvi/solanum) - -To install the native binaries, simply place them on your path. +Releases are published on the [GitHub project](https://github.com/greglook/solanum/releases). +The native binaries are self-contained, so to install them simply place them on +your path. ## Metric Events From a279d0db28932026da9767fd771d7b9d667639d1 Mon Sep 17 00:00:00 2001 From: Greg Look Date: Sat, 3 Nov 2018 11:22:50 -0700 Subject: [PATCH 03/12] Add new source for checking HTTP endpoints. --- CHANGELOG.md | 4 +- project.clj | 2 + src/solanum/config.clj | 1 + src/solanum/source/core.clj | 9 +++ src/solanum/source/http.clj | 152 ++++++++++++++++++++++++++++++++++++ 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 src/solanum/source/http.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe9a18..68b5192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -... +### Added +- New HTTP source allows for checking URL endpoints and asserting that the + response meets certain properties. ## [3.0.0] - 2018-10-27 diff --git a/project.clj b/project.clj index 448cc52..2286128 100644 --- a/project.clj +++ b/project.clj @@ -9,9 +9,11 @@ :dependencies [[org.clojure/clojure "1.9.0"] + [org.clojure/data.json "0.2.6"] [org.clojure/tools.cli "0.4.1"] [org.clojure/tools.logging "0.4.1"] [ch.qos.logback/logback-classic "1.2.3"] + [clj-http-lite "0.3.0"] [org.yaml/snakeyaml "1.23"] [riemann-clojure-client "0.5.0"]] diff --git a/src/solanum/config.clj b/src/solanum/config.clj index 1d8db4e..cc4df2f 100644 --- a/src/solanum/config.clj +++ b/src/solanum/config.clj @@ -10,6 +10,7 @@ [solanum.source.cpu] [solanum.source.disk-space] [solanum.source.disk-stats] + [solanum.source.http] [solanum.source.load] [solanum.source.memory] [solanum.source.network] diff --git a/src/solanum/source/core.clj b/src/solanum/source/core.clj index 87b4025..d5a3ddc 100644 --- a/src/solanum/source/core.clj +++ b/src/solanum/source/core.clj @@ -25,6 +25,15 @@ ;; ## Event Helpers +(defn stopwatch + "Constructs a delayed value which will yield the number of milliseconds + elapsed since its construction when realized." + [] + (let [start (System/nanoTime)] + (delay + (/ (- (System/nanoTime) start) 1e6)))) + + (defn byte-str "Format a byte size into a human-friendly string representation." [size] diff --git a/src/solanum/source/http.clj b/src/solanum/source/http.clj new file mode 100644 index 0000000..9e65f22 --- /dev/null +++ b/src/solanum/source/http.clj @@ -0,0 +1,152 @@ +(ns solanum.source.http + "Metrics source that checks the availability of a local TCP port." + (:require + [clj-http.lite.client :as http] + [clojure.data.json :as json] + [clojure.edn :as edn] + [clojure.string :as str] + [clojure.tools.logging :as log] + [solanum.source.core :as source])) + + +(defn- parse-body + "Return a parsed response body if the content type is advertized as a known + data format. Returns nil otherwise." + [response] + (let [content-type (get-in response [:headers "content-type"])] + (cond + (str/starts-with? content-type "application/edn") + (edn/read-string (:body response)) + + (str/starts-with? content-type "application/json") + (json/read-str (:body response)) + + :else nil))) + + +(defn- acceptable-values + "Determine the acceptable set of values for a given check config." + [check] + (let [single (:value check) + multi (:values check)] + (if (some? single) + (set (cons single multi)) + (set multi)))) + + +(defn- check-response + "Test the response to an HTTP call to determine if it is healthy, based on + the configured check. Returns a tuple of the check state (true or false) and + a description fragment." + [response check] + (case (:type check) + :status + (let [acceptable (acceptable-values check)] + (if (contains? acceptable (:status response)) + [true (str "status " (:status response))] + [false (format "status %s is not in %s" + (:status response) + (str/join "/" (sort acceptable)))])) + + :pattern + (let [pattern (re-pattern (:pattern check))] + (if (re-seq pattern (:body response)) + [true (str "body matches " (pr-str pattern))] + [false (str "body does not match " (pr-str pattern))])) + + :data + (if-let [data @(:data response)] + (let [acceptable (acceptable-values check) + key-path (if (sequential? (:key check)) + (vec (:key check)) + [(:key check)]) + value (get-in data key-path)] + (if (contains? acceptable value) + [true (format "%s: %s" + (str/join "." key-path) + (pr-str value))] + [false (format "%s: %s is not in %s" + (str/join "." key-path) + (pr-str value) + (str/join "/" (sort acceptable)))])) + [false (format "Body content %s is not parseable for data check" + (get-in response [:headers "content-type"]))]) + + ; else + (log/error "Unknown HTTP response check type" (pr-str (:type check))))) + + +(defn- collect-fields + "Collect additional event attributes from the response data to include in the + health event." + [fields data] + (into {} + (keep + (fn collect + [[attr-key data-key]] + (let [key-path (if (sequential? data-key) + (vec data-key) + [data-key]) + value (get-in data key-path)] + (when (some? value) + [attr-key (str value)])))) + fields)) + + +(defrecord HTTPSource + [label url timeout response-checks record-fields] + + source/Source + + (collect-events + [this] + (let [elapsed (source/stopwatch)] + (try + (let [resp (http/get url {:throw-exceptions false + :socket-timeout 1000 + :conn-timeout timeout}) + data (delay (parse-body resp)) + checks (mapv (partial check-response (assoc resp :data data)) + response-checks) + healthy? (every? (comp true? first) checks)] + [{:service "http url time" + :label (or label url) + :metric @elapsed} + (merge + (when (seq record-fields) + (collect-fields record-fields @data)) + {:service "http url health" + :label (or label url) + :metric (if healthy? 1 0) + :state (if healthy? "ok" "critical") + :description (format "Checked %s in %.1f ms:\n%s" + (or label url) + @elapsed + (str/join "\n" (map second checks)))})]) + (catch Exception ex + [{:service "http url time" + :label (or label url) + :metric @elapsed} + {:service "http url health" + :label (or label url) + :metric 0 + :state "critical" + :description (format "%s: %s" + (.getSimpleName (class ex)) + (.getMessage ex))}]))))) + + +(defmethod source/initialize :http + [config] + (when-not (:url config) + (throw (IllegalArgumentException. + "Cannot initialize HTTP source without a URL"))) + (-> (merge {:timeout 1000 + :response-checks [{:type :status + :values #{200}}]} + config) + (select-keys [:type :period :label + :url :timeout + :response-checks + :record-fields]) + (map->HTTPSource))) From a4de9257e68570155199bc0063adec4af067bae6 Mon Sep 17 00:00:00 2001 From: Greg Look Date: Sat, 3 Nov 2018 11:34:31 -0700 Subject: [PATCH 04/12] Run cljfmt over code. --- project.clj | 6 ++++++ src/solanum/config.clj | 10 +++++----- src/solanum/output/riemann.clj | 3 ++- src/solanum/scheduler.clj | 19 ++++++++----------- src/solanum/source/process.clj | 16 ++++++++-------- src/solanum/system/darwin.clj | 8 ++++---- 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/project.clj b/project.clj index 2286128..30bb7a9 100644 --- a/project.clj +++ b/project.clj @@ -17,6 +17,12 @@ [org.yaml/snakeyaml "1.23"] [riemann-clojure-client "0.5.0"]] + :cljfmt + {:padding-lines 2 + :max-consecutive-blank-lines 3 + :indents {cond [[:block 0]] + case [[:block 0]]}} + :hiera {:cluster-depth 2 :vertical false diff --git a/src/solanum/config.clj b/src/solanum/config.clj index cc4df2f..529f5de 100644 --- a/src/solanum/config.clj +++ b/src/solanum/config.clj @@ -6,6 +6,9 @@ [clojure.string :as str] [clojure.tools.logging :as log] [clojure.walk :as walk] + [solanum.output.core :as output] + [solanum.output.print] + [solanum.output.riemann] [solanum.source.core :as source] [solanum.source.cpu] [solanum.source.disk-space] @@ -18,9 +21,6 @@ [solanum.source.tcp] [solanum.source.test] [solanum.source.uptime] - [solanum.output.core :as output] - [solanum.output.print] - [solanum.output.riemann] [solanum.util :as u]) (:import org.yaml.snakeyaml.Yaml)) @@ -63,8 +63,8 @@ (let [parser (Yaml.) data (.load parser (slurp file))] (walk/prewalk yaml->clj data)) - (catch Exception ex - (log/error ex "Failed to load configuration from" path))) + (catch Exception ex + (log/error ex "Failed to load configuration from" path))) (log/warn "Can't load configuration from nonexistent file" path)))) diff --git a/src/solanum/output/riemann.clj b/src/solanum/output/riemann.clj index f2f167f..1cf14dd 100644 --- a/src/solanum/output/riemann.clj +++ b/src/solanum/output/riemann.clj @@ -4,7 +4,8 @@ [riemann.client :as riemann] [solanum.output.core :as output]) (:import - io.riemann.riemann.client.RiemannClient)) + (io.riemann.riemann.client + RiemannClient))) (defn- add-timestamp diff --git a/src/solanum/scheduler.clj b/src/solanum/scheduler.clj index 9865a84..3784356 100644 --- a/src/solanum/scheduler.clj +++ b/src/solanum/scheduler.clj @@ -1,15 +1,13 @@ (ns solanum.scheduler "Event collection scheduling code." (:require + [clojure.tools.logging :as log] [solanum.channel :as chan] [solanum.source.core :as source] - [solanum.util :as u] - [clojure.tools.logging :as log]) + [solanum.util :as u]) (:import - (java.time - Instant) - (java.time.temporal - ChronoUnit) + java.time.Instant + java.time.temporal.ChronoUnit (java.util PriorityQueue Queue))) @@ -44,11 +42,10 @@ (defn collect-source "Collect events from a source and put them onto the event channel." [defaults source] - (let [prep-event (comp - #(assoc % :time (event-time)) - (partial u/merge-attrs - defaults - (:attributes source)))] + (let [prep-event (comp #(assoc % :time (event-time)) + (partial u/merge-attrs + defaults + (:attributes source)))] (try (log/debug "Collecting events from" (pr-str source)) (into [] (map prep-event) (source/collect-events source)) diff --git a/src/solanum/source/process.clj b/src/solanum/source/process.clj index 28f6962..9fe0f0c 100644 --- a/src/solanum/source/process.clj +++ b/src/solanum/source/process.clj @@ -28,14 +28,14 @@ :rss (Long/parseLong rss) :vsize (Long/parseLong vsize) :state (case state - "D" :uninterruptible-sleep ; usually IO - "R" :running - "S" :sleep ; interruptable - "T" :stopped - "W" :paging ; defunct since 2.6.x - "X" :dead - "Z" :zombie - :unknown) + "D" :uninterruptible-sleep ; usually IO + "R" :running + "S" :sleep ; interruptable + "T" :stopped + "W" :paging ; defunct since 2.6.x + "X" :dead + "Z" :zombie + :unknown) :user user :group group :start-time lstart ; TODO: parse? diff --git a/src/solanum/system/darwin.clj b/src/solanum/system/darwin.clj index 21957b9..7908648 100644 --- a/src/solanum/system/darwin.clj +++ b/src/solanum/system/darwin.clj @@ -27,10 +27,10 @@ [] (let [result (shell/sh "top" "-l" "1")] (if (zero? (:exit result)) - (->> (str/split (:out result) #"\n") - (take 10) - (parse-top-output)) - (log/error "Failed to run top:" (pr-str (:err result)))))) + (->> (str/split (:out result) #"\n") + (take 10) + (parse-top-output)) + (log/error "Failed to run top:" (pr-str (:err result)))))) (defn read-top From 172fa2113f9cd23387f38ad5f3bf635179861b97 Mon Sep 17 00:00:00 2001 From: Greg Look Date: Sat, 3 Nov 2018 11:56:21 -0700 Subject: [PATCH 05/12] Add http example to config, rename for clarity. --- config.yml => example-config.yml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) rename config.yml => example-config.yml (65%) diff --git a/config.yml b/example-config.yml similarity index 65% rename from config.yml rename to example-config.yml index fa11554..146014f 100644 --- a/config.yml +++ b/example-config.yml @@ -35,14 +35,22 @@ sources: - type: tcp label: postgresql port: 5432 - #- type: certificate - # host: www.google.com - # period: 300 - # attributes: - # ttl: 3600 - # expiry_states: - # warning: 180 - # critical: 30 + - type: http + period: 300 + label: vault + url: 'http://localhost:8200/v1/sys/health' + response_checks: + - type: status + values: [201, 429] + - type: data + key: initialized + value: true + - type: data + key: sealed + value: false + record_fields: + standby: standby + version: version outputs: - type: print From 0cf62e1412ea47747ec3b5f8d66abfd0a077abcf Mon Sep 17 00:00:00 2001 From: Greg Look Date: Sat, 3 Nov 2018 11:56:38 -0700 Subject: [PATCH 06/12] Add release packaging to Makefile. --- .gitignore | 1 + Makefile | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 5297db7..6cf5a94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /solanum +/dist /target /classes /checkouts diff --git a/Makefile b/Makefile index 0da8e16..6455f59 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,19 @@ # Build file for Solanum -# -# https://www.astrecipes.net/blog/2018/07/20/cmd-line-apps-with-clojure-and-graalvm/ -# https://medium.com/graalvm/instant-netty-startup-using-graalvm-native-image-generation-ed6f14ff7692 -default: lint +default: package -.PHONY: setup clean lint test uberjar +.PHONY: setup clean lint test uberjar package -# TODO: fetch graal? +version := $(shell grep defproject project.clj | cut -d ' ' -f 3 | tr -d \") +platform := $(shell uname -s | tr '[:upper:]' '[:lower:]') +release_name := solanum_$(version)_$(platform) +# TODO: fetch graal? setup: lein deps clean: - rm -rf target solanum + rm -rf target dist solanum lint: lein check @@ -26,14 +26,19 @@ target/uberjar/solanum.jar: src/* resources/* svm/java/* uberjar: target/uberjar/solanum.jar -solanum: reflection-config=svm/reflection-config.json +# TODO: --static ? +solanum: reflection-config := svm/reflection-config.json solanum: target/uberjar/solanum.jar $(reflection-config) $(GRAAL_PATH)/bin/native-image \ --report-unsupported-elements-at-runtime \ + --delay-class-initialization-to-runtime=io.netty.handler.ssl.ReferenceCountedOpenSslEngine \ -H:ReflectionConfigurationFiles=$(reflection-config) \ -J-Xmx3G -J-Xms3G \ --no-server \ -jar $< -# seems to be automatic because of --report-unsupported-elements-at-runtime -#--delay-class-initialization-to-runtime=io.netty.handler.ssl.ReferenceCountedOpenSslEngine \ +dist/$(release_name).tar.gz: solanum + @mkdir -p dist + tar -cvzf $@ $^ + +package: dist/$(release_name).tar.gz From 8b1483a4fd92d463e6ef3343623153d67364d104 Mon Sep 17 00:00:00 2001 From: Greg Look Date: Sun, 4 Nov 2018 11:58:28 -0800 Subject: [PATCH 07/12] Add basic config unit tests. --- .circleci/config.yml | 2 + src/solanum/channel.clj | 3 +- src/solanum/config.clj | 2 +- test/solanum/config_test.clj | 88 ++++++++++++++++++++++++++++++++++++ test/solanum/test_util.clj | 14 ++++++ 5 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 test/solanum/config_test.clj create mode 100644 test/solanum/test_util.clj diff --git a/.circleci/config.yml b/.circleci/config.yml index 1e7916b..d179b40 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,6 +5,8 @@ jobs: working_directory: ~/repo docker: - image: circleci/clojure:lein-2.7.1 + environment: + SOLANUM_LOG_APPENDER: nop steps: - checkout - restore_cache: diff --git a/src/solanum/channel.clj b/src/solanum/channel.clj index 5586121..a9b3c99 100644 --- a/src/solanum/channel.clj +++ b/src/solanum/channel.clj @@ -1,5 +1,6 @@ (ns solanum.channel - "Shared event channel." + "Shared event channel logic for sending collected metric events to be written + to outputs." (:import (java.util.concurrent LinkedBlockingQueue diff --git a/src/solanum/config.clj b/src/solanum/config.clj index 529f5de..f516d65 100644 --- a/src/solanum/config.clj +++ b/src/solanum/config.clj @@ -101,7 +101,7 @@ (log/error ex "Failed to initialize output:" (pr-str output-config))))) -(defn initialize-plugins +(defn- initialize-plugins "Initialize all source and output plugins." [config] (-> (into {} config) diff --git a/test/solanum/config_test.clj b/test/solanum/config_test.clj new file mode 100644 index 0000000..056ea4c --- /dev/null +++ b/test/solanum/config_test.clj @@ -0,0 +1,88 @@ +(ns solanum.config-test + (:require + [clojure.java.io :as io] + [clojure.test :refer :all] + [solanum.config :as config] + [solanum.output.core :as output] + [solanum.source.core :as source] + [solanum.test-util :refer :all])) + + +(def path-a "target/test/config-a.yml") +(def path-b "target/test/config-b.yml") + + +(def config-a + "--- +defaults: + stack: foo + ttl: 60 + tags: + - solanum + +sources: + - type: cpu + +outputs: + - type: print +") + + +(def config-b + "--- +defaults: + sys: bar + tags: + - abc + +sources: + - type: memory + +outputs: + - type: print +") + + +(defn write-configs! + [] + (io/make-parents path-a) + (spit path-a config-a) + (spit path-b config-b)) + + +(deftest config-errors + (write-configs!) + (testing "file reading" + (is (nil? (#'config/read-file "target/test/not-a-file.yml"))) + (with-redefs [config/yaml->clj boom!] + (is (nil? (#'config/read-file path-a))))) + (testing "source configuration" + (is (nil? (#'config/configure-source {:foo :bar}))) + (is (nil? (source/initialize {:type :???}))) + (with-redefs [solanum.source.core/initialize boom!] + (is (nil? (#'config/configure-source {:type :foo}))))) + (testing "output configuration" + (is (nil? (#'config/configure-output {:foo :bar}))) + (is (nil? (output/initialize {:type :???}))) + (with-redefs [solanum.output.core/initialize boom!] + (is (nil? (#'config/configure-output {:type :foo})))))) + + +(deftest config-loading + (write-configs!) + (let [config (config/load-files [path-a path-b])] + (is (= {:stack "foo" + :sys "bar" + :tags ["solanum" "abc"] + :ttl 60} + (:defaults config)) + "defaults should be merged") + (is (= [:cpu :memory] + (map :type (:sources config))) + "sources should be merged") + (is (= [:cpu :memory] + (map :type (:sources config))) + "sources should be merged") + (is (= [:print :print] + (map :type (:outputs config))) + "outputs should be merged"))) diff --git a/test/solanum/test_util.clj b/test/solanum/test_util.clj new file mode 100644 index 0000000..859373c --- /dev/null +++ b/test/solanum/test_util.clj @@ -0,0 +1,14 @@ +(ns solanum.test-util) + + +(defn quiet-exception + "Construct a runtime exception which elides stacktrace data." + [message] + (doto (RuntimeException. ^String message) + (.setStackTrace (into-array StackTraceElement [])))) + + +(defn boom! + "Always throws a quiet exception, no matter what it is called with." + [& _] + (throw (quiet-exception "BOOM"))) From 5d50368cc6f4a1fca7d083ce83c4f0c43b7ef7ba Mon Sep 17 00:00:00 2001 From: Greg Look Date: Sun, 4 Nov 2018 12:16:55 -0800 Subject: [PATCH 08/12] Add source util tests. --- test/solanum/source/core_test.clj | 62 +++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 test/solanum/source/core_test.clj diff --git a/test/solanum/source/core_test.clj b/test/solanum/source/core_test.clj new file mode 100644 index 0000000..ef8cdc3 --- /dev/null +++ b/test/solanum/source/core_test.clj @@ -0,0 +1,62 @@ +(ns solanum.source.core-test + (:require + [clojure.test :refer :all] + [solanum.source.core :as source])) + + +(deftest timing-utils + (let [elapsed (source/stopwatch)] + (is (not (realized? elapsed))) + (is (pos? @elapsed)) + (is (realized? elapsed)) + (is (= @elapsed @elapsed)))) + + +(deftest format-utils + (testing "byte-str" + (is (= "0 B" (source/byte-str 0))) + (is (= "1000 B" (source/byte-str 1000))) + (is (= "3 KB" (source/byte-str (* 3 1024)))) + (is (= "6.0 KB" (source/byte-str (* 6.0 1024)))) + (is (= "3.5 MB" (source/byte-str (* 3.5 1024 1024)))) + (is (= "100.0 GB" (source/byte-str (* 100.0 1024 1024 1024)))) + (is (= "600 TB" (source/byte-str (* 600 1024 1024 1024 1024)))) + (is (= "2000.0 PB" (source/byte-str (* 2000.0 1024 1024 1024 1024 1024))))) + (testing "duration-str" + (is (= "00:00:00" (source/duration-str 0))) + (is (= "00:00:05" (source/duration-str 5))) + (is (= "00:01:00" (source/duration-str 60))) + (is (= "00:27:38" (source/duration-str (+ (* 27 60) 38)))) + (is (= "05:27:38" (source/duration-str (+ (* 5 60 60) (* 27 60) 38)))) + (is (= "23:59:59" (source/duration-str (dec (* 24 60 60))))) + (is (= "1 days, 00:00:00" (source/duration-str (* 24 60 60)))) + (is (= "3 days, 08:10:30" (source/duration-str (+ (* 3 24 60 60) (* 8 60 60) (* 10 60) 30)))))) + + +(deftest state-utils + (testing "state-over" + (is (= :x (source/state-over {} 10 :x))) + (is (= :x (source/state-over {:y 15} 10 :x))) + (is (= :y (source/state-over {:y 15} 15 :x))) + (is (= :y (source/state-over {:y 15} 20 :x))) + (is (= :y (source/state-over {:y 15, :z 30} 20 :x))) + (is (= :z (source/state-over {:y 15, :z 30} 30 :x))) + (is (= :z (source/state-over {:y 15, :z 30} 35 :x)))) + (testing "state-under" + (is (= :x (source/state-under {} 30 :x))) + (is (= :x (source/state-under {:y 25} 30 :x))) + (is (= :y (source/state-under {:y 25} 25 :x))) + (is (= :y (source/state-under {:y 25} 20 :x))) + (is (= :y (source/state-under {:y 25, :z 15} 20 :x))) + (is (= :z (source/state-under {:y 25, :z 15} 15 :x))) + (is (= :z (source/state-under {:y 25, :z 15} 10 :x))))) + + +(deftest counter-diffing + (is (= {:foo {:x 0, :y 5} + :bar {:x 10, :y 8, :z 3}} + (source/diff-tracker + {:foo {:x 200, :y 123, :z 805} + :bar {:x 320, :y 241, :z 800}} + {:foo {:x 200, :y 128, :a 1} + :bar {:x 330, :y 249, :z 803}})))) From 1307954f952e808257c90a5cfae455e71ef24355 Mon Sep 17 00:00:00 2001 From: Greg Look Date: Sun, 4 Nov 2018 23:15:34 -0800 Subject: [PATCH 09/12] Simplify util ns by inlining some functions. --- src/solanum/config.clj | 14 ++++++++++---- src/solanum/util.clj | 26 +++----------------------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/solanum/config.clj b/src/solanum/config.clj index f516d65..210f980 100644 --- a/src/solanum/config.clj +++ b/src/solanum/config.clj @@ -28,6 +28,12 @@ ;; ## File Loading +(defn- keybabify + "Replace underscores in a keyword with hyphens. Only uses the name portion." + [k] + (keyword (str/replace (name k) "_" "-"))) + + (defn- coerce-map "Coerces a Java map into a Clojure map, keywordizing the keys and `:type` values." @@ -35,9 +41,9 @@ (into {} (map (fn coerce-entry [[k v]] - (let [k (u/keybabify k)] + (let [k (keybabify k)] [k (if (= :type k) - (u/keybabify v) + (keybabify v) v)]))) m)) @@ -72,8 +78,8 @@ "Merge configuration maps together to produce a combined config." [a b] {:defaults (u/merge-attrs (:defaults a) (:defaults b)) - :sources (u/merge-vec (:sources a) (:sources b)) - :outputs (u/merge-vec (:outputs a) (:outputs b))}) + :sources (into (vec (:sources a)) (:sources b)) + :outputs (into (vec (:outputs a)) (:outputs b))}) diff --git a/src/solanum/util.clj b/src/solanum/util.clj index 37d2791..d16490f 100644 --- a/src/solanum/util.clj +++ b/src/solanum/util.clj @@ -1,17 +1,9 @@ (ns solanum.util "Shared utility code. Should not be used by source or output - implementations." - (:require - [clojure.string :as str])) + implementations.") -(defn tagged? - "True if the attributes include some tag values." - [x] - (boolean (seq (:tags x)))) - - -(defn merge-tags +(defn- merge-tags "Combine two tag vectors together." [a b] (vec (distinct (concat a b)))) @@ -21,20 +13,8 @@ "Merge attribute maps, handling tags correctly." ([a b] (let [attrs (merge a b)] - (if (or (tagged? a) (tagged? b)) + (if (or (seq (:tags a)) (seq (:tags b))) (assoc attrs :tags (merge-tags (:tags a) (:tags b))) attrs))) ([a b & more] (reduce merge-attrs (list* a b more)))) - - -(defn merge-vec - "Merge two vectors of configuration together by concatenating them." - [a b] - (into (vec a) b)) - - -(defn keybabify - "Replace underscores in a keyword with hyphens. Only uses the name portion." - [k] - (keyword (str/replace (name k) "_" "-"))) From 752736d0754a810e5d39092957d4c20079d34efd Mon Sep 17 00:00:00 2001 From: Greg Look Date: Mon, 5 Nov 2018 16:06:35 -0800 Subject: [PATCH 10/12] Drop test profile. --- project.clj | 5 ----- 1 file changed, 5 deletions(-) diff --git a/project.clj b/project.clj index 30bb7a9..1aed270 100644 --- a/project.clj +++ b/project.clj @@ -37,11 +37,6 @@ [org.clojure/tools.namespace "0.2.11"]] :jvm-opts ["-DSOLANUM_LOG_APPENDER=repl"]} - :test - {:jvm-opts ["-DSOLANUM_LOG_APPENDER=nop" - "-DSOLANUM_LOG_LEVEL_ROOT=TRACE" - "-DSOLANUM_LOG_LEVEL=TRACE"]} - :svm {:java-source-paths ["svm/java"] :dependencies From 0172a4b58d117751b2366d1c36cf366d5304b751 Mon Sep 17 00:00:00 2001 From: Greg Look Date: Mon, 5 Nov 2018 16:16:41 -0800 Subject: [PATCH 11/12] Update CHANGELOG for 3.1.0 release. --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68b5192..07bb61a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +... + +## [3.1.0] - 2018-11-05 + ### Added - New HTTP source allows for checking URL endpoints and asserting that the response meets certain properties. @@ -19,5 +23,6 @@ Clojure rewrite. Final cut of Ruby version. -[Unreleased]: https://github.com/greglook/solanum/compare/3.0.0...HEAD +[Unreleased]: https://github.com/greglook/solanum/compare/3.1.0...HEAD +[3.1.0]: https://github.com/greglook/solanum/compare/3.0.0...3.1.0 [3.0.0]: https://github.com/greglook/solanum/compare/2.0.0...3.0.0 From 46c8e1d1d49d8e37b490048ae121cd80c169e52f Mon Sep 17 00:00:00 2001 From: Greg Look Date: Mon, 5 Nov 2018 16:17:08 -0800 Subject: [PATCH 12/12] Set release version. --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 1aed270..2e5c382 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject mvxcvi/solanum "3.0.1-SNAPSHOT" +(defproject mvxcvi/solanum "3.1.0" :description "Local host monitoring daemon." :url "https://github.com/greglook/solanum" :license {:name "Public Domain"