Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental daml assistant support for metering report [DPP-816] #12485

Merged
merged 3 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions daml-assistant/daml-helper/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ da_haskell_library(
"uuid",
"vector",
"yaml",
"aeson-pretty",
],
visibility = ["//visibility:public"],
deps = [
Expand All @@ -68,6 +69,8 @@ da_haskell_binary(
"process",
"safe-exceptions",
"typed-process",
"time",
"text",
],
main_function = "DA.Daml.Helper.Main.main",
visibility = ["//visibility:public"],
Expand Down
28 changes: 26 additions & 2 deletions daml-assistant/daml-helper/src/DA/Daml/Helper/Ledger.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@ module DA.Daml.Helper.Ledger (
runLedgerGetDalfs,
runLedgerListPackages,
runLedgerListPackages0,
runLedgerMeteringReport,
-- exported for testing
downloadAllReachablePackages
downloadAllReachablePackages,
) where

import Control.Exception (SomeException(..), catch)
import Control.Applicative ((<|>))
import Control.Lens (toListOf)
import Control.Monad.Extra hiding (fromMaybeM)
import Control.Monad.IO.Class (liftIO)
import Data.Aeson ((.=))
import Data.Aeson ((.=), encode)
import qualified Data.Aeson as A
import Data.Aeson.Text
import qualified Data.ByteString as BS
Expand Down Expand Up @@ -74,6 +75,9 @@ import qualified DA.Ledger as L
import qualified DA.Service.Logger as Logger
import qualified DA.Service.Logger.Impl.IO as Logger
import qualified SdkVersion
import DA.Ledger.Types (Timestamp(..), ApplicationId(..), IsoTime(..))
import Data.Aeson.Encode.Pretty (encodePretty)


data LedgerApi
= Grpc
Expand Down Expand Up @@ -651,3 +655,23 @@ sanitizeToken :: String -> String
sanitizeToken tok
| "Bearer " `isPrefixOf` tok = tok
| otherwise = "Bearer " <> tok

-- | Report on Ledger Use.
runLedgerMeteringReport :: LedgerFlags -> IsoTime -> Maybe IsoTime -> Maybe ApplicationId -> Bool -> IO ()
runLedgerMeteringReport flags fromIso toIso application compactOutput = do
args <- getDefaultArgs flags
report <- meteringReport args (L.isoTimeToTimestamp fromIso) (fmap L.isoTimeToTimestamp toIso) application
let encodeFn = if compactOutput then encode else encodePretty
let encoded = encodeFn report
let bsc = BSL.toStrict encoded
let output = BSC.unpack bsc
putStrLn output

meteringReport :: LedgerArgs -> Timestamp -> Maybe Timestamp -> Maybe ApplicationId -> IO L.MeteringReport
meteringReport args from to application =
case api args of
Grpc -> runWithLedgerArgs args $ do L.getMeteringReport from to application
HttpJson -> do
hPutStrLn stderr "Error: daml ledger metering can only be run via gRPC at the moment."
exitFailure

18 changes: 17 additions & 1 deletion daml-assistant/daml-helper/src/DA/Daml/Helper/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import System.IO.Extra
import System.Process (showCommandForUser)
import System.Process.Typed (unsafeProcessHandle)
import Text.Read (readMaybe)

import DA.Signals
import DA.Daml.Helper.Init
import DA.Daml.Helper.Ledger
Expand All @@ -26,6 +25,8 @@ import DA.Daml.Helper.Studio
import DA.Daml.Helper.Util
import DA.Daml.Helper.Codegen
import DA.PortFile
import DA.Ledger.Types (ApplicationId(..), IsoTime(..))
import Data.Text.Lazy (pack)

main :: IO ()
main = do
Expand Down Expand Up @@ -71,6 +72,7 @@ data Command
| LedgerNavigator { flags :: LedgerFlags, remainingArguments :: [String] }
| Codegen { lang :: Lang, remainingArguments :: [String] }
| PackagesList {flags :: LedgerFlags}
| LedgerMeteringReport { flags :: LedgerFlags, from :: IsoTime, to :: Maybe IsoTime, application :: Maybe ApplicationId, compactOutput :: Bool }
| CantonSandbox
{ cantonOptions :: CantonOptions
, portFileM :: Maybe FilePath
Expand Down Expand Up @@ -261,6 +263,9 @@ commandParser = subparser $ fold
, command "navigator" $ info
(ledgerNavigatorCmd <**> helper)
(forwardOptions <> progDesc "Launch Navigator on ledger")
, command "metering-report" $ info
(ledgerMeteringReportCmd <**> helper)
(forwardOptions <> progDesc "Report on Ledger Use")
]
, subparser $ internal <> fold -- hidden subcommands
[ command "allocate-party" $ info
Expand Down Expand Up @@ -335,6 +340,16 @@ commandParser = subparser $ fold
<$> ledgerFlags (ShowJsonApi False)
<*> many (argument str (metavar "ARG" <> help "Extra arguments to navigator."))

app :: ReadM ApplicationId
app = fmap (ApplicationId . pack) str

ledgerMeteringReportCmd = LedgerMeteringReport
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other flags default to a human readable output and then have a --json flag. i think it would be nice to keep that here.

Copy link
Contributor Author

@simonmaxen-da simonmaxen-da Jan 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this is a special case as the purpose of this helper is to output a report in json format as per the "Design: Ledger Metering" spec. By default the json is not pretty printed. I could add a --pretty flag to output the JSON in a more human readable format (or make pretty output the default and add a --nopretty flag) ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about pretty print by default and add --compact-output (matching jq’s flag name) to not pretty print?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done:

DAML> daml-helper ledger metering-report --host localhost --port 6865 --from "2022-01-22T00:00:00Z" --compact-output
{"from":"2022-01-22T00:00:00Z","participant":"participant1","toActual":"2022-01-24T11:27:01.308061Z","applications":[{"application":"app1","events":100},{"application":"app2","events":200}]}

DAML> daml-helper ledger metering-report --host localhost --port 6865 --from "2022-01-22T00:00:00Z"
{
    "from": "2022-01-22T00:00:00Z",
    "participant": "participant1",
    "toActual": "2022-01-24T11:27:05.718135Z",
    "applications": [
        {
            "application": "app1",
            "events": 100
        },
        {
            "application": "app2",
            "events": 200
        }
    ]
}

<$> ledgerFlags (ShowJsonApi True)
<*> option auto (long "from" <> metavar "FROM" <> help "From date of report (inclusive).")
<*> optional (option auto (long "to" <> metavar "TO" <> help "To date of report (exclusive)."))
<*> optional (option app (long "application" <> metavar "APP" <> help "Report application identifier."))
<*> switch (long "compact-output" <> help "Generate compact report.")

ledgerFlags showJsonApi = LedgerFlags
<$> httpJsonFlag showJsonApi
<*> sslConfig
Expand Down Expand Up @@ -470,6 +485,7 @@ runCommand = \case
LedgerExport {..} -> runLedgerExport flags remainingArguments
LedgerNavigator {..} -> runLedgerNavigator flags remainingArguments
Codegen {..} -> runCodegen lang remainingArguments
LedgerMeteringReport {..} -> runLedgerMeteringReport flags from to application compactOutput
CantonSandbox {..} ->
withCantonPortFile cantonOptions $ \cantonOptions cantonPortFile ->
withCantonSandbox cantonOptions remainingArguments $ \ph -> do
Expand Down
10 changes: 9 additions & 1 deletion language-support/hs/bindings/src/DA/Ledger/Convert.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ module DA.Ledger.Convert (
raiseGetTimeResponse,
raiseTimestamp,
raisePackageId,
RaiseFailureReason,
raiseApplicationId,
raiseParticipantId,
RaiseFailureReason(..),
) where

import Prelude hiding(Enum)
Expand Down Expand Up @@ -485,6 +487,12 @@ raiseChoice = fmap Choice . raiseText "Choice"
raiseParty :: Text -> Perhaps Party
raiseParty = fmap Party . raiseText "Party"

raiseApplicationId :: Text -> Perhaps ApplicationId
raiseApplicationId = fmap ApplicationId . raiseText "ApplicationId"

raiseParticipantId :: Text -> Perhaps ParticipantId
raiseParticipantId = fmap ParticipantId . raiseText "ParticipantId"

raisePackageId :: Text -> Perhaps PackageId
raisePackageId = fmap PackageId . raiseText "PackageId"

Expand Down
1 change: 1 addition & 0 deletions language-support/hs/bindings/src/DA/Ledger/Services.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ import DA.Ledger.Services.PartyManagementService as X
import DA.Ledger.Services.PackageService as X
import DA.Ledger.Services.TimeService as X
import DA.Ledger.Services.TransactionService as X
import DA.Ledger.Services.MeteringReportService as X
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
-- Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

{-# LANGUAGE DuplicateRecordFields #-}

module DA.Ledger.Services.MeteringReportService (
getMeteringReport,
MeteringReport(..),
isoTimeToTimestamp,
) where


import Data.Aeson ( KeyValue((.=)), ToJSON(..), object)
import DA.Ledger.Convert
import DA.Ledger.GrpcWrapUtils
import DA.Ledger.LedgerService
import DA.Ledger.Types
import qualified Data.Text.Lazy as TL
import Network.GRPC.HighLevel.Generated
import qualified Com.Daml.Ledger.Api.V1.Admin.MeteringReportService as LL
import Data.Maybe (maybeToList)
import qualified Data.Time.Clock.System as System
import qualified Data.Time.Format.ISO8601 as ISO8601
import GHC.Int (Int64)
import GHC.Word (Word32)

data MeteredApplication = MeteredApplication {
application :: ApplicationId
, events :: Int64
} deriving (Show)

instance ToJSON MeteredApplication where
toJSON (MeteredApplication application events) =
object
[ "application" .= unApplicationId application
, "events" .= events
]

data MeteringReport = MeteringReport {
participant :: ParticipantId
, from :: Timestamp
, toRequested :: Maybe Timestamp
, toActual :: Timestamp
, applications :: [MeteredApplication]
} deriving (Show)

instance ToJSON MeteringReport where
toJSON (MeteringReport participant from toRequested toActual applications) =
object (
[ "participant" .= unParticipantId participant
, "from" .= timestampToIso8601 from
, "toActual" .= timestampToIso8601 toActual
, "applications" .= applications
]
++ maybeToList (fmap (("toRequested" .=) . timestampToIso8601) toRequested)
)

timestampToSystemTime :: Timestamp -> System.SystemTime
timestampToSystemTime ts = st
where
s = fromIntegral (seconds ts) :: Int64
n = fromIntegral (nanos ts) :: Word32
st = System.MkSystemTime s n

systemTimeToTimestamp :: System.SystemTime -> Timestamp
systemTimeToTimestamp st = ts
where
s = fromIntegral (System.systemSeconds st) :: Int
n = fromIntegral (System.systemNanoseconds st) :: Int
ts = Timestamp s n

timestampToIso8601 :: Timestamp -> String
simonmaxen-da marked this conversation as resolved.
Show resolved Hide resolved
timestampToIso8601 ts = ISO8601.iso8601Show ut
where
st = timestampToSystemTime ts
ut = System.systemToUTCTime st

isoTimeToTimestamp :: IsoTime -> Timestamp
isoTimeToTimestamp iso = systemTimeToTimestamp $ System.utcToSystemTime $ unIsoTime iso

raiseApplicationMeteringReport :: LL.ApplicationMeteringReport -> Perhaps MeteredApplication
raiseApplicationMeteringReport (LL.ApplicationMeteringReport llApp events) = do
application <- raiseApplicationId llApp
return MeteredApplication {..}

raiseParticipantMeteringReport :: LL.GetMeteringReportRequest -> LL.ParticipantMeteringReport -> Perhaps MeteringReport
raiseParticipantMeteringReport (LL.GetMeteringReportRequest (Just llFrom) llTo _) (LL.ParticipantMeteringReport llParticipantId (Just llToActual) llAppReports) = do
participant <- raiseParticipantId llParticipantId
from <- raiseTimestamp llFrom
toRequested <- traverse raiseTimestamp llTo
toActual <- raiseTimestamp llToActual
applications <- raiseList raiseApplicationMeteringReport llAppReports
return MeteringReport{..}

raiseParticipantMeteringReport _ response = Left $ Unexpected ("raiseParticipantMeteringReport unable to parse response: " <> show response)

raiseGetMeteringReportResponse :: LL.GetMeteringReportResponse -> Perhaps MeteringReport
raiseGetMeteringReportResponse (LL.GetMeteringReportResponse (Just request) (Just report) (Just _)) =
raiseParticipantMeteringReport request report

raiseGetMeteringReportResponse response = Left $ Unexpected ("raiseMeteredReport unable to parse response: " <> show response)

getMeteringReport :: Timestamp -> Maybe Timestamp -> Maybe ApplicationId -> LedgerService MeteringReport
getMeteringReport from to applicationId =
makeLedgerService $ \timeout config mdm ->
withGRPCClient config $ \client -> do
service <- LL.meteringReportServiceClient client
let
LL.MeteringReportService {meteringReportServiceGetMeteringReport=rpc} = service
gFrom = Just $ lowerTimestamp from
gTo = fmap lowerTimestamp to
gApp = maybe TL.empty unApplicationId applicationId
request = LL.GetMeteringReportRequest gFrom gTo gApp
rpc (ClientNormalRequest request timeout mdm)
>>= unwrap
>>= either (fail . show) return . raiseGetMeteringReportResponse
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import qualified Data.Aeson as A
import Data.Aeson ((.:))
import qualified Com.Daml.Ledger.Api.V1.Admin.PartyManagementService as LL

newtype ParticipantId = ParticipantId { unParticipantId :: Text} deriving (Eq,Ord,Show)

getParticipantId :: LedgerService ParticipantId
getParticipantId =
makeLedgerService $ \timeout config mdm -> do
Expand Down
15 changes: 14 additions & 1 deletion language-support/hs/bindings/src/DA/Ledger/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ module DA.Ledger.Types( -- High Level types for communication over Ledger API
SubmissionId(..),
LL.Duration(..),
LL.Status(..),
DeduplicationPeriod(..)
DeduplicationPeriod(..),
ParticipantId(..),
IsoTime(..),
) where

import qualified Data.Aeson as A
Expand All @@ -67,6 +69,9 @@ import Prelude hiding(Enum)
import qualified Data.Text.Lazy as Text(unpack)
import qualified Google.Protobuf.Duration as LL
import qualified Google.Rpc.Status as LL
import qualified Data.Time.Format.ISO8601 as ISO8601
import qualified Data.Time.Clock as Clock
import qualified Text.ParserCombinators.ReadP as ReadP

-- commands.proto

Expand Down Expand Up @@ -272,6 +277,7 @@ newtype DaysSinceEpoch = DaysSinceEpoch { unDaysSinceEpoch :: Int}
newtype TemplateId = TemplateId Identifier deriving (Eq,Ord,Show)

newtype ApplicationId = ApplicationId { unApplicationId :: Text } deriving (Eq,Ord,Show)
newtype ParticipantId = ParticipantId { unParticipantId :: Text} deriving (Eq,Ord,Show)
newtype CommandId = CommandId { unCommandId :: Text } deriving (Eq,Ord,Show)
newtype ConstructorId = ConstructorId { unConstructorId :: Text } deriving (Eq,Ord,Show)
newtype ContractId = ContractId { unContractId :: Text } deriving (Eq,Ord,Show)
Expand All @@ -294,3 +300,10 @@ instance A.FromJSON Party where
parseJSON v = Party <$> A.parseJSON v

newtype Verbosity = Verbosity { unVerbosity :: Bool } deriving (Eq,Ord,Show)

-- A wrapped UTCTime the can be read in ISO8601 format
newtype IsoTime = IsoTime { unIsoTime :: Clock.UTCTime } deriving Show

instance Read IsoTime where
readsPrec _ = ReadP.readP_to_S $ fmap IsoTime $ ISO8601.formatReadP ISO8601.iso8601Format

8 changes: 8 additions & 0 deletions language-support/hs/bindings/test/DA/Ledger/Tests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ sharedSandboxTests testDar = testGroupWithSandbox testDar Nothing "shared sandbo
, tUploadDarFileBad
, tUploadDarFileGood
, tAllocateParty
, tMeteringReport
]

authenticatingSandboxTests :: FilePath -> TestTree
Expand Down Expand Up @@ -543,6 +544,13 @@ tValueConversion withSandbox = testCase "tValueConversion" $ run withSandbox $ \
[RecordField{label="owner"},RecordField{label="bucket",fieldValue=bucketReturned}] <- return fields
liftIO $ assertEqual "bucket" bucket (detag bucketReturned)

tMeteringReport :: SandboxTest
simonmaxen-da marked this conversation as resolved.
Show resolved Hide resolved
tMeteringReport withSandbox = testCase "tMeteringReport" $ run withSandbox $ \_ _testId -> do
let expected = Timestamp {seconds = 1, nanos = 2}
report <- getMeteringReport expected Nothing Nothing
let MeteringReport{from=actual} = report
liftIO $ assertEqual "report from date" expected actual

-- Strip the rid,vid,eid tags recusively from record, variant and enum values
detag :: Value -> Value
detag = \case
Expand Down