0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-28 15:41:25 -05:00

🎉 Add prometheus metrics.

This commit is contained in:
Andrey Antukh 2020-05-16 20:38:35 +02:00
parent ccb79f7188
commit b532e74310
20 changed files with 359 additions and 82 deletions

View file

@ -9,11 +9,15 @@
;; Logging
org.clojure/tools.logging {:mvn/version "1.1.0"}
org.apache.logging.log4j/log4j-api {:mvn/version "2.13.2"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.13.2"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.13.2"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.13.2"}
org.apache.logging.log4j/log4j-slf4j-impl {:mvn/version "2.13.2"}
org.apache.logging.log4j/log4j-api {:mvn/version "2.13.3"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.13.3"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.13.3"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.13.3"}
org.apache.logging.log4j/log4j-slf4j-impl {:mvn/version "2.13.3"}
io.prometheus/simpleclient {:mvn/version "0.9.0"}
io.prometheus/simpleclient_hotspot {:mvn/version "0.9.0"}
io.prometheus/simpleclient_httpserver {:mvn/version "0.9.0"}
expound/expound {:mvn/version "0.8.4"}
instaparse/instaparse {:mvn/version "1.4.10"}
@ -26,7 +30,7 @@
seancorfield/next.jdbc {:mvn/version "1.0.424"}
metosin/reitit-ring {:mvn/version "0.4.2"}
org.postgresql/postgresql {:mvn/version "42.2.12"}
com.zaxxer/HikariCP {:mvn/version "3.4.3"}
com.zaxxer/HikariCP {:mvn/version "3.4.5"}
funcool/datoteka {:mvn/version "1.2.0"}
funcool/promesa {:mvn/version "5.1.0"}
@ -51,7 +55,7 @@
io.aviso/pretty {:mvn/version "0.1.37"}
mount/mount {:mvn/version "0.1.16"}
environ/environ {:mvn/version "1.1.0"}}
environ/environ {:mvn/version "1.2.0"}}
:paths ["src" "resources" "../common" "common"]
:aliases
{:dev

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" monitorInterval="60">
<Appenders>
<Console name="console" target="SYSTEM_OUT">

View file

@ -17,11 +17,13 @@
[next.jdbc.result-set :as jdbc-rs]
[next.jdbc.sql :as jdbc-sql]
[next.jdbc.sql.builder :as jdbc-bld]
[uxbox.metrics :as mtx]
[uxbox.common.exceptions :as ex]
[uxbox.config :as cfg]
[uxbox.util.data :as data])
(:import
org.postgresql.util.PGobject
com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory
com.zaxxer.hikari.HikariConfig
com.zaxxer.hikari.HikariDataSource))
@ -30,17 +32,20 @@
(let [dburi (:database-uri cfg)
username (:database-username cfg)
password (:database-password cfg)
config (HikariConfig.)]
config (HikariConfig.)
mfactory (PrometheusMetricsTrackerFactory. mtx/registry)]
(doto config
(.setJdbcUrl (str "jdbc:" dburi))
(.setPoolName "main")
(.setAutoCommit true)
(.setReadOnly false)
(.setConnectionTimeout 30000)
(.setValidationTimeout 5000)
(.setIdleTimeout 600000)
(.setMaxLifetime 1800000)
(.setMinimumIdle 10)
(.setMaximumPoolSize 20))
(.setConnectionTimeout 30000) ;; 30seg
(.setValidationTimeout 5000) ;; 5seg
(.setIdleTimeout 900000) ;; 15min
(.setMaxLifetime 900000) ;; 15min
(.setMinimumIdle 5)
(.setMaximumPoolSize 10)
(.setMetricsTrackerFactory mfactory))
(when username (.setUsername config username))
(when password (.setPassword config password))
config))
@ -127,3 +132,11 @@
(= typ "jsonb"))
(json/read-str val)
val)))
;; Instrumentation
(mtx/instrument-with-counter!
{:var [#'jdbc/execute-one!
#'jdbc/execute!]
:id "database__query_counter"
:help "An absolute counter of database queries."})

View file

@ -17,12 +17,14 @@
[uxbox.http.middleware :as middleware]
[uxbox.http.session :as session]
[uxbox.http.ws :as ws]
[uxbox.metrics :as mtx]
[uxbox.services.notifications :as usn]))
(defn- create-router
[]
(rring/router
[["/api" {:middleware [[middleware/format-response-body]
[["/metrics" {:get mtx/dump}]
["/api" {:middleware [[middleware/format-response-body]
[middleware/errors errors/handle]
[middleware/parse-request-body]
[middleware/params]
@ -37,7 +39,6 @@
["/logout" {:handler handlers/logout-handler
:method :post}]
["/w" {:middleware [session/auth]}
["/query/:type" {:get handlers/query-handler}]
["/mutation/:type" {:post handlers/mutation-handler}]]]]))
@ -46,8 +47,9 @@
:start (rring/ring-handler
(create-router)
(constantly {:status 404, :body ""})
{:middleware [middleware/development-resources
middleware/development-cors]}))
{:middleware [[middleware/development-resources]
[middleware/development-cors]
[middleware/metrics]]}))
(defn start-server
[cfg app]

View file

@ -9,6 +9,7 @@
(:require
[clojure.tools.logging :as log]
[cuerdas.core :as str]
[uxbox.metrics :as mtx]
[io.aviso.exception :as e]))
(defmulti handle-exception

View file

@ -15,6 +15,7 @@
[ring.middleware.multipart-params :refer [wrap-multipart-params]]
[ring.middleware.params :refer [wrap-params]]
[ring.middleware.resource :refer [wrap-resource]]
[uxbox.metrics :as mtx]
[uxbox.common.exceptions :as ex]
[uxbox.config :as cfg]
[uxbox.util.transit :as t]))
@ -83,6 +84,12 @@
{:name ::errors
:compile (constantly wrap-errors)})
(def metrics
{:name ::metrics
:wrap (fn [handler]
(mtx/wrap-counter handler {:id "http__requests_counter"
:help "Absolute http requests counter."}))})
(def cookies
{:name ::cookies
:compile (constantly wrap-cookies)})

View file

@ -0,0 +1,181 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.metrics
(:require
[clojure.tools.logging :as log]
[cuerdas.core :as str])
(:import
io.prometheus.client.CollectorRegistry
io.prometheus.client.Counter
io.prometheus.client.Gauge
io.prometheus.client.Summary
io.prometheus.client.exporter.common.TextFormat
io.prometheus.client.hotspot.DefaultExports
java.io.StringWriter))
(defn- create-registry
[]
(let [registry (CollectorRegistry.)]
(DefaultExports/register registry)
registry))
(defonce registry (create-registry))
(defonce cache (atom {}))
(defmacro with-measure
[sym expr teardown]
`(let [~sym (System/nanoTime)]
(try
~expr
(finally
(let [~sym (/ (- (System/nanoTime) ~sym) 1000000)]
~teardown)))))
(defn make-counter
[{:keys [id help] :as props}]
(let [instance (doto (Counter/build)
(.name id)
(.help help))
instance (.register instance registry)]
(reify
clojure.lang.IDeref
(deref [_] instance)
clojure.lang.IFn
(invoke [_ cmd]
(.inc ^Counter instance))
(invoke [_ cmd val]
(case cmd
:wrap (fn
([a]
(.inc ^Counter instance)
(val a))
([a b]
(.inc ^Counter instance)
(val a b))
([a b c]
(.inc ^Counter instance)
(val a b c)))
(throw (IllegalArgumentException. "invalid arguments")))))))
(defn counter
[{:keys [id] :as props}]
(or (get @cache id)
(let [v (make-counter props)]
(swap! cache assoc id v)
v)))
(defn make-gauge
[{:keys [id help] :as props}]
(let [instance (doto (Gauge/build)
(.name id)
(.help help))
instance (.register instance registry)]
(reify
clojure.lang.IDeref
(deref [_] instance)
clojure.lang.IFn
(invoke [_ cmd]
(case cmd
:inc (.inc ^Gauge instance)
:dec (.dec ^Gauge instance))))))
(defn gauge
[{:keys [id] :as props}]
(or (get @cache id)
(let [v (make-gauge props)]
(swap! cache assoc id v)
v)))
(defn make-summary
[{:keys [id help] :as props}]
(let [instance (doto (Summary/build)
(.name id)
(.help help)
(.quantile 0.5 0.05)
(.quantile 0.9 0.01)
(.quantile 0.99 0.001))
instance (.register instance registry)]
(reify
clojure.lang.IDeref
(deref [_] instance)
clojure.lang.IFn
(invoke [_ val]
(.observe ^Summary instance val))
(invoke [_ cmd val]
(case cmd
:wrap (fn
([a]
(with-measure $$
(val a)
(.observe ^Summary instance $$)))
([a b]
(with-measure $$
(val a b)
(.observe ^Summary instance $$)))
([a b c]
(with-measure $$
(val a b c)
(.observe ^Summary instance $$))))
(throw (IllegalArgumentException. "invalid arguments")))))))
(defn summary
[{:keys [id] :as props}]
(or (get @cache id)
(let [v (make-summary props)]
(swap! cache assoc id v)
v)))
(defn wrap-summary
[f props]
(let [sm (summary props)]
(sm :wrap f)))
(defn wrap-counter
[f props]
(let [cnt (counter props)]
(cnt :wrap f)))
(defn instrument-with-counter!
[{:keys [var] :as props}]
(let [cnt (counter props)
vars (if (var? var) [var] var)]
(doseq [var vars]
(alter-var-root var (fn [root]
(let [mdata (meta root)
original (::counter-original mdata root)]
(with-meta
(cnt :wrap original)
(assoc mdata ::counter-original original))))))))
(defn instrument-with-summary!
[{:keys [var] :as props}]
(let [sm (summary props)]
(alter-var-root var (fn [root]
(let [mdata (meta root)
original (::summary-original mdata root)]
(with-meta
(sm :wrap original)
(assoc mdata ::summary-original original)))))))
(defn dump
[& args]
(let [samples (.metricFamilySamples ^CollectorRegistry registry)
writer (StringWriter.)]
(TextFormat/write004 writer samples)
{:headers {"content-type" TextFormat/CONTENT_TYPE_004}
:body (.toString writer)}))

View file

@ -0,0 +1,73 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.services.middleware
"Common middleware for services."
(:require
[clojure.tools.logging :as log]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[expound.alpha :as expound]
[uxbox.common.exceptions :as ex]
[uxbox.common.spec :as us]
[uxbox.metrics :as mtx]))
(defn wrap-spec
[handler]
(let [mdata (meta handler)
spec (s/get-spec (:spec mdata))]
(if (nil? spec)
handler
(with-meta
(fn [params]
(let [result (us/conform spec params)]
(handler result)))
(assoc mdata ::wrap-spec true)))))
(defn wrap-error
[handler]
(let [mdata (meta handler)]
(with-meta
(fn [params]
(try
(handler params)
(catch Throwable error
(ex/raise :type :service-error
:name (:spec mdata)
:cause error))))
(assoc mdata ::wrap-error true))))
(defn- get-prefix
[nsname]
(let [[a b c] (str/split nsname ".")]
c))
(defn wrap-metrics
[handler]
(let [mdata (meta handler)
nsname (namespace (:spec mdata))
smname (name (:spec mdata))
prefix (get-prefix nsname)
sname (str prefix "/" smname)
props {:id (str/join "__" [prefix
(str/snake smname)
"response_time"])
:help (str "Service timing measures for: " sname ".")}]
(with-meta
(mtx/wrap-summary handler props)
(assoc mdata ::wrap-metrics true))))
(defn wrap
[handler]
(-> handler
(wrap-spec)
(wrap-error)
(wrap-metrics)))

View file

@ -5,16 +5,16 @@
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2019-2020 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.services.mutations
(:require
[uxbox.services.middleware :as middleware]
[uxbox.util.dispatcher :as uds]))
(uds/defservice handle
:dispatch-by ::type
:wrap [uds/wrap-spec
uds/wrap-error])
:wrap middleware/wrap)
(defmacro defmutation
[key & rest]

View file

@ -13,8 +13,9 @@
[ring.adapter.jetty9 :as jetty]
[uxbox.common.exceptions :as ex]
[uxbox.common.uuid :as uuid]
[uxbox.redis :as redis]
[uxbox.db :as db]
[uxbox.redis :as redis]
[uxbox.metrics :as mtx]
[uxbox.util.time :as dt]
[uxbox.util.transit :as t]))
@ -193,11 +194,20 @@
(jetty/send! conn (t/encode-str val))
(recur)))))
(defonce metrics-active-connections
(mtx/gauge {:id "notificatons__active_connections"
:help "Active connections to the notifications service."}))
(defonce metrics-message-counter
(mtx/counter {:id "notificatons__messages_counter"
:help "A total number of messages handled by the notifications service."}))
(defn websocket
[{:keys [file-id] :as params}]
(let [in (a/chan 32)
out (a/chan 32)]
{:on-connect (fn [conn]
(metrics-active-connections :inc)
(let [xf (map t/decode-str)
sub (redis/subscribe (str file-id) xf)
ws (WebSocket. conn in out sub nil params)]
@ -207,21 +217,19 @@
(a/close! sub))))
:on-error (fn [conn e]
;; (prn "websocket" :on-error e)
(a/close! out)
(a/close! in))
:on-close (fn [conn status-code reason]
;; (prn "websocket" :on-close status-code reason)
(metrics-active-connections :dec)
(a/close! out)
(a/close! in))
:on-text (fn [ws message]
(metrics-message-counter :inc)
(let [message (t/decode-str message)]
;; (prn "websocket" :on-text message)
(a/>!! in message)))
:on-bytes (fn [ws bytes offset len]
#_(prn "websocket" :on-bytes bytes))}))
:on-bytes (constantly nil)}))

View file

@ -2,16 +2,19 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.services.queries
(:require
[uxbox.services.middleware :as middleware]
[uxbox.util.dispatcher :as uds]))
(uds/defservice handle
:dispatch-by ::type
:wrap [uds/wrap-spec
uds/wrap-error])
:wrap middleware/wrap)
(defmacro defquery
[key & rest]

View file

@ -10,8 +10,6 @@
(ns uxbox.services.queries.icons
(:require
[clojure.spec.alpha :as s]
[promesa.core :as p]
[promesa.exec :as px]
[uxbox.common.exceptions :as ex]
[uxbox.common.spec :as us]
[uxbox.common.uuid :as uuid]

View file

@ -10,13 +10,12 @@
(ns uxbox.services.queries.images
(:require
[clojure.spec.alpha :as s]
[promesa.core :as p]
[uxbox.common.exceptions :as ex]
[uxbox.common.spec :as us]
[uxbox.db :as db]
[uxbox.images :as images]
[uxbox.services.queries.teams :as teams]
[uxbox.services.queries :as sq]))
[uxbox.services.queries :as sq]
[uxbox.services.queries.teams :as teams]))
(s/def ::id ::us/uuid)
(s/def ::name ::us/string)

View file

@ -16,6 +16,7 @@
[uxbox.common.spec :as us]
[uxbox.config :as cfg]
[uxbox.db :as db]
[uxbox.metrics :as mtx]
[uxbox.tasks.sendmail]
[uxbox.tasks.gc]
[uxbox.tasks.remove-media]
@ -68,3 +69,8 @@
([conn opts]
(s/assert ::impl/task-options opts)
(impl/submit! conn opts)))
(mtx/instrument-with-counter!
{:var #'submit!
:id "tasks__submit_counter"
:help "Absolute task submit counter."})

View file

@ -15,7 +15,7 @@
[uxbox.common.exceptions :as ex]
[uxbox.common.spec :as us]
[uxbox.db :as db]
[uxbox.media :as media]
[uxbox.metrics :as mtx]
[uxbox.util.storage :as ust]))
(s/def ::type keyword?)
@ -36,6 +36,11 @@
(db/with-atomic [conn db/pool]
(handle-deletion conn props)))
(mtx/instrument-with-summary!
{:var #'handler
:id "tasks__delete_object"
:help "Timing of remove-object task."})
(defmethod handle-deletion :image
[conn {:keys [id] :as props}]
(let [sql "delete from image where id=? and deleted_at is not null"]

View file

@ -15,7 +15,7 @@
[uxbox.common.exceptions :as ex]
[uxbox.common.spec :as us]
[uxbox.db :as db]
[uxbox.media :as media]
[uxbox.metrics :as mtx]
[uxbox.util.storage :as ust]))
(declare delete-profile-data)
@ -39,6 +39,11 @@
(log/warn "Profile " (:id profile)
"does not match constraints for deletion")))))
(mtx/instrument-with-summary!
{:var #'handler
:id "tasks__delete_profile"
:help "Timing of delete-profile task."})
(defn- delete-profile-data
[conn profile-id]
(log/info "Proceding to delete all data related to profile" profile-id)

View file

@ -15,6 +15,7 @@
[uxbox.common.exceptions :as ex]
[uxbox.common.spec :as us]
[uxbox.media :as media]
[uxbox.metrics :as mtx]
[uxbox.util.storage :as ust]))
(s/def ::path ::us/not-empty-string)
@ -28,3 +29,7 @@
(ust/delete! media/media-storage (:path props))
(log/debug "Media " (:path props) " removed.")))
(mtx/instrument-with-summary!
{:var #'handler
:id "tasks__remove_media"
:help "Timing of remove-media task."})

View file

@ -15,6 +15,7 @@
[uxbox.common.data :as d]
[uxbox.common.exceptions :as ex]
[uxbox.config :as cfg]
[uxbox.metrics :as mtx]
[uxbox.util.http :as http]))
(defmulti sendmail (fn [config email] (:sendmail-backend config)))
@ -94,3 +95,7 @@
[{:keys [props] :as task}]
(sendmail cfg/config props))
(mtx/instrument-with-summary!
{:var #'handler
:id "tasks__sendmail"
:help "Timing of sendmail task."})

View file

@ -20,22 +20,18 @@
(definterface IDispatcher
(^void add [key f]))
(defn- wrap-handler
[items handler]
(reduce #(%2 %1) handler items))
(deftype Dispatcher [reg attr wrap-fns]
(deftype Dispatcher [reg attr wrap]
IDispatcher
(add [this key f]
(let [f (wrap-handler wrap-fns f)]
(.put ^Map reg key f)
this))
(.put ^Map reg key (wrap f))
this)
clojure.lang.IDeref
(deref [_]
{:registry reg
:attr attr
:wrap-fns wrap-fns})
:wrap wrap})
clojure.lang.IFn
(invoke [_ params]
@ -100,36 +96,3 @@
`(do
(s/assert dispatcher? ~sym)
(add-method ~sym ~key ~f ~meta))))
(defn wrap-spec
[handler]
(let [mdata (meta handler)
spec (s/get-spec (:spec mdata))]
(if (nil? spec)
handler
(with-meta
(fn [params]
(let [result (s/conform spec params)]
(if (not= result ::s/invalid)
(handler result)
(let [data (s/explain-data spec params)]
(ex/raise :type :validation
:code :spec-validation
:explain (with-out-str
(expound/printer data))
:data (::s/problems data))))))
(assoc mdata ::wrap-spec true)))))
(defn wrap-error
[handler]
(let [mdata (meta handler)]
(with-meta
(fn [params]
(try
(handler params)
(catch Throwable error
(ex/raise :type :service-error
:name (:spec mdata)
:cause error))))
(assoc mdata ::wrap-error true))))

View file

@ -19,11 +19,9 @@
[clojure.repl :refer :all]
[criterium.core :refer [quick-bench bench with-progress-reporting]]
[clj-kondo.core :as kondo]
[promesa.core :as p]
[promesa.exec :as px]
[uxbox.migrations]
[uxbox.db :as db]
;; [uxbox.redis :as rd]
[uxbox.metrics :as mtx]
[uxbox.util.storage :as st]
[uxbox.util.time :as tm]
[uxbox.util.blob :as blob]