diff --git a/cabal.project b/cabal.project index c796a173d35..bb66fb516c2 100644 --- a/cabal.project +++ b/cabal.project @@ -48,7 +48,7 @@ package cryptonite flags: -support_rdrand package snap-server - flags: +openssl + flags: -openssl package bitvec flags: -simd @@ -62,8 +62,8 @@ constraints: allow-newer: , katip:Win32 + , ekg-wai:time -- IMPORTANT -- Do NOT add more source-repository-package stanzas here unless they are strictly -- temporary! Please read the section in CONTRIBUTING about updating dependencies. - diff --git a/cardano-tracer/CHANGELOG.md b/cardano-tracer/CHANGELOG.md index 3eba079f520..f055f08eaec 100644 --- a/cardano-tracer/CHANGELOG.md +++ b/cardano-tracer/CHANGELOG.md @@ -1,5 +1,16 @@ # ChangeLog +## 0.3 (September 20, 2024) + +* Abondon `snap` webserver in favour of `wai`/`warp` for Prometheus and EKG Monitoring. +* Add dynamic routing to EKG stores of all connected nodes. +* Derive URL compliant routes from connected node names (instead of plain node names). +* Remove the requirement of two distinct ports for the EKG backend (changing `hasEKG` config type). +* For optional RTView component only: Disable SSL/https connections. Force `snap-server` + dependency to build with `-flag -openssl`. +* Add JSON responses when listing connected nodes for both Prometheus and EKG Monitoring. +* Add consistency check for redundant port values in the config. + ## 0.2.4 (August 13, 2024) * `systemd` is enabled by default. To disable it use the cabal diff --git a/cardano-tracer/cardano-tracer.cabal b/cardano-tracer/cardano-tracer.cabal index cfc53723a54..7f6efc072d3 100644 --- a/cardano-tracer/cardano-tracer.cabal +++ b/cardano-tracer/cardano-tracer.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: cardano-tracer -version: 0.2.4 +version: 0.3 synopsis: A service for logging and monitoring over Cardano nodes description: A service for logging and monitoring over Cardano nodes. category: Cardano, @@ -155,11 +155,12 @@ library cardano-git-rev ^>=0.2.2 , cassava , threepenny-gui + , utf8-string , vector build-depends: aeson , async - , async-extras + , auto-update , bimap , blaze-html , bytestring @@ -168,21 +169,20 @@ library , containers , contra-tracer , directory - , ekg , ekg-core - , ekg-forward ^>= 0.5 + , ekg-forward >= 0.5 + , ekg-wai , extra , filepath + , http-types , mime-mail , optparse-applicative , ouroboros-network ^>= 0.17 , ouroboros-network-api , ouroboros-network-framework , signal + , slugify , smtp-mail ^>= 0.5 - , snap-blaze - , snap-core - , snap-server , stm , string-qq , text @@ -191,6 +191,8 @@ library , trace-forward , trace-resources , unordered-containers + , wai ^>= 3.2 + , warp ^>= 3.4 , yaml if flag(systemd) && os(linux) @@ -281,8 +283,7 @@ library demo-acceptor-lib exposed-modules: Cardano.Tracer.Test.Acceptor - build-depends: async-extras - , bytestring + build-depends: bytestring , cardano-tracer , containers , extra diff --git a/cardano-tracer/configuration/complete-example.json b/cardano-tracer/configuration/complete-example.json index f788e2bf821..eea7606ab44 100644 --- a/cardano-tracer/configuration/complete-example.json +++ b/cardano-tracer/configuration/complete-example.json @@ -6,16 +6,10 @@ }, "loRequestNum": 100, "ekgRequestFreq": 2, - "hasEKG": [ - { - "epHost": "127.0.0.1", - "epPort": 3100 - }, - { - "epHost": "127.0.0.1", - "epPort": 3101 - } - ], + "hasEKG": { + "epHost": "127.0.0.1", + "epPort": 3100 + }, "hasPrometheus": { "epHost": "127.0.0.1", "epPort": 3000 diff --git a/cardano-tracer/configuration/complete-example.yaml b/cardano-tracer/configuration/complete-example.yaml index 6afba4a652c..a4004864762 100644 --- a/cardano-tracer/configuration/complete-example.yaml +++ b/cardano-tracer/configuration/complete-example.yaml @@ -7,10 +7,8 @@ network: loRequestNum: 100 ekgRequestFreq: 2 hasEKG: -- epHost: 127.0.0.1 + epHost: 127.0.0.1 epPort: 3100 -- epHost: 127.0.0.1 - epPort: 3101 hasPrometheus: epHost: 127.0.0.1 epPort: 3000 diff --git a/cardano-tracer/demo/multi/active-tracer-config.json b/cardano-tracer/demo/multi/active-tracer-config.json index 00187fd1ac4..467a460f67f 100644 --- a/cardano-tracer/demo/multi/active-tracer-config.json +++ b/cardano-tracer/demo/multi/active-tracer-config.json @@ -8,16 +8,10 @@ "/run/user/1000/cardano-tracer-demo-3.sock" ] }, - "hasEKG": [ - { + "hasEKG": { "epHost": "127.0.0.1", "epPort": 3100 - }, - { - "epHost": "127.0.0.1", - "epPort": 3101 - } - ], + }, "hasPrometheus": { "epHost": "127.0.0.1", "epPort": 3000 diff --git a/cardano-tracer/demo/multi/passive-tracer-config.json b/cardano-tracer/demo/multi/passive-tracer-config.json index 7e06ca8dfd4..868781da7d2 100644 --- a/cardano-tracer/demo/multi/passive-tracer-config.json +++ b/cardano-tracer/demo/multi/passive-tracer-config.json @@ -4,16 +4,10 @@ "tag": "AcceptAt", "contents": "/run/user/1000/cardano-tracer-demo-1.sock" }, - "hasEKG": [ - { - "epHost": "127.0.0.1", - "epPort": 3100 - }, - { - "epHost": "127.0.0.1", - "epPort": 3101 - } - ], + "hasEKG": { + "epHost": "127.0.0.1", + "epPort": 3100 + }, "hasPrometheus": { "epHost": "127.0.0.1", "epPort": 3000 diff --git a/cardano-tracer/docs/cardano-tracer.md b/cardano-tracer/docs/cardano-tracer.md index 172e189cb4e..7331223c1a4 100644 --- a/cardano-tracer/docs/cardano-tracer.md +++ b/cardano-tracer/docs/cardano-tracer.md @@ -337,72 +337,135 @@ The fields `rpMaxAgeMinutes`, `rpMaxAgeHours` specify the lifetime of the log fi ## Prometheus -The optional field `hasPrometheus` specifies the host and port of the web page with metrics. For example: +At top-level route `/` Promtheus gives a list of connected nodes. + +The responses are either human-readable names (HTML) with clickable +links, or JSON mapping from connected node names to relative URLs, +depending on desired content type (`Accept:` header of the request). + +The routes dynamically depend on the connected nodes, the node names +are [sluggified](https://hackage.haskell.org/package/slugify). + +The optional field `hasPrometheus` specifies the host and port of the +web page with Prometheus metrics. For example: ``` "hasPrometheus": { "epHost": "127.0.0.1", - "epPort": 3000 + "epPort": 3200 } ``` -Here the web page is available at `http://127.0.0.1:3000`. Please note that if you skip this field, the web page will not be available. +With this example, the list of clickable identifiers of connected +nodes will be available at `http://127.0.0.1:3200`, such as: + +``` +* 127.0.0.1:30004 +* 127.0.0.1:30001 +* 127.0.0.1:30005 +* 127.0.0.1:30000 +* 127.0.0.1:30003 +* 127.0.0.1:30002 +* TxGenerator +``` + +Clicking an identifier will take you to its monitoring page. For +example clicking on `127.0.0.1:30004` displays the monitoring metrics +at `http://localhost:3200/12700130004`. -After you open `http://127.0.0.1:3000` in your browser, you will see the list of identifiers of connected nodes (or the warning message, if there are no connected nodes yet), for example: +Sending a HTTP GET request with a JSON Accept header gives the metrics +of the top-level route, or identifier as JSON. `jq '.'` pretty-prints +the JSON object. ``` -* tmp-forwarder.sock@0 -* tmp-forwarder.sock@1 -* tmp-forwarder.sock@2 +$ curl --silent -H "Accept: application/json" '127.0.0.1:3200' | jq '.' +{ + "127.0.0.1:30000": "/12700130000", + "127.0.0.1:30001": "/12700130001", + "127.0.0.1:30002": "/12700130002", + "127.0.0.1:30003": "/12700130003", + "127.0.0.1:30004": "/12700130004", + "127.0.0.1:30005": "/12700130005", + "TxGenerator": "/txgenerator" +} ``` -Each identifier is a hyperlink to the page where you will see the **current** list of metrics received from the corresponding node, in such a format: +The Promethus output is a map from Prometheus metric to value: ``` +$ curl '127.0.0.1:3200/12700130004' +blockNum_int 35 +rts_gc_init_cpu_ms 5 rts_gc_par_tot_bytes_copied 0 -rts_gc_num_gcs 2 -rts_gc_max_bytes_slop 15880 -rts_gc_num_bytes_usage_samples 1 -rts_gc_wall_ms 4005 -... -rts_gc_par_max_bytes_copied 0 -rts_gc_mutator_cpu_ms 57 -rts_gc_mutator_wall_ms 4004 -rts_gc_gc_cpu_ms 1 -rts_gc_cumulative_bytes_used 184824 +served_block_counter 31 +submissions_accepted_counter 2771 +density_real 5.7692307692307696e-2 +blocksForged_int 6 + ``` ## EKG Monitoring -The optional field `hasEKG` specifies the hosts and ports of two web pages: +At top-level route `/` EKG gives a list of connected nodes. + +The responses are either human-readable names (HTML) with clickable +links, or JSON mapping from connected node names to relative URLs, +depending on desired content type (`Accept:` header of the request). -1. the list of identifiers of connected nodes, -2. EKG monitoring page. +The routes dynamically depend on the connected nodes, the node names +are [sluggified](https://hackage.haskell.org/package/slugify). -For example, if you use JSON configuration file: +The optional field `hasEKG` specifies the host and port of the web +page with EKG metrics. For example: ``` -"hasEKG": [ - { - "epHost": "127.0.0.1", - "epPort": 3100 - }, - { - "epHost": "127.0.0.1", - "epPort": 3101 - } -] +"hasEKG": { + "epHost": "127.0.0.1", + "epPort": 3100 +} ``` -The page with the list of identifiers of connected nodes will be available at `http://127.0.0.1:3100`, for example: +With this example, the list of clickable identifiers of connected +nodes will be available at `http://127.0.0.1:3100`, such as: ``` -* tmp-forwarder.sock@0 -* tmp-forwarder.sock@1 -* tmp-forwarder.sock@2 +* 127.0.0.1:30004 +* 127.0.0.1:30001 +* 127.0.0.1:30005 +* 127.0.0.1:30000 +* 127.0.0.1:30003 +* 127.0.0.1:30002 +* TxGenerator ``` -Each identifier is a hyperlink, after clicking to it you will be redirected to `http://127.0.0.1:3101` where you will see EKG monitoring page for corresponding node. +Clicking an identifier will take you to its monitoring page. For +example clicking on `127.0.0.1:30004` displays the monitoring metrics +at `http://localhost:3100/12700130004`. + +Sending a HTTP GET request with a JSON Accept header gives the metrics +of an identifier as JSON. `jq '.'` pretty-prints the JSON object. + +``` +$ curl --silent -H 'Accept: application/json' '127.0.0.1:3100/12700130004' | jq '.' +{ + "ChainSync": { + "HeadersServed_counter": { + "type": "c", + "val": 24 + } + }, + "Mem": { + "resident_int": { + "type": "g", + "val": 91877376 + } + }, + "RTS": { + "alloc_int": { + "type": "g", + "val": 1014189896 + }, +``` ## Verbosity diff --git a/cardano-tracer/src/Cardano/Tracer/Acceptors/Utils.hs b/cardano-tracer/src/Cardano/Tracer/Acceptors/Utils.hs index a28e89c6a9f..a07f911f046 100644 --- a/cardano-tracer/src/Cardano/Tracer/Acceptors/Utils.hs +++ b/cardano-tracer/src/Cardano/Tracer/Acceptors/Utils.hs @@ -1,7 +1,6 @@ {-# LANGUAGE NamedFieldPuns #-} -#if RTVIEW {-# LANGUAGE OverloadedStrings #-} -#endif +{-# LANGUAGE TupleSections #-} module Cardano.Tracer.Acceptors.Utils ( prepareDataPointRequestor @@ -26,6 +25,7 @@ import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO) import qualified Data.Bimap as BM import qualified Data.Map.Strict as M import qualified Data.Set as S +import Data.Time.Clock.POSIX (getPOSIXTime) #if RTVIEW import Data.Time.Clock.System (getSystemTime, systemToUTCTime) #endif @@ -51,12 +51,26 @@ prepareMetricsStores -> IO (EKG.Store, TVar MetricsLocalStore) prepareMetricsStores TracerEnv{teConnectedNodes, teAcceptedMetrics} connId = do addConnectedNode teConnectedNodes connId - storesForNewNode <- (,) <$> EKG.newStore - <*> newTVarIO emptyMetricsLocalStore - atomically $ - modifyTVar' teAcceptedMetrics $ M.insert (connIdToNodeId connId) storesForNewNode + store <- EKG.newStore + + EKG.registerCounter "ekg.server_timestamp_ms" getTimeMs store + storesForNewNode <- (store ,) <$> newTVarIO emptyMetricsLocalStore + + atomically do + modifyTVar' teAcceptedMetrics do + M.insert (connIdToNodeId connId) storesForNewNode + return storesForNewNode + where + -- forkServer definition of `getTimeMs'. The ekg frontend relies + -- on the "ekg.server_timestamp_ms" metric being in every + -- store. While forkServer adds that that automatically we must + -- manually add it. + -- url + -- + https://github.com/tvh/ekg-wai/blob/master/System/Remote/Monitoring/Wai.hs#L237-L238 + getTimeMs = (round . (* 1000)) `fmap` getPOSIXTime + addConnectedNode :: ConnectedNodes -> ConnectionId LocalAddress diff --git a/cardano-tracer/src/Cardano/Tracer/Configuration.hs b/cardano-tracer/src/Cardano/Tracer/Configuration.hs index 5dc607b1a1b..ab4535546db 100644 --- a/cardano-tracer/src/Cardano/Tracer/Configuration.hs +++ b/cardano-tracer/src/Cardano/Tracer/Configuration.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} @@ -9,6 +10,7 @@ module Cardano.Tracer.Configuration ( Address (..) , Endpoint (..) + , setEndpoint , LogFormat (..) , LogMode (..) , LoggingParams (..) @@ -24,19 +26,23 @@ import qualified Cardano.Logging.Types as Log import Control.Applicative ((<|>)) import Data.Aeson (FromJSON (..), ToJSON, withObject, (.:)) import Data.Fixed (Pico) +import Data.Function ((&)) import Data.Functor ((<&>)) -import Data.List (intercalate) +import Data.List (intercalate, nub) import Data.List.Extra (notNull) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE import Data.Map.Strict (Map) import Data.Maybe (catMaybes) +import Data.String (fromString) import Data.Text (Text) import Data.Word (Word16, Word32, Word64) import Data.Yaml (decodeFileEither) import GHC.Generics (Generic) import System.Exit (die) +import Network.Wai.Handler.Warp (HostPreference, Port, Settings, setHost, setPort) + -- | Only local socket is supported, to avoid unauthorized connections. newtype Address = LocalSocket FilePath deriving stock (Eq, Generic, Show) @@ -45,11 +51,17 @@ newtype Address = LocalSocket FilePath -- | Endpoint for internal services. data Endpoint = Endpoint { epHost :: !String - , epPort :: !Word16 + , epPort :: !Port } deriving stock (Eq, Generic, Show) deriving anyclass (FromJSON, ToJSON) +-- | Endpoint {host, port} acting on Settings: setting host and port. +setEndpoint :: Endpoint -> (Settings -> Settings) +setEndpoint Endpoint{epHost, epPort} settings = settings + & setPort (epPort :: Port) + & setHost (fromString epHost :: HostPreference) + -- | Parameters of rotation mechanism for logs. data RotationParams = RotationParams { rpFrequencySecs :: !Word32 -- ^ Rotation period, in seconds. @@ -113,7 +125,7 @@ data TracerConfig = TracerConfig , network :: !Network -- ^ How cardano-tracer will be connected to node(s). , loRequestNum :: !(Maybe Word16) -- ^ How many 'TraceObject's will be asked in each request. , ekgRequestFreq :: !(Maybe Pico) -- ^ How often to request for EKG-metrics, in seconds. - , hasEKG :: !(Maybe (Endpoint, Endpoint)) -- ^ Endpoint for EKG web-page (list of nodes, monitoring). + , hasEKG :: !(Maybe Endpoint) -- ^ Endpoint for EKG web-page. , hasPrometheus :: !(Maybe Endpoint) -- ^ Endpoint for Prometheus web-page. , hasRTView :: !(Maybe Endpoint) -- ^ Endpoint for RTView web-page. -- | Socket for tracer's to reforward on. Second member of the triplet is the list of prefixes to reforward. @@ -137,8 +149,8 @@ readTracerConfig pathToConfig = decodeFileEither pathToConfig >>= \case Left e -> die $ "Invalid tracer's configuration: " <> show e Right (config :: TracerConfig) -> - case checkMeaninglessValues config of - Left problems -> die $ "Tracer's configuration is meaningless: " <> problems + case wellFormed config of + Left problems -> die $ "Tracer's configuration is ill-formed: " <> problems Right{} -> return (nubLogging config) where @@ -148,8 +160,8 @@ readTracerConfig pathToConfig = { logging = NE.nub logging } -checkMeaninglessValues :: TracerConfig -> Either String () -checkMeaninglessValues TracerConfig +wellFormed :: TracerConfig -> Either String () +wellFormed TracerConfig { network , hasEKG , hasPrometheus @@ -160,23 +172,35 @@ checkMeaninglessValues TracerConfig then Right () else Left $ intercalate ", " problems where + problems :: [String] problems = catMaybes [ case network of AcceptAt addr -> check "AcceptAt is empty" $ nullAddress addr - ConnectTo addrs -> check "ConnectTo are empty" $ null . NE.filter (not . nullAddress) $ addrs - , check "empty logRoot(s)" $ notNull . NE.filter invalidFileMode $ logging - , (check "no host(s) in hasEKG" . nullEndpoints) =<< hasEKG - , (check "no host in hasPrometheus" . nullEndpoint) =<< hasPrometheus - , (check "no host in hasRTView" . nullEndpoint) =<< hasRTView + ConnectTo addrs -> check "ConnectTo are empty" $ null (NE.filter (not . nullAddress) addrs) + , check "empty logRoot(s)" $ notNull (NE.filter invalidFileMode logging) + , check "duplicate ports in config" $ hasDuplicates ports + , check "no host(s) in hasEKG" . nullEndpoint =<< hasEKG + , check "no host in hasPrometheus" . nullEndpoint =<< hasPrometheus + , check "no host in hasRTView" . nullEndpoint =<< hasRTView ] - check msg cond = if cond then Just msg else Nothing + ports :: [Port] + ports = epPort <$> catMaybes [hasEKG, hasPrometheus, hasRTView] - nullAddress (LocalSocket p) = null p + check :: String -> Bool -> Maybe String + check msg True = Just msg + check _ False = Nothing - nullEndpoint (Endpoint h _) = null h + nullAddress :: Address -> Bool + nullAddress (LocalSocket address) = null address - nullEndpoints (ep1, ep2) = nullEndpoint ep1 || nullEndpoint ep2 + nullEndpoint :: Endpoint -> Bool + nullEndpoint (Endpoint host _port) = null host + invalidFileMode :: LoggingParams -> Bool invalidFileMode (LoggingParams root FileMode _) = null root invalidFileMode (LoggingParams _ JournalMode _) = False + +-- | Checks if a list contains duplicate elements. +hasDuplicates :: Ord a => [a] -> Bool +hasDuplicates xs = nub xs /= xs diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs index 5b5074adb74..51b882b23cd 100644 --- a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -7,51 +6,23 @@ module Cardano.Tracer.Handlers.Metrics.Monitoring ( runMonitoringServer ) where +import Prelude hiding (head) import Cardano.Tracer.Configuration import Cardano.Tracer.Environment -#if RTVIEW -import Cardano.Tracer.Handlers.SSL.Certs -#endif import Cardano.Tracer.MetaTrace import Cardano.Tracer.Types -import Control.Concurrent (ThreadId) -import Control.Concurrent.STM (atomically) -import Control.Concurrent.STM.TMVar (TMVar, newEmptyTMVarIO, putTMVar, tryReadTMVar) -import Control.Concurrent.STM.TVar (readTVarIO) -#if RTVIEW -import Control.Monad (forM, void) -#endif -import Control.Monad.Extra (whenJust) -import Control.Monad.IO.Class (liftIO) -#if !RTVIEW -import Data.Foldable -import Data.Function ((&)) -#endif -import qualified Data.Map.Strict as M -import qualified Data.Set as S -#if !RTVIEW -import Data.String -#endif import qualified Data.Text as T -import Data.Text.Encoding (encodeUtf8) -import System.Remote.Monitoring (forkServerWith, serverThreadId) import System.Time.Extra (sleep) -#if !RTVIEW -import System.IO.Unsafe (unsafePerformIO) -import Text.Blaze.Html5 hiding (title) -import Text.Blaze.Html5.Attributes -#endif -#if RTVIEW -import qualified Graphics.UI.Threepenny as UI -import Graphics.UI.Threepenny.Core (Element, UI, set, (#), (#+)) -#else -import Snap.Blaze (blaze) -import Snap.Core (Snap, route) -import Snap.Http.Server (Config, ConfigLog (..), defaultConfig, setAccessLog, setBind, - setErrorLog, setPort, simpleHttpServe) -#endif +import qualified Cardano.Tracer.Handlers.Metrics.Utils as Utils +import Cardano.Tracer.Handlers.Metrics.Utils (renderListOfConnectedNodes) +import Data.ByteString.Builder (stringUtf8) +import Network.HTTP.Types +import Network.Wai +import Network.Wai.Handler.Warp (runSettings, defaultSettings) +import qualified System.Metrics as EKG +import System.Remote.Monitoring.Wai -- | 'ekg' package allows to run only one EKG server, to display only one web page -- for particular EKG.Store. Since 'cardano-tracer' can be connected to any number @@ -60,165 +31,46 @@ import Snap.Http.Server (Config, ConfigLog (..), defaultConfig, setAcc -- redirected to the monitoring web page (the second 'Endpoint') built by 'ekg' package. -- This page will display the metrics received from that node. -- --- If the user returns to the first web page and clicks to another node's href, + -- If the user returns to the first web page and clicks to another node's href, -- the EKG server will be restarted and the monitoring page will display the metrics -- received from that node. runMonitoringServer :: TracerEnv - -> (Endpoint, Endpoint) -- ^ (web page with list of connected nodes, EKG web page). + -> Endpoint -- ^ (web page with list of connected nodes, EKG web page). + -> IO Utils.RouteDictionary -> IO () -#if RTVIEW -runMonitoringServer tracerEnv (endpoint@(Endpoint listHost listPort), monitorEP) = do +runMonitoringServer TracerEnv{teTracer} endpoint computeRoutes_autoUpdate = do -- Pause to prevent collision between "Listening"-notifications from servers. sleep 0.2 - (certFile, keyFile) <- placeDefaultSSLFiles tracerEnv - traceWith (teTracer tracerEnv) TracerStartedMonitoring + traceWith teTracer TracerStartedMonitoring { ttMonitoringEndpoint = endpoint , ttMonitoringType = "list" } - UI.startGUI (config certFile keyFile) \window -> do - void $ return window # set UI.title "EKG Monitoring Nodes" - void $ mkPageBody window tracerEnv monitorEP - where - config cert key = - UI.defaultConfig - { UI.jsLog = const $ return () - , UI.jsUseSSL = - Just $ UI.ConfigSSL - { UI.jsSSLBind = encodeUtf8 $ T.pack listHost - , UI.jsSSLPort = fromIntegral listPort - , UI.jsSSLCert = cert - , UI.jsSSLKey = key - , UI.jsSSLChainCert = False - } - } -#else -runMonitoringServer tracerEnv (endpoint@(Endpoint listHost listPort), monitorEP) = do - -- Pause to prevent collision between "Listening"-notifications from servers. - sleep 0.2 - traceWith (teTracer tracerEnv) TracerStartedMonitoring - { ttMonitoringEndpoint = endpoint - , ttMonitoringType = "list" - } - simpleHttpServe config do - route - [ ("/", renderEkg) - ] - where - TracerEnv{teConnectedNodes} = tracerEnv + dummyStore <- EKG.newStore + runSettings (setEndpoint endpoint defaultSettings) do + renderEkg dummyStore computeRoutes_autoUpdate - config :: Config Snap () - config = defaultConfig - & setErrorLog ConfigNoLog - & setAccessLog ConfigNoLog - & setBind (encodeUtf8 (T.pack listHost)) - & setPort (fromIntegral listPort) +renderEkg :: EKG.Store -> IO Utils.RouteDictionary -> Application +renderEkg dummyStore computeRoutes_autoUpdate request send = do + routeDictionary :: Utils.RouteDictionary <- + computeRoutes_autoUpdate - renderEkg :: Snap () - renderEkg = do - nodes <- liftIO $ S.toList <$> readTVarIO teConnectedNodes - -- HACK - case nodes of - [] -> - pure () - nodeId:_nodes -> liftIO do - restartEKGServer tracerEnv nodeId monitorEP currentServerHack - blaze do - docTypeHtml do - ekgHtml monitorEP nodes + let nodeNames :: [NodeName] + nodeNames = Utils.nodeNames routeDictionary -{-# NOINLINE currentServerHack #-} --- | There is supposed to be one EKG server per port. The desired EKG --- server for the connected node gets restarted always on the same --- port. We limit functionality to only run one EKG server, this will --- be resolved in a future PR. -currentServerHack :: CurrentEKGServer -currentServerHack = unsafePerformIO newEmptyTMVarIO - -ekgHtml - :: Endpoint - -> [NodeId] - -> Html -ekgHtml (Endpoint monitorHost monitorPort) = \case - [] -> - toHtml @T.Text "ekgHtml: There are no connected nodes yet" - connectedNodes -> do - for_ connectedNodes \(NodeId anId) -> - li do - a ! href (fromString ("http://" <> monitorHost <> ":" <> show monitorPort)) - ! target "_blank" - ! title "Open EKG monitor page for this node" - $ toHtml anId -#endif - -type CurrentEKGServer = TMVar (NodeId, ThreadId) -#if RTVIEW --- | The first web page contains only the list of hrefs --- corresponding to currently connected nodes. -mkPageBody - :: UI.Window - -> TracerEnv - -> Endpoint - -> UI Element -mkPageBody window tracerEnv mEP@(Endpoint monitorHost monitorPort) = do - nodes <- liftIO $ S.toList <$> readTVarIO teConnectedNodes - nodesHrefs <- - if null nodes - then UI.string "There are no connected nodes yet" - else do - currentServer :: CurrentEKGServer <- liftIO newEmptyTMVarIO - nodesLinks <- - forM nodes \nodeId@(NodeId anId) -> do - nodeLink <- - UI.li #+ - [ UI.anchor # set UI.href ("http://" <> monitorHost <> ":" <> show monitorPort) - # set UI.target "_blank" - # set UI.title__ "Open EKG monitor page for this node" - # set UI.text (T.unpack anId) - ] - void $ UI.on UI.click nodeLink $ const do - liftIO do - restartEKGServer - tracerEnv nodeId mEP currentServer - return $ UI.element nodeLink - UI.ul #+ nodesLinks - UI.getBody window #+ [ UI.element nodesHrefs ] - where - TracerEnv{teConnectedNodes} = tracerEnv -#endif - --- | After clicking on the node's href, the user will be redirected to the monitoring page --- which is rendered by 'ekg' package. But before, we have to check if EKG server is --- already launched, and if so, restart the server if needed. -restartEKGServer - :: TracerEnv - -> NodeId - -> Endpoint - -> CurrentEKGServer - -> IO () -restartEKGServer TracerEnv{teAcceptedMetrics, teTracer} newNodeId - endpoint@(Endpoint monitorHost monitorPort) currentServer = do - metrics <- readTVarIO teAcceptedMetrics - whenJust (metrics M.!? newNodeId) \(storeForSelectedNode, _) -> - atomically (tryReadTMVar currentServer) >>= \case - Just (_curNodeId, _sThread) -> - -- TODO: Currently we cannot restart EKG server, - -- please see https://github.com/tibbe/ekg/issues/87 - return () - -- unless (newNodeId == curNodeId) do - -- killThread sThread - -- runEKGAndSave storeForSelectedNode - Nothing -> - -- Current server wasn't stored yet, it's a first click on the href. - runEKGAndSave storeForSelectedNode - where - runEKGAndSave store = do - traceWith teTracer TracerStartedMonitoring - { ttMonitoringEndpoint = endpoint - , ttMonitoringType = "monitor" - } - ekgServer <- forkServerWith store - (encodeUtf8 . T.pack $ monitorHost) - (fromIntegral monitorPort) - atomically do - putTMVar currentServer (newNodeId, serverThreadId ekgServer) + case pathInfo request of + [] -> + send $ responseLBS status200 [] (renderListOfConnectedNodes "EKG metrics" nodeNames) + route:rest + | Just (store :: EKG.Store, _ :: NodeName) + <- lookup route (Utils.getRouteDictionary routeDictionary) + -> monitor store request { pathInfo = rest } send + -- all endings in ekg-wai's asset/ folder + | any (`T.isSuffixOf` route) [".html", ".css", ".js", ".png"] + -- we actually need an empty dummy store here, as we're sure monitor will internally invoke the staticApp to serve the assets + -> monitor dummyStore request send + | otherwise + -> send $ responseBuilder status404 [] do + "Not found: " + <> stringUtf8 (show route) + <> "\n" <> stringUtf8 (show nodeNames) diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs index 4290e17a8da..3358639eb5c 100644 --- a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -10,32 +9,26 @@ module Cardano.Tracer.Handlers.Metrics.Prometheus import Cardano.Tracer.Configuration import Cardano.Tracer.Environment import Cardano.Tracer.MetaTrace -import Cardano.Tracer.Types -import Cardano.Tracer.Utils import Prelude hiding (head) -import Control.Concurrent.STM.TVar (readTVarIO) -import Control.Monad (forever) -import Control.Monad.IO.Class (liftIO) -import qualified Data.Bimap as BM -import Data.Function ((&)) +import Data.ByteString.Builder (stringUtf8) import Data.Functor ((<&>)) -import qualified Data.HashMap.Strict as HM -import qualified Data.Map.Strict as M -import Data.String (IsString (..)) +import Data.Map.Strict (Map) import Data.Text (Text) -import qualified Data.Text as T -import Data.Text.Encoding (decodeUtf8, encodeUtf8) +import Network.HTTP.Types +import Network.Wai hiding (responseHeaders) +import Network.Wai.Handler.Warp (runSettings, defaultSettings) import System.Metrics (Sample, Value (..), sampleAll) import System.Time.Extra (sleep) -import Text.Blaze.Html5 hiding (map) -import Text.Blaze.Html5.Attributes hiding (title) - -import Snap.Blaze (blaze) -import Snap.Core (Snap, getRequest, route, rqParams, writeText) -import Snap.Http.Server (Config, ConfigLog (..), defaultConfig, setAccessLog, setBind, - setErrorLog, setPort, simpleHttpServe) +import qualified Cardano.Tracer.Handlers.Metrics.Utils as Utils +import qualified Data.ByteString as ByteString +import qualified Data.HashMap.Strict as HM +import qualified Data.Map.Strict as Map +import qualified Data.Text as T +import qualified Data.Text.Lazy as Lazy.Text +import qualified Data.Text.Lazy.Encoding as Lazy.Text +import qualified System.Metrics as EKG -- | Runs simple HTTP server that listens host and port and returns -- the list of currently connected nodes in such a format: @@ -59,84 +52,77 @@ import Snap.Http.Server (Config, ConfigLog (..), defaultConfig, setAcc runPrometheusServer :: TracerEnv -> Endpoint + -> IO Utils.RouteDictionary -> IO () -runPrometheusServer tracerEnv endpoint@(Endpoint host port) = forever do +runPrometheusServer tracerEnv endpoint computeRoutes_autoUpdate = do -- Pause to prevent collision between "Listening"-notifications from servers. sleep 0.1 -- If everything is okay, the function 'simpleHttpServe' never returns. -- But if there is some problem, it never throws an exception, but just stops. -- So if it stopped - it will be re-started. - traceWith (teTracer tracerEnv) TracerStartedPrometheus + traceWith teTracer TracerStartedPrometheus { ttPrometheusEndpoint = endpoint } - simpleHttpServe config do - route - [ ("/", renderListOfConnectedNodes) - , ("/:nodename", renderMetricsFromNode) - ] - sleep 1.0 - where - TracerEnv{teConnectedNodesNames, teAcceptedMetrics} = tracerEnv - - config :: Config Snap () - config = defaultConfig - & setErrorLog ConfigNoLog - & setAccessLog ConfigNoLog - & setBind (encodeUtf8 (T.pack host)) - & setPort (fromIntegral port) - - renderListOfConnectedNodes :: Snap () - renderListOfConnectedNodes = do - nIdsWithNames <- liftIO $ readTVarIO teConnectedNodesNames - if BM.null nIdsWithNames - then writeText "There are no connected nodes yet." - else blaze . mkPage . map mkHref $ BM.toList nIdsWithNames - - mkHref (_, nodeName) = - a ! href (fromString $ "http://" <> host <> ":" <> show port <> "/" <> nodeName') - $ toHtml nodeName' - where - nodeName' = T.unpack nodeName - - mkPage hrefs = html $ do - head . title $ "Prometheus metrics" - body . ul $ mapM_ li hrefs - - renderMetricsFromNode :: Snap () - renderMetricsFromNode = do - reqParams <- rqParams <$> getRequest - case M.lookup "nodename" reqParams of - Just [nodeName] -> do - liftIO (askNodeId tracerEnv $ decodeUtf8 nodeName) >>= \case - Nothing -> writeText "No such a node!" - Just anId -> writeText =<< liftIO (getMetricsFromNode tracerEnv anId teAcceptedMetrics) - _ -> writeText "No such a node!" + runSettings (setEndpoint endpoint defaultSettings) do + renderPrometheus computeRoutes_autoUpdate metricsComp where + + TracerEnv + { teTracer + , teConfig = TracerConfig { metricsComp } + } = tracerEnv + +renderPrometheus :: IO Utils.RouteDictionary -> Maybe (Map Text Text) -> Application +renderPrometheus computeRoutes_autoUpdate metricsComp request send = do + routeDictionary :: Utils.RouteDictionary <- + computeRoutes_autoUpdate + + let header :: RequestHeaders + header = requestHeaders request + + let wantsJson :: Bool + wantsJson = all @Maybe ("application/json" `ByteString.isInfixOf`) (lookup hAccept header) + + let responseHeaders :: ResponseHeaders + responseHeaders = [(hContentType, if wantsJson then "application/json" else "text/html")] + + case pathInfo request of + + [] -> + send $ responseLBS status200 responseHeaders if wantsJson + then Utils.renderJson routeDictionary + else Utils.renderListOfConnectedNodes "Prometheus metrics" (Utils.nodeNames routeDictionary) + + route:_ + | Just (store :: EKG.Store, _) <- lookup route (Utils.getRouteDictionary routeDictionary) + -> do metrics <- getMetricsFromNode metricsComp store + send $ responseLBS status200 [(hContentType, "text/plain")] (Lazy.Text.encodeUtf8 (Lazy.Text.fromStrict metrics)) + + -- all endings in ekg-wai's asset/ folder + | otherwise + -> send $ responseBuilder status404 [(hContentType, "text/plain")] do + "Not found: " + <> stringUtf8 (show route) type MetricName = Text type MetricValue = Text type MetricsList = [(MetricName, MetricValue)] getMetricsFromNode - :: TracerEnv - -> NodeId - -> AcceptedMetrics + :: Maybe (Map Text Text) + -> EKG.Store -> IO Text -getMetricsFromNode tracerEnv nodeId acceptedMetrics = - readTVarIO acceptedMetrics >>= - (\case - Nothing -> - return "No such a node!" - Just (ekgStore, _) -> - sampleAll ekgStore <&> renderListOfMetrics . getListOfMetrics - ) . M.lookup nodeId +getMetricsFromNode metricsComp ekgStore = + sampleAll ekgStore <&> renderListOfMetrics . getListOfMetrics where + getListOfMetrics :: Sample -> MetricsList getListOfMetrics = - metricsCompatibility - . filter (not . T.null . fst) - . map metricsWeNeed - . HM.toList + metricsCompatibility + . filter (not . T.null . fst) + . map metricsWeNeed + . HM.toList + metricsWeNeed :: (Text, Value) -> (Text, Text) metricsWeNeed (mName, mValue) = case mValue of Counter c -> (mName, T.pack $ show c) @@ -144,23 +130,24 @@ getMetricsFromNode tracerEnv nodeId acceptedMetrics = Label l -> (mName, l) _ -> ("", "") -- 'ekg-forward' doesn't support 'Distribution' yet. + metricsCompatibility :: MetricsList -> MetricsList + metricsCompatibility metricsList = + case metricsComp of + Nothing -> metricsList + Just mmap -> foldl (\ accu p'@(mn,mv) -> case Map.lookup mn mmap of + Nothing -> p' : accu + Just rep -> p' : (rep,mv) : accu) + [] + metricsList + renderListOfMetrics :: MetricsList -> Text renderListOfMetrics [] = "No metrics were received from this node." renderListOfMetrics mList = T.intercalate "\n" $ map (\(mName, mValue) -> prepareName mName <> " " <> mValue) mList + prepareName :: Text -> Text prepareName = T.filter (`elem` (['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ ['_'])) . T.replace " " "_" . T.replace "-" "_" . T.replace "." "_" - - metricsCompatibility :: MetricsList -> MetricsList - metricsCompatibility metricsList = - case metricsComp (teConfig tracerEnv) of - Nothing -> metricsList - Just mmap -> foldl (\ accu p'@(mn,mv) -> case M.lookup mn mmap of - Nothing -> p' : accu - Just rep -> p' : (rep,mv) : accu) - [] - metricsList diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Servers.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Servers.hs index 0a50e856fd3..19e97fdd4c2 100644 --- a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Servers.hs +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Servers.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE CPP #-} @@ -9,10 +10,12 @@ import Cardano.Tracer.Configuration import Cardano.Tracer.Environment import Cardano.Tracer.Handlers.Metrics.Monitoring import Cardano.Tracer.Handlers.Metrics.Prometheus +import qualified Cardano.Tracer.Handlers.Metrics.Utils as Utils +import Cardano.Tracer.Utils (sequenceConcurrently_) -import Control.Concurrent.Async.Extra (sequenceConcurrently) -import Control.Monad (void) +import Control.AutoUpdate import Data.Maybe (catMaybes) +import Control.Monad (unless) -- | Runs metrics servers if needed: -- @@ -22,10 +25,22 @@ import Data.Maybe (catMaybes) runMetricsServers :: TracerEnv -> IO () -runMetricsServers tracerEnv = void do sequenceConcurrently servers +runMetricsServers tracerEnv = do + unless (null servers) do + computeRoutes_autoUpdate :: IO Utils.RouteDictionary <- + mkAutoUpdate defaultUpdateSettings + { updateAction = Utils.computeRoutes tracerEnv + , updateFreq = 1_000_000 -- 1/sec + } + + sequenceConcurrently_ do + servers `routing` computeRoutes_autoUpdate where - servers :: [IO ()] + routing :: [IO Utils.RouteDictionary -> a] -> IO Utils.RouteDictionary -> [a] + routing = sequence + + servers :: [IO Utils.RouteDictionary -> IO ()] servers = catMaybes [ runPrometheusServer tracerEnv <$> hasPrometheus , runMonitoringServer tracerEnv <$> hasEKG diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs index 5f786d2c0f7..8e0168cfa74 100644 --- a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs @@ -1,15 +1,42 @@ +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} + module Cardano.Tracer.Handlers.Metrics.Utils ( MetricName , MetricValue , MetricsList + , RouteDictionary(..) , getListOfMetrics + , renderListOfConnectedNodes + , renderJson + , nodeNames + , computeRoutes ) where -import qualified Data.HashMap.Strict as HM +import qualified Data.ByteString.Lazy as Lazy import Data.Maybe (mapMaybe) +import Data.Foldable (for_) +import qualified Data.HashMap.Strict as HM +import qualified Data.Map as Map +import Data.Map (Map) import Data.Text (Text) import qualified Data.Text as T +import Prelude hiding (head) +import qualified Data.Bimap as Bimap + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TVar (readTVar) +import Data.Aeson (encode) +import Cardano.Tracer.Environment (TracerEnv(..)) +import qualified System.Metrics as EKG +import Cardano.Tracer.Types (NodeName, NodeId, MetricsStores) import System.Metrics (Store, Value (..), sampleAll) +import Text.Blaze.Html (Html) +import Text.Blaze.Html.Renderer.Utf8 (renderHtml) +import Text.Blaze.Html5 (Markup, a, li, ul, body, title, head, (!), textValue, html, toHtml) -- hiding (map) +import Text.Blaze.Html5.Attributes hiding (title) +import Text.Slugify (slugify) + type MetricName = Text type MetricValue = Text @@ -24,3 +51,55 @@ getListOfMetrics = fmap (mapMaybe metricsWeNeed . HM.toList) . sampleAll Gauge g -> Just (mName, T.pack $ show g) Label l -> Just (mName, l) _ -> Nothing -- 'ekg-forward' doesn't support 'Distribution' yet. + +newtype RouteDictionary = RouteDictionary + { getRouteDictionary :: [(Text, (EKG.Store, NodeName))] + } + +renderListOfConnectedNodes :: Text -> [NodeName] -> Lazy.ByteString +renderListOfConnectedNodes metricsTitle nodenames + | [] <- nodenames + = "There are no connected nodes yet." + | otherwise + = renderHtml do mkPage mkHref nodenames + + where + mkHref :: NodeName -> Markup + mkHref nodeName = + a ! href (textValue ("/" <> slugify nodeName)) + $ toHtml nodeName' + where + nodeName' = T.unpack nodeName + + mkPage :: (NodeName -> Markup) -> [NodeName] -> Html + mkPage f hrefs = html do + head $ title $ toHtml metricsTitle + body $ ul $ for_ hrefs (li . f) + +renderJson :: RouteDictionary -> Lazy.ByteString +renderJson (RouteDictionary routeDict) = encode do + Map.fromList + [ (nodeName, "/" <> slug) + | (slug, (_store, nodeName)) <- routeDict + ] + +nodeNames :: RouteDictionary -> [NodeName] +nodeNames (RouteDictionary routeDict) = map (snd . snd) routeDict + +computeRoutes :: TracerEnv -> IO RouteDictionary +computeRoutes TracerEnv{teConnectedNodesNames, teAcceptedMetrics} = atomically do + nIdsWithNames :: Map NodeId NodeName <- + Bimap.toMap <$> readTVar teConnectedNodesNames + + acceptedMetrics :: Map NodeId MetricsStores <- + readTVar teAcceptedMetrics + + let mapFromNodeId :: Map NodeId (NodeName, MetricsStores) + mapFromNodeId = Map.intersectionWith (,) nIdsWithNames acceptedMetrics + + routes :: [(Text, (EKG.Store, NodeName))] + routes = [ (slugify nodeName, (metric, nodeName)) + | (nodeName, (metric, _)) <- Map.elems mapFromNodeId + ] + + pure (RouteDictionary routes) diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs index cd2ce634db6..d8a25cb7d8f 100644 --- a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs @@ -15,15 +15,13 @@ import Cardano.Tracer.Handlers.RTView.State.Last import Cardano.Tracer.Handlers.RTView.UI.HTML.Main import Cardano.Tracer.Handlers.RTView.Update.EraSettings import Cardano.Tracer.Handlers.RTView.Update.Historical -import Cardano.Tracer.Handlers.SSL.Certs import Cardano.Tracer.Handlers.State.TraceObjects import Cardano.Tracer.MetaTrace +import Cardano.Tracer.Utils (sequenceConcurrently_) -import Control.Concurrent.Async.Extra (sequenceConcurrently) -import Control.Monad (void) import Control.Monad.Extra (whenJust) -import qualified Data.Text as T -import Data.Text.Encoding (encodeUtf8) +import Data.ByteString.UTF8 (fromString) +import Network.Wai.Handler.Warp (Port) import System.Time.Extra (sleep) import qualified Graphics.UI.Threepenny as UI @@ -42,8 +40,6 @@ runRTView tracerEnv@TracerEnv{teTracer} tracerEnvRTView = traceWith teTracer TracerStartedRTView -- Pause to prevent collision between "Listening"-notifications from servers. sleep 0.3 - -- Get paths to default SSL files for config. - (certFile, keyFile) <- placeDefaultSSLFiles tracerEnv -- Initialize displayed stuff outside of main page renderer, -- to be able to update corresponding elements after page reloading. displayedElements <- initDisplayedElements @@ -55,8 +51,8 @@ runRTView tracerEnv@TracerEnv{teTracer} tracerEnvRTView = lastResources <- initLastResources eraSettings <- initErasSettings - void . sequenceConcurrently $ - [ UI.startGUI (config host port certFile keyFile) $ + sequenceConcurrently_ + [ UI.startGUI (config host port) $ mkMainPage tracerEnv tracerEnvRTView @@ -73,16 +69,12 @@ runRTView tracerEnv@TracerEnv{teTracer} tracerEnvRTView = TracerConfig{network, logging, hasRTView} = teConfig tracerEnv -- RTView's web page is available via 'https://' url only. - config h p cert key = + config :: String -> Port -> UI.Config + config host port = UI.defaultConfig - { UI.jsLog = const $ return () -- To hide 'threepenny-gui' internal messages. + { UI.jsAddr = Just (fromString host) + , UI.jsPort = Just port + , UI.jsLog = const $ return () -- To hide 'threepenny-gui' internal messages. , UI.jsWindowReloadOnDisconnect = False - , UI.jsUseSSL = - Just $ UI.ConfigSSL - { UI.jsSSLBind = encodeUtf8 $ T.pack h - , UI.jsSSLPort = fromIntegral p - , UI.jsSSLCert = cert - , UI.jsSSLKey = key - , UI.jsSSLChainCert = False - } + , UI.jsUseSSL = Nothing } diff --git a/cardano-tracer/src/Cardano/Tracer/Run.hs b/cardano-tracer/src/Cardano/Tracer/Run.hs index ddd1d03edc8..0879a2a2b32 100644 --- a/cardano-tracer/src/Cardano/Tracer/Run.hs +++ b/cardano-tracer/src/Cardano/Tracer/Run.hs @@ -28,7 +28,6 @@ import Cardano.Tracer.Utils import Control.Concurrent (threadDelay) import Control.Concurrent.Async (async, link) -import Control.Concurrent.Async.Extra (sequenceConcurrently) import Control.Concurrent.Extra (newLock) #if RTVIEW import Control.Concurrent.STM.TVar (newTVarIO) @@ -154,7 +153,7 @@ doRunCardanoTracer config rtViewStateDir tr protocolsBrake dpRequestors = do traceWith tr TracerShutdownComplete traceWith tr TracerInitDone - void . sequenceConcurrently $ + sequenceConcurrently_ [ runLogsRotator tracerEnv , runMetricsServers tracerEnv , runAcceptors tracerEnv tracerEnvRTView diff --git a/cardano-tracer/src/Cardano/Tracer/Utils.hs b/cardano-tracer/src/Cardano/Tracer/Utils.hs index 72ea2d34588..39ca20b3964 100644 --- a/cardano-tracer/src/Cardano/Tracer/Utils.hs +++ b/cardano-tracer/src/Cardano/Tracer/Utils.hs @@ -38,6 +38,7 @@ module Cardano.Tracer.Utils , modifyRegistry_ , readRegistry , getProcessId + , sequenceConcurrently_ ) where import Cardano.Node.Startup (NodeInfo (..)) @@ -54,6 +55,7 @@ import Control.Applicative (liftA3) import Control.Applicative (liftA2, liftA3) #endif import Control.Concurrent (killThread, mkWeakThreadId, myThreadId) +import Control.Concurrent.Async (Concurrently(..)) import Control.Concurrent.Extra (Lock) import Control.Concurrent.MVar (newMVar, swapMVar, readMVar, tryReadMVar, modifyMVar_) import Control.Concurrent.STM (atomically) @@ -285,3 +287,6 @@ getProcessId = do CPid pid <- getProcessID return $ fromIntegral pid #endif + +sequenceConcurrently_ :: Traversable t => t (IO a) -> IO () +sequenceConcurrently_ = runConcurrently . traverse_ Concurrently diff --git a/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs b/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs index 7c73562a204..72be8a68d72 100644 --- a/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs +++ b/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs @@ -18,14 +18,13 @@ import Cardano.Tracer.MetaTrace import Cardano.Tracer.Types import Cardano.Tracer.Utils -import Control.Concurrent.Async.Extra (sequenceConcurrently) import Control.Concurrent.Extra (newLock) #if RTVIEW import Control.Concurrent.STM.TVar (newTVarIO, readTVarIO) #else import Control.Concurrent.STM.TVar (readTVarIO) #endif -import Control.Monad (forM_, forever, void) +import Control.Monad (forM_, forever) import qualified Data.ByteString.Lazy as LBS import qualified Data.List.NonEmpty as NE import qualified Data.Map.Strict as M @@ -94,7 +93,7 @@ launchAcceptorsSimple mode localSock dpName = do } #endif -- NOTE: no reforwarding in this acceptor. - void . sequenceConcurrently $ + sequenceConcurrently_ [ runAcceptors tracerEnv tracerEnvRTView , runDataPointsPrinter dpName dpRequestors ] diff --git a/nix/nixos/cardano-tracer-service.nix b/nix/nixos/cardano-tracer-service.nix index 71f2934c30d..b75fc48148c 100644 --- a/nix/nixos/cardano-tracer-service.nix +++ b/nix/nixos/cardano-tracer-service.nix @@ -28,10 +28,10 @@ let serviceConfigToJSON = rpMaxAgeHours = 24; } // (cfg.rotation or {}); - hasEKG = [ - { epHost = "127.0.0.1"; epPort = cfg.ekgPortBase; } - { epHost = "127.0.0.1"; epPort = cfg.ekgPortBase + 1; } - ]; + hasEKG = { + epHost = "127.0.0.1"; + epPort = cfg.ekgPortBase; + }; ekgRequestFreq = 1; hasPrometheus = { epHost = "127.0.0.1";