@@ -9,10 +9,14 @@ import Control.Monad.Trans.Except (runExceptT)
9
9
import Control.Monad.Trans.Writer (WriterT (runWriterT ))
10
10
import Data.ByteString qualified as BSL
11
11
import Data.ByteString.Short qualified as BSS
12
+ import Data.Digest.Murmur64 (Hash64 )
12
13
import Data.Either (isRight )
14
+ import Data.Map (Map )
15
+ import Data.Map qualified as Map
13
16
import Data.Text qualified as Text
14
17
import Data.Text.IO qualified as TIO
15
- import Data.Time.Clock (getCurrentTime )
18
+ import Data.Time.Clock (NominalDiffTime , diffUTCTime , getCurrentTime , nominalDiffTimeToSeconds )
19
+ import Data.Word (Word32 )
16
20
import Database qualified as Db
17
21
import Database.PostgreSQL.Simple qualified as Postgres
18
22
import Database.Schema (ScriptEvaluationRecord' (.. ))
@@ -33,19 +37,20 @@ import PlutusLedgerApi.Common (
33
37
import PlutusLedgerApi.V1 qualified as V1
34
38
import PlutusLedgerApi.V2 qualified as V2
35
39
import PlutusLedgerApi.V3 qualified as V3
40
+ import System.Exit (ExitCode (.. ), exitWith )
36
41
import Text.PrettyBy qualified as Pretty
37
- import UnliftIO (IORef , atomicModifyIORef' , liftIO , newIORef , readIORef )
42
+ import UnliftIO (IORef , MonadIO , atomicModifyIORef' , liftIO , newIORef , readIORef , writeIORef )
38
43
import UnliftIO.Concurrent (forkFinally , threadDelay )
39
44
40
45
data ScriptEvaluationInput = MkScriptEvaluationInput
41
- { seiPlutusLedgerLanguage :: PlutusLedgerLanguage
42
- , seiMajorProtocolVersion :: MajorProtocolVersion
43
- , seiEvaluationContext :: EvaluationContext
46
+ { seiPlutusLedgerLanguage :: ! PlutusLedgerLanguage
47
+ , seiMajorProtocolVersion :: ! MajorProtocolVersion
48
+ , seiEvaluationContext :: ! EvaluationContext
44
49
, seiData :: [Data ]
45
- , seiScript :: ScriptForEvaluation
46
- , seiExBudget :: ExBudget
47
- , seiEvaluationSuccess :: Bool
48
- , seiBlock :: BlockNo
50
+ , seiScript :: ! ScriptForEvaluation
51
+ , seiExBudget :: ! ExBudget
52
+ , seiEvaluationSuccess :: ! Bool
53
+ , seiBlock :: ! BlockNo
49
54
}
50
55
51
56
renderScriptEvaluationInput :: ScriptEvaluationInput -> String
@@ -79,9 +84,10 @@ accumulateScripts
79
84
-> (ScriptEvaluationInput -> a -> m a )
80
85
-- ^ Accumulation function
81
86
-> m a
82
- accumulateScripts conn startBlock initialAccum accumulate =
87
+ accumulateScripts conn startBlock initialAccum accumulate = do
88
+ evaluationContexts <- newIORef Map. empty
83
89
Db. withScriptEvaluationEvents conn startBlock initialAccum \ accum record -> do
84
- scriptInput <- inputFromRecord record
90
+ scriptInput <- inputFromRecord evaluationContexts record
85
91
accumulate scriptInput accum
86
92
87
93
evaluateScripts
@@ -96,42 +102,96 @@ evaluateScripts
96
102
-> m ()
97
103
evaluateScripts conn startBlock callback = do
98
104
maxThreads <- liftIO getNumCapabilities
99
- threadCounter <- newIORef 0
105
+ st <-
106
+ newIORef
107
+ ( 0 -- current number of threads
108
+ , 0 -- number of evaluated scripts
109
+ , 0 -- average processing time (millis)
110
+ , 0 -- average evaluation time (millis)
111
+ )
112
+ evalContexts <- newIORef Map. empty -- cashed evaluation contexts
100
113
Db. withScriptEvaluationEvents conn startBlock () \ _unit record -> do
101
- waitForAFreeThread maxThreads threadCounter
102
- atomicModifyIORef' threadCounter \ n -> (n + 1 , () )
103
- _threadId <- forkFinally (callback =<< inputFromRecord record) \ _ ->
104
- atomicModifyIORef' threadCounter \ n -> (n - 1 , () )
114
+ startProcessing <- liftIO getCurrentTime
115
+ waitForAFreeThread maxThreads st
116
+ atomicModifyIORef' st \ (threads, n, a, s) ->
117
+ ((threads + 1 , n, a, s), () )
118
+ let work = do
119
+ input <- inputFromRecord evalContexts record
120
+ startEvaluation <- liftIO getCurrentTime
121
+ callback input
122
+ end <- liftIO getCurrentTime
123
+ pure
124
+ ( nominalDiffTimeToMillis (end `diffUTCTime` startProcessing)
125
+ , nominalDiffTimeToMillis (end `diffUTCTime` startEvaluation)
126
+ )
127
+ _threadId <- forkFinally work \ case
128
+ Left err -> liftIO do
129
+ putStrLn $ " Failed to evaluate script: " <> show err
130
+ exitWith (ExitFailure 1 )
131
+ Right (! dtp, ! dte) -> do
132
+ atomicModifyIORef' st \ (threads, n, pt, et) ->
133
+ let pt' =
134
+ if pt == 0
135
+ then dtp
136
+ else
137
+ round @ Double @ Word32 $
138
+ fromIntegral (pt * (n - 1 ) + dtp) / fromIntegral n
139
+ et' =
140
+ if et == 0
141
+ then dte
142
+ else
143
+ round @ Double @ Word32 $
144
+ fromIntegral (et * (n - 1 ) + dte) / fromIntegral n
145
+ in ((threads - 1 , n + 1 , pt', et'), () )
105
146
pure ()
106
147
where
107
- waitForAFreeThread :: Int -> IORef Int -> m ()
148
+ {-
149
+ (_, n, pt, et) <- readIORef st
150
+ when (n `mod` 100 == 0) $ liftIO do
151
+ putStrLn $ "Average time: processing " <> show pt <> "ms, "
152
+ <> "evaluation " <> show et <> "ms"
153
+ -}
154
+
155
+ waitForAFreeThread :: Int -> IORef (Int , Word32 , Word32 , Word32 ) -> m ()
108
156
waitForAFreeThread maxThreads counter = do
109
- threadCount <- readIORef counter
157
+ ( threadCount, _, _, _) <- readIORef counter
110
158
when (threadCount >= maxThreads) do
111
159
threadDelay 1_000 -- wait for 1ms
112
160
waitForAFreeThread maxThreads counter
113
161
162
+ nominalDiffTimeToMillis :: NominalDiffTime -> Word32
163
+ nominalDiffTimeToMillis dt = round (1000 * nominalDiffTimeToSeconds dt)
164
+
114
165
inputFromRecord
115
- :: (MonadFail m )
116
- => Db. ScriptEvaluationRecord
166
+ :: (MonadFail m , MonadIO m )
167
+ => IORef (Map Hash64 EvaluationContext )
168
+ -> Db. ScriptEvaluationRecord
117
169
-> m ScriptEvaluationInput
118
- inputFromRecord MkScriptEvaluationRecord' {.. } = do
170
+ inputFromRecord evalCtxRef MkScriptEvaluationRecord' {.. } = do
119
171
let mkEvalCtx f =
120
172
runExceptT (runWriterT f) >>= \ case
121
173
Left e -> fail $ " Failed to create evaluation context: " <> show e
122
174
Right (ctx, _warnings) -> pure ctx
123
- seiEvaluationContext <-
124
- mkEvalCtx case seLedgerLanguage of
125
- PlutusV1 -> V1. mkEvaluationContext seCostModelParams
126
- PlutusV2 -> V2. mkEvaluationContext seCostModelParams
127
- PlutusV3 -> V3. mkEvaluationContext seCostModelParams
175
+ seiEvaluationContext <- do
176
+ keyedEvalCtxs <- liftIO $ readIORef evalCtxRef
177
+ case Map. lookup seCostModelKey keyedEvalCtxs of
178
+ Just ctx -> pure ctx
179
+ Nothing -> do
180
+ ctx <- mkEvalCtx case seLedgerLanguage of
181
+ PlutusV1 -> V1. mkEvaluationContext seCostModelParams
182
+ PlutusV2 -> V2. mkEvaluationContext seCostModelParams
183
+ PlutusV3 -> V3. mkEvaluationContext seCostModelParams
184
+ let keyedEvalCtxs' = Map. insert seCostModelKey ctx keyedEvalCtxs
185
+ liftIO $ writeIORef evalCtxRef keyedEvalCtxs'
186
+ pure ctx
128
187
seiScript <-
129
188
case deserialiseScript
130
189
seLedgerLanguage
131
190
seMajorProtocolVersion
132
191
(BSS. toShort seScript) of
133
192
Left err -> fail $ " Failed to deserialise script: " <> show err
134
193
Right script -> pure script
194
+
135
195
pure
136
196
MkScriptEvaluationInput
137
197
{ seiPlutusLedgerLanguage = seLedgerLanguage
0 commit comments