diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 8785f272e..d3f76df80 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -150,7 +150,7 @@ ;; When metrics namespace is provided (when metrics - (->> (:registry metrics) + (->> (::mtx/registry metrics) (PrometheusMetricsTrackerFactory.) (.setMetricsTrackerFactory config))) diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 6347e8e6b..a86227e60 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -151,7 +151,7 @@ [middleware/errors errors/handle] [middleware/restrict-methods]]} - ["/metrics" {:handler (:handler metrics)}] + ["/metrics" {:handler (::mtx/handler metrics)}] ["/assets" {:middleware [(:middleware session)]} ["/by-id/:id" {:handler (:objects-handler assets)}] ["/by-file-media-id/:id" {:handler (:file-objects-handler assets)}] diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj index 8641e9f07..e170f9af7 100644 --- a/backend/src/app/metrics.clj +++ b/backend/src/app/metrics.clj @@ -8,6 +8,8 @@ (:refer-clojure :exclude [run!]) (:require [app.common.logging :as l] + [app.common.spec :as us] + [app.metrics.definition :as-alias mdef] [clojure.spec.alpha :as s] [integrant.core :as ig]) (:import @@ -16,11 +18,12 @@ io.prometheus.client.Counter$Child io.prometheus.client.Gauge io.prometheus.client.Gauge$Child - io.prometheus.client.Summary - io.prometheus.client.Summary$Child - io.prometheus.client.Summary$Builder io.prometheus.client.Histogram io.prometheus.client.Histogram$Child + io.prometheus.client.SimpleCollector + io.prometheus.client.Summary + io.prometheus.client.Summary$Builder + io.prometheus.client.Summary$Child io.prometheus.client.exporter.common.TextFormat io.prometheus.client.hotspot.DefaultExports java.io.StringWriter)) @@ -28,7 +31,7 @@ (set! *warn-on-reflection* true) (declare create-registry) -(declare create) +(declare create-collector) (declare handler) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -37,128 +40,151 @@ (def default-metrics {:update-file-changes - {:name "penpot_rpc_update_file_changes_total" - :help "A total number of changes submitted to update-file." - :type :counter} + {::mdef/name "penpot_rpc_update_file_changes_total" + ::mdef/help "A total number of changes submitted to update-file." + ::mdef/type :counter} :update-file-bytes-processed - {:name "penpot_rpc_update_file_bytes_processed_total" - :help "A total number of bytes processed by update-file." - :type :counter} + {::mdef/name "penpot_rpc_update_file_bytes_processed_total" + ::mdef/help "A total number of bytes processed by update-file." + ::mdef/type :counter} :rpc-mutation-timing - {:name "penpot_rpc_mutation_timing" - :help "RPC mutation method call timming." - :labels ["name"] - :type :histogram} + {::mdef/name "penpot_rpc_mutation_timing" + ::mdef/help "RPC mutation method call timming." + ::mdef/labels ["name"] + ::mdef/type :histogram} :rpc-command-timing - {:name "penpot_rpc_command_timing" - :help "RPC command method call timming." - :labels ["name"] - :type :histogram} + {::mdef/name "penpot_rpc_command_timing" + ::mdef/help "RPC command method call timming." + ::mdef/labels ["name"] + ::mdef/type :histogram} :rpc-query-timing - {:name "penpot_rpc_query_timing" - :help "RPC query method call timing." - :labels ["name"] - :type :histogram} + {::mdef/name "penpot_rpc_query_timing" + ::mdef/help "RPC query method call timing." + ::mdef/labels ["name"] + ::mdef/type :histogram} :websocket-active-connections - {:name "penpot_websocket_active_connections" - :help "Active websocket connections gauge" - :type :gauge} + {::mdef/name "penpot_websocket_active_connections" + ::mdef/help "Active websocket connections gauge" + ::mdef/type :gauge} :websocket-messages-total - {:name "penpot_websocket_message_total" - :help "Counter of processed messages." - :labels ["op"] - :type :counter} + {::mdef/name "penpot_websocket_message_total" + ::mdef/help "Counter of processed messages." + ::mdef/labels ["op"] + ::mdef/type :counter} :websocket-session-timing - {:name "penpot_websocket_session_timing" - :help "Websocket session timing (seconds)." - :type :summary} + {::mdef/name "penpot_websocket_session_timing" + ::mdef/help "Websocket session timing (seconds)." + ::mdef/type :summary} :session-update-total - {:name "penpot_http_session_update_total" - :help "A counter of session update batch events." - :type :counter} + {::mdef/name "penpot_http_session_update_total" + ::mdef/help "A counter of session update batch events." + ::mdef/type :counter} :tasks-timing - {:name "penpot_tasks_timing" - :help "Background tasks timing (milliseconds)." - :labels ["name"] - :type :summary} + {::mdef/name "penpot_tasks_timing" + ::mdef/help "Background tasks timing (milliseconds)." + ::mdef/labels ["name"] + ::mdef/type :summary} :redis-eval-timing - {:name "penpot_redis_eval_timing" - :help "Redis EVAL commands execution timings (ms)" - :labels ["name"] - :type :summary} + {::mdef/name "penpot_redis_eval_timing" + ::mdef/help "Redis EVAL commands execution timings (ms)" + ::mdef/labels ["name"] + ::mdef/type :summary} :rpc-semaphore-queued-submissions - {:name "penpot_rpc_semaphore_queued_submissions" - :help "Current number of queued submissions on RPC-SEMAPHORE." - :labels ["name"] - :type :gauge} + {::mdef/name "penpot_rpc_semaphore_queued_submissions" + ::mdef/help "Current number of queued submissions on RPC-SEMAPHORE." + ::mdef/labels ["name"] + ::mdef/type :gauge} :rpc-semaphore-used-permits - {:name "penpot_rpc_semaphore_used_permits" - :help "Current number of used permits on RPC-SEMAPHORE." - :labels ["name"] - :type :gauge} + {::mdef/name "penpot_rpc_semaphore_used_permits" + ::mdef/help "Current number of used permits on RPC-SEMAPHORE." + ::mdef/labels ["name"] + ::mdef/type :gauge} :rpc-semaphore-acquires-total - {:name "penpot_rpc_semaphore_acquires_total" - :help "Total number of acquire operations on RPC-SEMAPHORE." - :labels ["name"] - :type :counter} + {::mdef/name "penpot_rpc_semaphore_acquires_total" + ::mdef/help "Total number of acquire operations on RPC-SEMAPHORE." + ::mdef/labels ["name"] + ::mdef/type :counter} :executors-active-threads - {:name "penpot_executors_active_threads" - :help "Current number of threads available in the executor service." - :labels ["name"] - :type :gauge} + {::mdef/name "penpot_executors_active_threads" + ::mdef/help "Current number of threads available in the executor service." + ::mdef/labels ["name"] + ::mdef/type :gauge} :executors-completed-tasks - {:name "penpot_executors_completed_tasks_total" - :help "Aproximate number of completed tasks by the executor." - :labels ["name"] - :type :counter} + {::mdef/name "penpot_executors_completed_tasks_total" + ::mdef/help "Aproximate number of completed tasks by the executor." + ::mdef/labels ["name"] + ::mdef/type :counter} :executors-running-threads - {:name "penpot_executors_running_threads" - :help "Current number of threads with state RUNNING." - :labels ["name"] - :type :gauge} + {::mdef/name "penpot_executors_running_threads" + ::mdef/help "Current number of threads with state RUNNING." + ::mdef/labels ["name"] + ::mdef/type :gauge} :executors-queued-submissions - {:name "penpot_executors_queued_submissions" - :help "Current number of queued submissions." - :labels ["name"] - :type :gauge}}) + {::mdef/name "penpot_executors_queued_submissions" + ::mdef/help "Current number of queued submissions." + ::mdef/labels ["name"] + ::mdef/type :gauge}}) + +(s/def ::mdef/name string?) +(s/def ::mdef/help string?) +(s/def ::mdef/labels (s/every string? :kind vector?)) +(s/def ::mdef/type #{:gauge :counter :summary :histogram}) + +(s/def ::mdef/instance + #(instance? SimpleCollector %)) + +(s/def ::mdef/definition + (s/keys :req [::mdef/name + ::mdef/help + ::mdef/type] + :opt [::mdef/labels + ::mdef/instance])) + +(s/def ::definitions + (s/map-of keyword? ::mdef/definition)) + +(s/def ::registry + #(instance? CollectorRegistry %)) + +(s/def ::handler fn?) +(s/def ::metrics + (s/keys :req [::registry + ::handler + ::definitions])) (defmethod ig/init-key ::metrics [_ _] (l/info :action "initialize metrics") (let [registry (create-registry) definitions (reduce-kv (fn [res k v] - (->> (assoc v :registry registry) - (create) + (->> (assoc v ::registry registry) + (create-collector) (assoc res k))) {} default-metrics)] - {:handler (partial handler registry) - :definitions definitions - :registry registry})) + (us/verify! ::definitions definitions) -;; TODO: revisit -(s/def ::handler fn?) -(s/def ::registry #(instance? CollectorRegistry %)) -(s/def ::metrics - (s/keys :req-un [::registry ::handler])) + {::handler (partial handler registry) + ::definitions definitions + ::registry registry})) (defn- handler [registry _ respond _] @@ -182,13 +208,16 @@ (def default-histogram-buckets [1 5 10 25 50 75 100 250 500 750 1000 2500 5000 7500]) +(defmulti run-collector! (fn [mdef _] (::mdef/type mdef))) +(defmulti create-collector ::mdef/type) + (defn run! - [{:keys [definitions]} {:keys [id] :as params}] + [{:keys [::definitions]} {:keys [id] :as params}] (when-let [mobj (get definitions id)] - ((::fn mobj) params) + (run-collector! mobj params) true)) -(defn create-registry +(defn- create-registry [] (let [registry (CollectorRegistry.)] (DefaultExports/register registry) @@ -200,79 +229,89 @@ (and (.isArray ^Class oc) (= (.getComponentType oc) String)))) -(defn make-counter - [{:keys [name help registry reg labels] :as props}] +(defmethod run-collector! :counter + [{:keys [::mdef/instance]} {:keys [inc labels] :or {inc 1 labels default-empty-labels}}] + (let [instance (.labels instance (if (is-array? labels) labels (into-array String labels)))] + (.inc ^Counter$Child instance (double inc)))) + +(defmethod run-collector! :gauge + [{:keys [::mdef/instance]} {:keys [inc dec labels val] :or {labels default-empty-labels}}] + (let [instance (.labels ^Gauge instance (if (is-array? labels) labels (into-array String labels)))] + (cond (number? inc) (.inc ^Gauge$Child instance (double inc)) + (number? dec) (.dec ^Gauge$Child instance (double dec)) + (number? val) (.set ^Gauge$Child instance (double val))))) + +(defmethod run-collector! :summary + [{:keys [::mdef/instance]} {:keys [val labels] :or {labels default-empty-labels}}] + (let [instance (.labels ^Summary instance (if (is-array? labels) labels (into-array String labels)))] + (.observe ^Summary$Child instance val))) + +(defmethod run-collector! :histogram + [{:keys [::mdef/instance]} {:keys [val labels] :or {labels default-empty-labels}}] + (let [instance (.labels ^Histogram instance (if (is-array? labels) labels (into-array String labels)))] + (.observe ^Histogram$Child instance val))) + +(defmethod create-collector :counter + [{::mdef/keys [name help reg labels] + ::keys [registry] + :as props}] + (let [registry (or registry reg) instance (.. (Counter/build) (name name) - (help help)) - _ (when (seq labels) - (.labelNames instance (into-array String labels))) - instance (.register instance registry)] + (help help))] + (when (seq labels) + (.labelNames instance (into-array String labels))) - {::instance instance - ::fn (fn [{:keys [inc labels] :or {inc 1 labels default-empty-labels}}] - (let [instance (.labels instance (if (is-array? labels) labels (into-array String labels)))] - (.inc ^Counter$Child instance (double inc))))})) + (assoc props ::mdef/instance (.register instance registry)))) -(defn make-gauge - [{:keys [name help registry reg labels] :as props}] +(defmethod create-collector :gauge + [{::mdef/keys [name help reg labels] + ::keys [registry] + :as props}] (let [registry (or registry reg) instance (.. (Gauge/build) (name name) - (help help)) - _ (when (seq labels) - (.labelNames instance (into-array String labels))) - instance (.register instance registry)] - {::instance instance - ::fn (fn [{:keys [inc dec labels val] :or {labels default-empty-labels}}] - (let [instance (.labels ^Gauge instance (if (is-array? labels) labels (into-array String labels)))] - (cond (number? inc) (.inc ^Gauge$Child instance (double inc)) - (number? dec) (.dec ^Gauge$Child instance (double dec)) - (number? val) (.set ^Gauge$Child instance (double val)))))})) + (help help))] + (when (seq labels) + (.labelNames instance (into-array String labels))) -(defn make-summary - [{:keys [name help registry reg labels max-age quantiles buckets] - :or {max-age 3600 buckets 12 quantiles default-quantiles} :as props}] + (assoc props ::mdef/instance (.register instance registry)))) + +(defmethod create-collector :summary + [{::mdef/keys [name help reg labels max-age quantiles buckets] + ::keys [registry] + :or {max-age 3600 buckets 12 quantiles default-quantiles} + :as props}] (let [registry (or registry reg) builder (doto (Summary/build) (.name name) - (.help help)) - _ (when (seq quantiles) - (.maxAgeSeconds ^Summary$Builder builder ^long max-age) - (.ageBuckets ^Summary$Builder builder buckets)) - _ (doseq [[q e] quantiles] - (.quantile ^Summary$Builder builder q e)) - _ (when (seq labels) - (.labelNames ^Summary$Builder builder (into-array String labels))) - instance (.register ^Summary$Builder builder registry)] + (.help help))] - {::instance instance - ::fn (fn [{:keys [val labels] :or {labels default-empty-labels}}] - (let [instance (.labels ^Summary instance (if (is-array? labels) labels (into-array String labels)))] - (.observe ^Summary$Child instance val)))})) + (when (seq quantiles) + (.maxAgeSeconds ^Summary$Builder builder ^long max-age) + (.ageBuckets ^Summary$Builder builder buckets)) -(defn make-histogram - [{:keys [name help registry reg labels buckets] - :or {buckets default-histogram-buckets}}] + (doseq [[q e] quantiles] + (.quantile ^Summary$Builder builder q e)) + + (when (seq labels) + (.labelNames ^Summary$Builder builder (into-array String labels))) + + (assoc props ::mdef/instance (.register ^Summary$Builder builder registry)))) + +(defmethod create-collector :histogram + [{::mdef/keys [name help reg labels buckets] + ::keys [registry] + :or {buckets default-histogram-buckets} + :as props}] (let [registry (or registry reg) instance (doto (Histogram/build) (.name name) (.help help) - (.buckets (into-array Double/TYPE buckets))) - _ (when (seq labels) - (.labelNames instance (into-array String labels))) - instance (.register instance registry)] + (.buckets (into-array Double/TYPE buckets)))] - {::instance instance - ::fn (fn [{:keys [val labels] :or {labels default-empty-labels}}] - (let [instance (.labels ^Histogram instance (if (is-array? labels) labels (into-array String labels)))] - (.observe ^Histogram$Child instance val)))})) + (when (seq labels) + (.labelNames instance (into-array String labels))) -(defn create - [{:keys [type] :as props}] - (case type - :counter (make-counter props) - :gauge (make-gauge props) - :summary (make-summary props) - :histogram (make-histogram props))) + (assoc props ::mdef/instance (.register instance registry)))) diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 334b3feb8..299cf92e5 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -14,8 +14,8 @@ [app.main.ui.settings.options :refer [options-page]] [app.main.ui.settings.password :refer [password-page]] [app.main.ui.settings.profile :refer [profile-page]] - [app.main.ui.settings.sidebar :refer [sidebar]] - [app.util.i18n :as i18n :refer [tr]] + [app.main.ui.settings.sidebar :refer [sidebar]] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [rumext.alpha :as mf])) @@ -31,10 +31,11 @@ (let [section (get-in route [:data :name]) profile (mf/deref refs/profile) locale (mf/deref i18n/locale)] + (mf/use-effect #(when (nil? profile) (st/emit! (rt/nav :auth-login)))) - + [:section.dashboard-layout [:& sidebar {:profile profile :locale locale