-- Copyright (c) 2014-present, Facebook, Inc. -- All rights reserved. -- -- This source code is distributed under the terms of a BSD license, -- found in the LICENSE file. {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} module StatsTests (tests) where import Test.HUnit import Data.List import Data.Maybe import Haxl.Prelude import Haxl.Core import Prelude() import ExampleDataSource import SleepDataSource import Haxl.DataSource.ConcurrentIO import Control.Monad (void) import Data.IORef import qualified Data.HashMap.Strict as HashMap aggregateBatches :: Test aggregateBatches = TestCase $ do let statsNoBatches = [ FetchStats { fetchDataSource = "foo" , fetchBatchSize = 7 , fetchStart = 0 , fetchDuration = 10 , fetchSpace = 1 , fetchFailures = 2 , fetchIgnoredFailures = 0 , fetchBatchId = n , fetchIds = [1,2] } | n <- reverse [1..10] ++ [11..20] ] ++ [ FetchCall "A" ["B"] 1, FetchCall "C" ["D"] 2 ] fetchBatch = [ FetchStats { fetchDataSource = "batch" , fetchBatchSize = 1 , fetchStart = 100 , fetchDuration = 1000 * n , fetchSpace = 3 , fetchFailures = if n <= 3 then 1 else 0 , fetchIgnoredFailures = 0 , fetchBatchId = 123 , fetchIds = [fromIntegral n] } | n <- [1..50] ] agg (sz,bids) FetchStats{..} = (sz + fetchBatchSize, fetchBatchId:bids) agg _ _ = error "unexpected" agg' = foldl' agg (0,[]) aggNoBatch = aggregateFetchBatches agg' (Stats statsNoBatches) expectedNoBatch = [(7, [n]) | n <- reverse [1..20] :: [Int]] aggBatch = aggregateFetchBatches agg' (Stats fetchBatch) expectedResultBatch = (50, [123 | _ <- [1..50] :: [Int]]) aggInterspersedBatch = aggregateFetchBatches agg' (Stats $ intersperse (head fetchBatch) statsNoBatches) expectedResultInterspersed = (21, [123 | _ <- [1..21] :: [Int]]) : expectedNoBatch assertEqual "No batch has no change" expectedNoBatch aggNoBatch assertEqual "Batch is combined" [expectedResultBatch] aggBatch assertEqual "Grouping works as expected" expectedResultInterspersed aggInterspersedBatch testEnv :: IO (Env () ()) testEnv = do -- To use a data source, we need to initialize its state: exstate <- ExampleDataSource.initGlobalState sleepState <- mkConcurrentIOState -- And create a StateStore object containing the states we need: let st = stateSet exstate (stateSet sleepState stateEmpty) -- Create the Env: env <- initEnv st () return env{ flags = (flags env){ report = setReportFlag ReportFetchStack profilingReportFlags } } fetchIdsSync :: Test fetchIdsSync = TestCase $ do env <- testEnv _ <- runHaxl env $ sequence_ [ void $ countAardvarks "abcabc" + (length <$> listWombats 3) , void $ listWombats 100 , void $ listWombats 99 , void $ countAardvarks "BANG4" `catch` \NotFound{} -> return 123 ] -- expect a single DS stat (Stats stats) <- readIORef (statsRef env) let fetchStats = [x | x@FetchStats{} <- stats] assertEqual "Only 1 batch" 1 (length fetchStats) let [stat] = fetchStats assertEqual "No real failures" 0 (fetchFailures stat) assertEqual "1 ignored failure" 1 (fetchIgnoredFailures stat) fetchIdsBackground :: Test fetchIdsBackground = TestCase $ do env <- testEnv _ <- runHaxl env $ sequence_ [ withLabel "short" $ sleep 1 , withLabel "long" $ sleep 500 ] -- make sure that with memo'ing we still preserve the stack _ <- runHaxl env $ withLabel "base" (memo (1 :: Int) $ withLabel "child" $ sleep 102) _ <- runHaxl env $ withLabel "short_cached" $ sleep 1 -- expect a single DS stat (Stats stats) <- readIORef (statsRef env) (Profile p pt _) <- readIORef (profRef env) let keyMap = HashMap.fromList [ (label, k) | ((label,_), k) <- HashMap.toList pt] revMap = HashMap.fromList [(v,k) | (k,v) <- HashMap.toList pt] parentMap = HashMap.fromList $ catMaybes [ case HashMap.lookup kp revMap of Just (lp,_) -> Just (label, lp) Nothing -> Nothing | ((label,kp), _) <- HashMap.toList pt] fetchMap = HashMap.fromList [ (fid, x) | x@FetchStats{} <- stats , fid <- fetchIds x] get l = [ (prof, wasCached, fetchStat) | Just key <- [HashMap.lookup l keyMap] , Just prof <- [HashMap.lookup key p] , ProfileFetch fid _ wasCached <- profileFetches prof , Just fetchStat <- [HashMap.lookup fid fetchMap]] [(short, shortWC, shortFetch)] = get "short" [(long, longWC, longFetch)] = get "long" [(shortCached, shortCachedWC, shortCachedFetch)] = get "short_cached" assertEqual "3 batches" 3 (HashMap.size fetchMap) assertEqual "6 labels (inc MAIN)" 6 (HashMap.size keyMap) assertEqual "child parent is base" (Just "base") (HashMap.lookup "child" parentMap) assertEqual "base parent is MAIN" (Just "MAIN") (HashMap.lookup "base" parentMap) assertEqual "long parent is MAIN" (Just "MAIN") (HashMap.lookup "long" parentMap) assertBool "original fetches not cached (short)" (not shortWC) assertBool "original fetches not cached (long)" (not longWC) assertBool "was cached short" shortCachedWC assertEqual "one fetch short" 1 (length $ profileFetches short) assertEqual "one fetch long" 1 (length $ profileFetches long) assertEqual "one fetch short_cached" 1 (length $ profileFetches shortCached) assertBool "short fetch mapped properly" (fetchDuration shortFetch < 100000) assertEqual "short cached fetch mapped properly" (fetchDuration shortFetch) (fetchDuration shortCachedFetch) assertBool "long fetch was mapped properly" (fetchDuration longFetch > 100000) ppStatsTest :: Test ppStatsTest = TestCase $ do let r = ppStats (Stats []) mc = ppStats (Stats [MemoCall 0 0]) fc = ppStats (Stats [FetchCall "" [] 0]) fw = ppStats (Stats [FetchWait HashMap.empty 0 1]) fs = ppStats (Stats [FetchStats "" 0 0 0 0 0 0 0 []]) assertEqual "empty stats -> empty string" r "" assertEqual "memo call stats -> empty string" mc "" assertEqual "fetch call stats -> empty string" fc "" assertBool "fetch wait stats -> some data" (not $ null fw) assertBool "fetch stats -> some data" (not $ null fs) tests = TestList [ TestLabel "Aggregate Batches" aggregateBatches , TestLabel "Fetch IDs Sync" fetchIdsSync , TestLabel "Fetch IDs Background" fetchIdsBackground , TestLabel "ppStats" ppStatsTest ]