From 2d25f3ccabcbf1c667b5a854f087c1b82e31e6f1 Mon Sep 17 00:00:00 2001 From: tamirms Date: Tue, 23 Nov 2021 08:03:22 +0000 Subject: [PATCH] exp/orderbook: Update orderbook benchmark to use liquidity pools from history archives (#4091) Update orderbook benchmark to use liquidity pools from history archives and report memory allocations. --- exp/orderbook/graph_benchmark_test.go | 118 +++++++++++++------------- exp/tools/dump-orderbook/main.go | 44 +++++++--- 2 files changed, 91 insertions(+), 71 deletions(-) diff --git a/exp/orderbook/graph_benchmark_test.go b/exp/orderbook/graph_benchmark_test.go index c702a64451..599f4aacc5 100644 --- a/exp/orderbook/graph_benchmark_test.go +++ b/exp/orderbook/graph_benchmark_test.go @@ -2,7 +2,6 @@ package orderbook import ( "context" - "encoding/binary" "flag" "io/ioutil" "math" @@ -20,32 +19,24 @@ import ( var ( // offersFile should contain a list of offers // each line in the offers file is the base 64 encoding of an offer entry xdr - offersFile = flag.String("offers", "", "offers file generated by the dump-orderbook tool") - includePools = flag.Bool("pools", false, "include pools in the benchmark") + offersFile = flag.String("offers", "", "offers file generated by the dump-orderbook tool") + poolsFile = flag.String("pools", "", "pools file generated by the dump-orderbook tool") ) -func assetFromString(s string) xdr.Asset { - if s == "native" { - return xdr.MustNewNativeAsset() - } - parts := strings.Split(s, "/") - asset, err := xdr.BuildAsset(parts[0], parts[2], parts[1]) +// loadGraphFromFiles reads an offers and pools file generated by the dump-orderbook tool +// and returns an orderbook built from those offers / pools +func loadGraphFromFiles(offerFilePath, poolFilePath string) (*OrderBookGraph, error) { + graph := NewOrderBookGraph() + offerDumpBytes, err := ioutil.ReadFile(offerFilePath) if err != nil { - panic(err) + return nil, errors.Wrap(err, "could not read offers file") } - return asset -} - -// loadGraphFromFile reads an offers file generated by the dump-orderbook tool -// and returns an orderbook built from those offers -func loadGraphFromFile(filePath string) (*OrderBookGraph, error) { - graph := NewOrderBookGraph() - rawBytes, err := ioutil.ReadFile(filePath) + poolDumpBytes, err := ioutil.ReadFile(poolFilePath) if err != nil { - return nil, errors.Wrap(err, "could not read file") + return nil, errors.Wrap(err, "could not read pools file") } - for _, line := range strings.Split(string(rawBytes), "\n") { + for _, line := range strings.Split(string(offerDumpBytes), "\n") { offer := xdr.OfferEntry{} if err := xdr.SafeUnmarshalBase64(line, &offer); err != nil { return nil, errors.Wrap(err, "could not base64 decode entry") @@ -53,46 +44,21 @@ func loadGraphFromFile(filePath string) (*OrderBookGraph, error) { graph.AddOffers(offer) } - if err := graph.Apply(1); err != nil { - return nil, err - } - if *includePools { - addLiquidityPools(graph) - if err := graph.Apply(2); err != nil { - return nil, err + for _, line := range strings.Split(string(poolDumpBytes), "\n") { + pool := xdr.LiquidityPoolEntry{} + if err := xdr.SafeUnmarshalBase64(line, &pool); err != nil { + return nil, errors.Wrap(err, "could not base64 decode entry") } + + graph.AddLiquidityPools(pool) } - return graph, nil -} -func addLiquidityPools(graph *OrderBookGraph) { - set := map[tradingPair]bool{} - for i, tp := range graph.tradingPairForOffer { - if !set[tp] { - a, b := assetFromString(tp.buyingAsset), assetFromString(tp.sellingAsset) - poolEntry := xdr.LiquidityPoolEntry{ - LiquidityPoolId: xdr.PoolId{}, - Body: xdr.LiquidityPoolEntryBody{ - Type: xdr.LiquidityPoolTypeLiquidityPoolConstantProduct, - ConstantProduct: &xdr.LiquidityPoolEntryConstantProduct{ - Params: xdr.LiquidityPoolConstantProductParameters{ - AssetA: a, - AssetB: b, - Fee: 30, - }, - ReserveA: 10000, - ReserveB: 10000, - TotalPoolShares: 1, - PoolSharesTrustLineCount: 1, - }, - }, - } - binary.PutVarint(poolEntry.LiquidityPoolId[:], int64(i)) - graph.addPool(poolEntry) - set[tp] = true - } + if err := graph.Apply(1); err != nil { + return nil, err } + + return graph, nil } type request struct { @@ -145,6 +111,38 @@ func loadRequestsFromFile(filePath string) ([]request, error) { return requests, nil } +// BenchmarkVibrantPath benchmarks the most commonly requested path payment from the vibrant app. +func BenchmarkVibrantPath(b *testing.B) { + if *offersFile == "" { + b.Skip("missing offers file") + } + graph, err := loadGraphFromFiles(*offersFile, *poolsFile) + if err != nil { + b.Fatalf("could not read graph from file: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + // https://horizon.stellar.org/paths/strict-send?source_asset_type=credit_alphanum4&source_asset_code=USDC&source_asset_issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&source_amount=10&destination_assets=ARST%3AGCSAZVWXZKWS4XS223M5F54H2B6XPIIXZZGP7KEAIU6YSL5HDRGCI3DG + + for i := 0; i < b.N; i++ { + _, _, err := graph.FindFixedPaths( + context.Background(), + 3, + xdr.MustNewCreditAsset("USDC", "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"), + amount.MustParse("10"), + []xdr.Asset{ + xdr.MustNewCreditAsset("ARST", "GCSAZVWXZKWS4XS223M5F54H2B6XPIIXZZGP7KEAIU6YSL5HDRGCI3DG"), + }, + 5, + true, + ) + if err != nil { + b.Fatal("could not find path") + } + } +} + // BenchmarkMultipleDestinationAssets benchmarks the path finding function // on a request which has multiple destination assets. Most requests to the // path finding endpoint only specify a single destination asset, so I @@ -154,12 +152,13 @@ func BenchmarkMultipleDestinationAssets(b *testing.B) { if *offersFile == "" { b.Skip("missing offers file") } - graph, err := loadGraphFromFile(*offersFile) + graph, err := loadGraphFromFiles(*offersFile, *poolsFile) if err != nil { b.Fatalf("could not read graph from file: %v", err) } b.ResetTimer() + b.ReportAllocs() for i := 0; i < b.N; i++ { _, _, err := graph.FindFixedPaths( @@ -188,7 +187,7 @@ func BenchmarkTestData(b *testing.B) { if *offersFile == "" { b.Skip("missing offers file") } - graph, err := loadGraphFromFile(*offersFile) + graph, err := loadGraphFromFiles(*offersFile, *poolsFile) if err != nil { b.Fatalf("could not read graph from file: %v", err) } @@ -199,6 +198,7 @@ func BenchmarkTestData(b *testing.B) { } b.ResetTimer() + b.ReportAllocs() for i := 0; i < b.N; i++ { for _, req := range requests { @@ -234,6 +234,8 @@ func BenchmarkLiquidityPoolDeposits(b *testing.B) { amounts := createRandomAmounts(b.N) b.ResetTimer() + b.ReportAllocs() + for _, amount := range amounts { makeTrade(eurUsdLiquidityPool, usdAsset, tradeTypeDeposit, amount) } @@ -243,6 +245,8 @@ func BenchmarkLiquidityPoolExpectations(b *testing.B) { amounts := createRandomAmounts(b.N) b.ResetTimer() + b.ReportAllocs() + for _, amount := range amounts { makeTrade(eurUsdLiquidityPool, usdAsset, tradeTypeExpectation, amount) } diff --git a/exp/tools/dump-orderbook/main.go b/exp/tools/dump-orderbook/main.go index 68056cb50d..eee12037b5 100644 --- a/exp/tools/dump-orderbook/main.go +++ b/exp/tools/dump-orderbook/main.go @@ -24,7 +24,9 @@ func main() { 0, "checkpoint ledger sequence to ingest, if omitted will use latest checkpoint ledger.", ) - output := flag.String("output", "offers.dump", "output file which will be populated with offerss") + offersOutput := flag.String("offers-output", "offers.dump", "output file which will be populated with offerss") + poolsOutput := flag.String("pools-output", "pools.dump", "output file which will be populated with pools") + flag.Parse() archive, err := archive(*testnet) @@ -55,13 +57,17 @@ func main() { log.WithField("err", err).Fatal("cannot construct change reader") } defer changeReader.Close() - var file *os.File - file, err = os.Create(*output) - if err != nil { + var offersFile, poolsFile *os.File + + if offersFile, err = os.Create(*offersOutput); err != nil { log.WithField("err", err).Fatal("could not create offers file") } + if poolsFile, err = os.Create(*poolsOutput); err != nil { + log.WithField("err", err).Fatal("could not create pools file") + } var offerXDRs []string + var poolXDRs []string for { var change ingest.Change @@ -73,19 +79,29 @@ func main() { log.WithField("err", err).Fatal("could not read change") } - if change.Type != xdr.LedgerEntryTypeOffer { - continue + switch change.Type { + case xdr.LedgerEntryTypeOffer: + var serialized string + serialized, err = xdr.MarshalBase64(change.Post.Data.MustOffer()) + if err != nil { + log.WithField("err", err).Fatal("could not marshall offer") + } + offerXDRs = append(offerXDRs, serialized) + case xdr.LedgerEntryTypeLiquidityPool: + var serialized string + serialized, err = xdr.MarshalBase64(change.Post.Data.MustLiquidityPool()) + if err != nil { + log.WithField("err", err).Fatal("could not marshall liquidity pool") + } + poolXDRs = append(poolXDRs, serialized) } - var serialized string - serialized, err = xdr.MarshalBase64(change.Post.Data.MustOffer()) - if err != nil { - log.WithField("err", err).Fatal("could not marshall offer") - } - offerXDRs = append(offerXDRs, serialized) } - if _, err = io.Copy(file, bytes.NewBufferString(strings.Join(offerXDRs, "\n"))); err != nil { - log.WithField("err", err).Fatal("could not write dump file") + if _, err = io.Copy(offersFile, bytes.NewBufferString(strings.Join(offerXDRs, "\n"))); err != nil { + log.WithField("err", err).Fatal("could not write offer dump file") + } + if _, err = io.Copy(poolsFile, bytes.NewBufferString(strings.Join(poolXDRs, "\n"))); err != nil { + log.WithField("err", err).Fatal("could not write pool dump file") } }