packages feed

core-telemetry 0.1.8.1 → 0.1.9.1

raw patch · 4 files changed

+311/−64 lines, 4 filesdep +network-infodep −locatorsdep −uuidPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: network-info

Dependencies removed: locators, uuid

API changes (from Hackage documentation)

- Core.Telemetry.Observability: class Telemetry σ
- Core.Telemetry.Observability: data Exporter
- Core.Telemetry.Observability: metric :: Telemetry σ => Rope -> σ -> MetricValue
- Core.Telemetry.Observability: type Label = Rope
+ Core.Telemetry.Identifiers: createIdentifierSpan :: TimeStamp -> Word16 -> Span
+ Core.Telemetry.Identifiers: createIdentifierTrace :: TimeStamp -> Word16 -> MAC -> Trace
+ Core.Telemetry.Identifiers: createTraceParentHeader :: Trace -> Span -> Rope
+ Core.Telemetry.Identifiers: getIdentifierSpan :: Program τ (Maybe Span)
+ Core.Telemetry.Identifiers: getIdentifierTrace :: Program τ (Maybe Trace)
+ Core.Telemetry.Identifiers: hostMachineIdentity :: MAC

Files

core-telemetry.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           core-telemetry-version:        0.1.8.1+version:        0.1.9.1 synopsis:       Advanced telemetry description:    This is part of a library to help build command-line programs, both tools and                 longer-running daemons.@@ -42,6 +42,7 @@       Core.Telemetry.Console       Core.Telemetry.General       Core.Telemetry.Honeycomb+      Core.Telemetry.Identifiers       Core.Telemetry.Observability       Core.Telemetry.Structured   hs-source-dirs:@@ -58,8 +59,8 @@     , exceptions     , http-streams     , io-streams-    , locators     , mtl+    , network-info     , random     , safe-exceptions     , scientific@@ -67,5 +68,4 @@     , template-haskell >=2.14 && <3     , text     , unix-    , uuid   default-language: Haskell2010
lib/Core/Telemetry.hs view
@@ -28,11 +28,18 @@     module Core.Telemetry.Console,     module Core.Telemetry.General,     module Core.Telemetry.Honeycomb,-    module Core.Telemetry.Structured+    module Core.Telemetry.Structured,++    -- * Internals++    -- |+    -- Various elper functions.+    module Core.Telemetry.Identifiers ) where  import Core.Telemetry.Console import Core.Telemetry.General import Core.Telemetry.Honeycomb+import Core.Telemetry.Identifiers import Core.Telemetry.Observability import Core.Telemetry.Structured
+ lib/Core/Telemetry/Identifiers.hs view
@@ -0,0 +1,274 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralisedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_HADDOCK prune #-}++{- |+Machinery for generating identifiers to be used in traces and spans. Meets the+requirements of the [W3C Trace+Context](https://www.w3.org/TR/trace-context/#traceparent-header)+specification, specifically as relates to forming trace identifiers and span+identifiers into @traceparent@ headers. The key requirements are that traces+be globally unique and that spans be unique within a trace.+-}+module Core.Telemetry.Identifiers (+    -- * Traces and Spans+    getIdentifierTrace,+    getIdentifierSpan,++    -- * Internals+    createIdentifierTrace,+    createIdentifierSpan,+    hostMachineIdentity,+    createTraceParentHeader,+    -- for testing+    toHexNormal64,+    toHexReversed64,+    toHexNormal32,+    toHexReversed32,+) where++import Control.Concurrent.MVar (readMVar)+import Core.Program.Context+import Core.System (unsafePerformIO)+import Core.System.Base (liftIO)+import Core.System.External (TimeStamp (unTimeStamp))+import Core.Text.Rope+import Data.Bits (shiftL, shiftR, (.&.), (.|.))+import Data.Text.Internal.Unsafe.Char (unsafeChr8)+import GHC.Word+import Network.Info (MAC (..), NetworkInterface, getNetworkInterfaces, mac)++{- |+Get the MAC address of the first interface that's not the loopback device. If+something goes weird then we return a valid but bogus address (in the locally+administered addresses block).++@since 0.1.9+-}+hostMachineIdentity :: MAC+hostMachineIdentity = unsafePerformIO $ do+    interfaces <- getNetworkInterfaces+    pure (go interfaces)+  where+    go :: [NetworkInterface] -> MAC+    go [] = bogusAddress+    go (interface : remainder) =+        let address = mac interface+         in if address /= loopbackAddress+                then address+                else go remainder++    loopbackAddress = MAC 00 00 00 00 00 00+    bogusAddress = MAC 0xfe 0xff 0xff 0xff 0xff 0xff+{-# NOINLINE hostMachineIdentity #-}++{- |+Generate an identifier suitable for use in a trace context. Trace identifiers+are 16 bytes. We incorporate the time to nanosecond precision, the host+system's MAC address, and a random element. This is similar to a version 1+UUID, but we render the least significant bits of the time stamp ordered first+so that visual distinctiveness is on the left. The MAC address in the lower 48+bits is /not/ reversed, leaving the most distinctiveness [the actual host as+opposed to manufacturer OIN] hanging on the right hand edge of the identifier.+The two bytes of randomness are in the middle.++@since 0.1.9+-}+createIdentifierTrace :: TimeStamp -> Word16 -> MAC -> Trace+createIdentifierTrace time rand address =+    let p1 = packRope (toHexReversed64 (fromIntegral time))+        p2 = packRope (toHexNormal16 rand)+        p3 = packRope (convertMACToHex address)+     in Trace+            (p1 <> p2 <> p3)++convertMACToHex :: MAC -> [Char]+convertMACToHex (MAC b1 b2 b3 b4 b5 b6) =+    nibbleToHex b1 4 :+    nibbleToHex b1 0 :+    nibbleToHex b2 4 :+    nibbleToHex b2 0 :+    nibbleToHex b3 4 :+    nibbleToHex b3 0 :+    nibbleToHex b4 4 :+    nibbleToHex b4 0 :+    nibbleToHex b5 4 :+    nibbleToHex b5 0 :+    nibbleToHex b6 4 :+    nibbleToHex b6 0 :+    []+  where+    nibbleToHex w = unsafeToDigit . fromIntegral . (.&.) 0x0f . shiftR w++toHexReversed64 :: Word64 -> [Char]+toHexReversed64 w =+    nibbleToHex 00 :+    nibbleToHex 04 :+    nibbleToHex 08 :+    nibbleToHex 12 :+    nibbleToHex 16 :+    nibbleToHex 20 :+    nibbleToHex 24 :+    nibbleToHex 28 : -- Word32+    nibbleToHex 32 :+    nibbleToHex 36 :+    nibbleToHex 40 :+    nibbleToHex 44 :+    nibbleToHex 48 :+    nibbleToHex 52 :+    nibbleToHex 56 :+    nibbleToHex 60 :+    []+  where+    nibbleToHex = unsafeToDigit . fromIntegral . (.&.) 0x0f . shiftR w++toHexNormal64 :: Word64 -> [Char]+toHexNormal64 w =+    nibbleToHex 60 :+    nibbleToHex 56 :+    nibbleToHex 52 :+    nibbleToHex 48 :+    nibbleToHex 44 :+    nibbleToHex 40 :+    nibbleToHex 36 :+    nibbleToHex 32 :+    nibbleToHex 28 : -- Word32+    nibbleToHex 24 :+    nibbleToHex 20 :+    nibbleToHex 16 :+    nibbleToHex 12 :+    nibbleToHex 08 :+    nibbleToHex 04 :+    nibbleToHex 00 :+    []+  where+    nibbleToHex = unsafeToDigit . fromIntegral . (.&.) 0x0f . shiftR w++--+-- Convert a 32-bit word to eight characters, but reversed so the least+-- significant bits are first.+--+toHexReversed32 :: Word32 -> [Char]+toHexReversed32 w =+    nibbleToHex 00 :+    nibbleToHex 04 :+    nibbleToHex 08 :+    nibbleToHex 12 :+    nibbleToHex 16 :+    nibbleToHex 20 :+    nibbleToHex 24 :+    nibbleToHex 28 :+    []+  where+    nibbleToHex = unsafeToDigit . fromIntegral . (.&.) 0x0f . shiftR w++toHexNormal32 :: Word32 -> [Char]+toHexNormal32 w =+    nibbleToHex 28 :+    nibbleToHex 24 :+    nibbleToHex 20 :+    nibbleToHex 16 :+    nibbleToHex 12 :+    nibbleToHex 08 :+    nibbleToHex 04 :+    nibbleToHex 00 :+    []+  where+    nibbleToHex = unsafeToDigit . fromIntegral . (.&.) 0x0f . shiftR w++toHexNormal16 :: Word16 -> [Char]+toHexNormal16 w =+    nibbleToHex 12 :+    nibbleToHex 08 :+    nibbleToHex 04 :+    nibbleToHex 00 :+    []+  where+    nibbleToHex = unsafeToDigit . fromIntegral . (.&.) 0x0f . shiftR w++{-+byteToHex :: Word8 -> [Char]+byteToHex c =+    let !low = unsafeToDigit (c .&. 0x0f)+        !hi = unsafeToDigit ((c .&. 0xf0) `shiftR` 4)+     in hi : low : []+-}++-- convert a nibble to its hexidecimal character equivalent+unsafeToDigit :: Word8 -> Char+unsafeToDigit w =+    if w < 10+        then unsafeChr8 (48 + w)+        else unsafeChr8 (97 + w - 10)++{- |+Generate an identifier for a span. We only have 8 bytes to work with. We use+the nanosecond prescision timestamp with the nibbles reversed, and then+overwrite the last two bytes with a random value.++@since 0.1.9+-}+createIdentifierSpan :: TimeStamp -> Word16 -> Span+createIdentifierSpan time rand =+    let t = fromIntegral (unTimeStamp time) :: Word64+        r = fromIntegral rand :: Word64+        w = (t .&. 0x0000ffffffffffff) .|. (shiftL r 48)+     in Span+            ( packRope+                ( toHexReversed64 w+                )+            )++{- |+Render the 'Trace' and 'Span' identifiers representing a span calling onward+to another component in a distributed system. The W3C Trace Context+recommendation specifies the HTTP header @traceparent@ with the 16 byte trace+identifier and the 8 byte span identifier formatted as follows:++@+traceparent: 00-fd533dbf96ecdc610156482ae36c24f7-1d1e9dbf96ec4649-00+@++@since 0.1.9+-}+createTraceParentHeader :: Trace -> Span -> Rope+createTraceParentHeader trace unique =+    let version = "00"+        flags = "00"+     in version <> "-" <> unTrace trace <> "-" <> unSpan unique <> "-" <> flags++{- |+Get the identifier of the current trace, if you are ithin a trace started by+'beginTrace' or 'usingTrace'.++@since 0.1.9+-}+getIdentifierTrace :: Program τ (Maybe Trace)+getIdentifierTrace = do+    context <- getContext++    liftIO $ do+        let v = currentDatumFrom context+        datum <- readMVar v++        pure (traceIdentifierFrom datum)++{- |+Get the identifier of the current span, if you are currently within a span+created by 'encloseSpan'.++@since 0.1.9+-}+getIdentifierSpan :: Program τ (Maybe Span)+getIdentifierSpan = do+    context <- getContext++    liftIO $ do+        let v = currentDatumFrom context+        datum <- readMVar v++        pure (spanIdentifierFrom datum)
lib/Core/Telemetry/Observability.hs view
@@ -1,8 +1,10 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralisedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RankNTypes #-}+{-# OPTIONS_HADDOCK prune #-}  {- | Traditional \"monitoring\" systems were concerned with gathering together obscene@@ -165,23 +167,21 @@ import Core.Encoding.Json import Core.Program.Arguments import Core.Program.Context-import Core.Program.Execute (sleepThread) import Core.Program.Logging import Core.System.Base (liftIO) import Core.System.External (TimeStamp (unTimeStamp), getCurrentTimeNanoseconds)+import Core.Telemetry.Identifiers import Core.Text.Rope import Core.Text.Utilities (oxford, quote) import qualified Data.ByteString as B (ByteString) import qualified Data.ByteString.Lazy as L (ByteString)-import Data.Int (Int32, Int64) import qualified Data.List as List (foldl')-import Data.Locator (padWithZeros, toBase62, toLatin25) import Data.Scientific (Scientific) import qualified Data.Text as T (Text) import qualified Data.Text.Lazy as U (Text)-import Data.UUID (UUID, toWords)-import Data.UUID.V1 (nextUUID)-import Data.Word (Word32, Word64)+import GHC.Int+import GHC.Word+import System.Random (randomIO)  {- | A telemetry value that can be sent over the wire. This is a wrapper around@@ -388,13 +388,19 @@ encloseSpan label action = do     context <- getContext -    unique <- generateIdentifierBase62+    start <- liftIO $ do+        getCurrentTimeNanoseconds++    rand <- liftIO $ do+        (randomIO :: IO Word16)++    let unique = createIdentifierSpan start rand+     internal label emptyRope-    internal "span = " unique+    internal "span = " (unSpan unique)      liftIO $ do         -- prepare new span-        start <- getCurrentTimeNanoseconds          -- slightly tricky: create a new Context with a new MVar with an         -- forked copy of the current Datum, creating the nested span.@@ -403,7 +409,7 @@          let datum' =                 datum-                    { spanIdentifierFrom = Just (Span unique)+                    { spanIdentifierFrom = Just unique                     , spanNameFrom = label                     , spanTimeFrom = start                     , parentIdentifierFrom = spanIdentifierFrom datum@@ -437,54 +443,7 @@         -- now back to your regularly scheduled Haskell program         pure result -getUniqueId :: Program τ UUID-getUniqueId = do-    next <- liftIO nextUUID-    case next of-        Just uuid -> pure uuid-        Nothing -> do-            sleepThread 0.000001-            getUniqueId- {- |-Generate a UUID but expressed in Latin 25, with least significant bits (the-time stamp) ordered to the left so that visual distinctiveness is on the left.-The MAC address in the lower 48 bits is /not/ reversed, leaving the most-distinctiveness [the actual host as opposed to manufacturer] hanging on the-right hand edge of the identifier.--}-generateIdentifierLatin25 :: Program τ Rope-generateIdentifierLatin25 = do-    uuid <- getUniqueId-    let (w1, w2, w3, w4) = toWords uuid-        l1 = convertL w1-        l2 = convertL w2-        l3 = convertB w3-        l4 = convertB w4-        result = l1 <> l2 <> l3 <> l4-    pure result-  where-    convertL = intoRope . padWithZeros 7 . reverse . toLatin25 . fromIntegral-    convertB = intoRope . padWithZeros 7 . toLatin25 . fromIntegral--{- |-Generate a UUID but expressed in Base 62.--}-generateIdentifierBase62 :: Program τ Rope-generateIdentifierBase62 = do-    uuid <- getUniqueId-    let (w1, w2, w3, w4) = toWords uuid-        b1 = convertL w1-        b2 = convertL w2-        b3 = convertB w3-        b4 = convertB w4-        result = b1 <> b2 <> b3 <> b4-    pure result-  where-    convertL = intoRope . padWithZeros 6 . reverse . toBase62 . fromIntegral-    convertB = intoRope . padWithZeros 6 . toBase62 . fromIntegral--{- | Start a new trace. A random identifier will be generated.  You /must/ have a single \"root span\" immediately below starting a new trace.@@ -499,9 +458,16 @@ -} beginTrace :: Program τ α -> Program τ α beginTrace action = do-    trace <- generateIdentifierLatin25-    usingTrace (Trace trace) Nothing action+    now <- liftIO $ do+        getCurrentTimeNanoseconds +    rand <- liftIO $ do+        (randomIO :: IO Word16)++    let trace = createIdentifierTrace now rand hostMachineIdentity++    usingTrace trace Nothing action+ {- | Begin a new trace, but using a trace identifier provided externally. This is the most common case. Internal services that are play a part of a larger@@ -547,7 +513,7 @@         let datum2 =                 datum                     { traceIdentifierFrom = Just trace-                    , parentIdentifierFrom = possibleParent+                    , spanIdentifierFrom = possibleParent                     }          -- fork the Context