packages feed

eventlog-live-otelcol-0.6.1.0: src/GHC/Eventlog/Live/Otelcol/Processor/Common/ProfilesDictionary.hs

{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : GHC.Eventlog.Live.Otelcol.Processor.Common.Profiles
Description : Abstraction over ProfilesDictionary for the OTLP protocol.
Stability   : experimental
Portability : portable
-}
module GHC.Eventlog.Live.Otelcol.Processor.Common.ProfilesDictionary (
  -- * Dictionary for deduplication logic of common values
  ProfilesDictionary,
  empty,

  -- * Retrieve the 'SymbolIndex' for various 'OP.ProfilesData' fields
  SymbolIndex,
  getLocation,
  getFunction,
  getString,
  getMapping,
  getLink,
  getAttribute,
  getStack,

  -- * Convert data to the formats used by @hs-opentelemetry-otlp@.
  toExportProfileServiceRequest,
  toProfilesDictionary,
)
where

import Control.Monad.Trans.State.Strict (StateT)
import Control.Monad.Trans.State.Strict qualified as State
import Data.ProtoLens (Message (..))
import Data.Text (Text)
import GHC.Eventlog.Live.Otelcol.Processor.Common.Core (messageWith)
import GHC.Eventlog.Live.Otelcol.Processor.Common.SymbolTable (SymbolIndex, SymbolTable)
import GHC.Eventlog.Live.Otelcol.Processor.Common.SymbolTable qualified as ST
import GHC.Generics (Generic)
import Lens.Family2 (Lens', (.~), (^.))
import Lens.Family2.Unchecked (lens)
import Proto.Opentelemetry.Proto.Collector.Profiles.V1development.ProfilesService qualified as OPS
import Proto.Opentelemetry.Proto.Collector.Profiles.V1development.ProfilesService_Fields qualified as OPS
import Proto.Opentelemetry.Proto.Profiles.V1development.Profiles qualified as OP
import Proto.Opentelemetry.Proto.Profiles.V1development.Profiles_Fields qualified as OP

toExportProfileServiceRequest :: OP.ProfilesData -> OPS.ExportProfilesServiceRequest
toExportProfileServiceRequest profilesData =
  messageWith
    [ OPS.resourceProfiles .~ profilesData ^. OPS.resourceProfiles
    , OPS.dictionary .~ profilesData ^. OPS.dictionary
    ]

data ProfilesDictionary = ProfilesDictionary
  { locationTable :: SymbolTable OP.Location
  {- ^ Common 'OP.Location' table, first entry is the 'defMessage'.
  This holds for OTLP 1.9.0.
  -}
  , functionTable :: SymbolTable OP.Function
  {- ^ Common 'OP.Function' table, first entry is the 'defMessage'.
  This holds for OTLP 1.9.0.
  -}
  , stringTable :: SymbolTable Text
  {- ^ Common string table, the first entry must be "" per the protobuf
  documentation.

  @
   // A common table for strings referenced by various messages.
   // string_table[0] must always be "".
   repeated string string_table = 5;
  @
  -}
  , mappingTable :: SymbolTable OP.Mapping
  {- ^ Common 'OP.Mapping' table, first entry is the 'defMessage'.
  This holds for OTLP 1.9.0.
  -}
  , linkTable :: SymbolTable OP.Link
  {- ^ Common 'OP.Link' table, first entry is the 'defMessage'.
  This holds for OTLP 1.9.0.
  -}
  , attributeTable :: SymbolTable OP.KeyValueAndUnit
  {- ^ Common 'OP.KeyValueAndUnit' table, first entry is the 'defMessage'.
  This holds for OTLP 1.9.0.
  -}
  , stackTable :: SymbolTable OP.Stack
  {- ^ Common 'OP.Stack' table, first entry is the 'defMessage'.
  This holds for OTLP 1.9.0.
  -}
  }
  deriving (Show, Ord, Eq, Generic)

toProfilesDictionary :: ProfilesDictionary -> OP.ProfilesDictionary
toProfilesDictionary st =
  messageWith
    [ OP.locationTable .~ locations st
    , OP.functionTable .~ functions st
    , OP.stringTable .~ strings st
    , OP.mappingTable .~ mappings st
    , OP.linkTable .~ links st
    , OP.attributeTable .~ attributes st
    , OP.stackTable .~ stacks st
    ]

empty :: ProfilesDictionary
empty =
  ProfilesDictionary
    { locationTable = ST.fromList [defMessage]
    , functionTable = ST.fromList [defMessage]
    , stringTable = ST.fromList [""]
    , mappingTable = ST.fromList [defMessage]
    , linkTable = ST.fromList [defMessage]
    , attributeTable = ST.fromList [defMessage]
    , stackTable = ST.fromList [defMessage]
    }

locations :: ProfilesDictionary -> [OP.Location]
locations pd = ST.toList pd.locationTable

functions :: ProfilesDictionary -> [OP.Function]
functions pd = ST.toList pd.functionTable

strings :: ProfilesDictionary -> [Text]
strings pd = ST.toList pd.stringTable

mappings :: ProfilesDictionary -> [OP.Mapping]
mappings pd = ST.toList pd.mappingTable

links :: ProfilesDictionary -> [OP.Link]
links pd = ST.toList pd.linkTable

attributes :: ProfilesDictionary -> [OP.KeyValueAndUnit]
attributes pd = ST.toList pd.attributeTable

stacks :: ProfilesDictionary -> [OP.Stack]
stacks pd = ST.toList pd.stackTable

getSymbolIndexFor :: (Ord a, Monad m) => Lens' ProfilesDictionary (SymbolTable a) -> a -> StateT ProfilesDictionary m SymbolIndex
getSymbolIndexFor accessor a = do
  st <- State.gets (^. accessor)
  let (si, pd') = ST.insert a st
  State.modify' (accessor .~ pd')
  pure si

getLocation :: (Monad m) => OP.Location -> StateT ProfilesDictionary m SymbolIndex
getLocation = getSymbolIndexFor (lens (.locationTable) (\pd st -> pd{locationTable = st}))

getFunction :: (Monad m) => OP.Function -> StateT ProfilesDictionary m SymbolIndex
getFunction = getSymbolIndexFor (lens (.functionTable) (\pd st -> pd{functionTable = st}))

getString :: (Monad m) => Text -> StateT ProfilesDictionary m SymbolIndex
getString = getSymbolIndexFor (lens (.stringTable) (\pd st -> pd{stringTable = st}))

getMapping :: (Monad m) => OP.Mapping -> StateT ProfilesDictionary m SymbolIndex
getMapping = getSymbolIndexFor (lens (.mappingTable) (\pd st -> pd{mappingTable = st}))

getLink :: (Monad m) => OP.Link -> StateT ProfilesDictionary m SymbolIndex
getLink = getSymbolIndexFor (lens (.linkTable) (\pd st -> pd{linkTable = st}))

getAttribute :: (Monad m) => OP.KeyValueAndUnit -> StateT ProfilesDictionary m SymbolIndex
getAttribute = getSymbolIndexFor (lens (.attributeTable) (\pd st -> pd{attributeTable = st}))

getStack :: (Monad m) => OP.Stack -> StateT ProfilesDictionary m SymbolIndex
getStack = getSymbolIndexFor (lens (.stackTable) (\pd st -> pd{stackTable = st}))