-
Notifications
You must be signed in to change notification settings - Fork 479
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
Node benchmarking utility #6198
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
// Copyright (C) 2019-2024 Algorand, Inc. | ||
// This file is part of go-algorand | ||
// | ||
// go-algorand is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as | ||
// published by the Free Software Foundation, either version 3 of the | ||
// License, or (at your option) any later version. | ||
// | ||
// go-algorand is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/algorand/go-algorand/config" | ||
"github.com/algorand/go-algorand/crypto" | ||
"github.com/algorand/go-algorand/data/bookkeeping" | ||
"github.com/algorand/go-algorand/ledger" | ||
"github.com/algorand/go-algorand/ledger/ledgercore" | ||
"github.com/algorand/go-algorand/logging" | ||
"github.com/algorand/go-algorand/protocol" | ||
tools "github.com/algorand/go-algorand/tools/network" | ||
) | ||
|
||
var reportJsonPath string | ||
Check failure on line 36 in cmd/catchpointdump/bench.go GitHub Actions / reviewdog-errors
|
||
|
||
func init() { | ||
benchCmd.Flags().StringVarP(&networkName, "net", "n", "", "Specify the network name ( i.e. mainnet.algorand.network )") | ||
benchCmd.Flags().IntVarP(&round, "round", "r", 0, "Specify the round number ( i.e. 7700000 )") | ||
benchCmd.Flags().StringVarP(&relayAddress, "relay", "p", "", "Relay address to use ( i.e. r-ru.algorand-mainnet.network:4160 )") | ||
benchCmd.Flags().StringVarP(&catchpointFile, "tar", "t", "", "Specify the catchpoint file (either .tar or .tar.gz) to process") | ||
benchCmd.Flags().StringVarP(&reportJsonPath, "report", "j", "", "Specify the file to save the Json formatted report to") | ||
} | ||
|
||
var benchCmd = &cobra.Command{ | ||
Use: "bench", | ||
Short: "Benchmark a catchpoint restore", | ||
Long: "Benchmark a catchpoint restore", | ||
Args: validateNoPosArgsFn, | ||
RunE: func(cmd *cobra.Command, args []string) (err error) { | ||
|
||
// Either source the file locally or require a network name to download | ||
if catchpointFile == "" && networkName == "" { | ||
return fmt.Errorf("provide either catchpoint file or network name") | ||
} | ||
loadOnly = true | ||
benchmark := makeBenchmarkReport() | ||
|
||
if catchpointFile == "" { | ||
if round == 0 { | ||
return fmt.Errorf("round not set") | ||
} | ||
stage := benchmark.startStage("network") | ||
catchpointFile, err = downloadCatchpointFromAnyRelay(networkName, round, relayAddress) | ||
if err != nil { | ||
return fmt.Errorf("failed to download catchpoint : %v", err) | ||
} | ||
stage.completeStage() | ||
} | ||
stats, err := os.Stat(catchpointFile) | ||
if err != nil { | ||
return fmt.Errorf("unable to stat '%s' : %v", catchpointFile, err) | ||
} | ||
|
||
catchpointSize := stats.Size() | ||
if catchpointSize == 0 { | ||
return fmt.Errorf("empty file '%s' : %v", catchpointFile, err) | ||
} | ||
|
||
genesisInitState := ledgercore.InitState{ | ||
Block: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{ | ||
UpgradeState: bookkeeping.UpgradeState{ | ||
CurrentProtocol: protocol.ConsensusCurrentVersion, | ||
}, | ||
}}, | ||
} | ||
cfg := config.GetDefaultLocal() | ||
l, err := ledger.OpenLedger(logging.Base(), "./ledger", false, genesisInitState, cfg) | ||
if err != nil { | ||
return fmt.Errorf("unable to open ledger : %v", err) | ||
} | ||
|
||
defer os.Remove("./ledger.block.sqlite") | ||
defer os.Remove("./ledger.block.sqlite-shm") | ||
defer os.Remove("./ledger.block.sqlite-wal") | ||
defer os.Remove("./ledger.tracker.sqlite") | ||
defer os.Remove("./ledger.tracker.sqlite-shm") | ||
defer os.Remove("./ledger.tracker.sqlite-wal") | ||
defer l.Close() | ||
|
||
catchupAccessor := ledger.MakeCatchpointCatchupAccessor(l, logging.Base()) | ||
err = catchupAccessor.ResetStagingBalances(context.Background(), true) | ||
if err != nil { | ||
return fmt.Errorf("unable to initialize catchup database : %v", err) | ||
} | ||
|
||
reader, err := os.Open(catchpointFile) | ||
if err != nil { | ||
return fmt.Errorf("unable to read '%s' : %v", catchpointFile, err) | ||
} | ||
defer reader.Close() | ||
|
||
printDigests = false | ||
stage := benchmark.startStage("database") | ||
|
||
_, err = loadCatchpointIntoDatabase(context.Background(), catchupAccessor, reader, catchpointSize) | ||
if err != nil { | ||
return fmt.Errorf("unable to load catchpoint file into in-memory database : %v", err) | ||
} | ||
stage.completeStage() | ||
|
||
stage = benchmark.startStage("digest") | ||
|
||
err = buildMerkleTrie(context.Background(), catchupAccessor) | ||
if err != nil { | ||
return fmt.Errorf("unable to build Merkle tree : %v", err) | ||
} | ||
stage.completeStage() | ||
|
||
benchmark.printReport() | ||
if reportJsonPath != "" { | ||
if err := benchmark.saveReport(reportJsonPath); err != nil { | ||
fmt.Printf("error writing report to %s: %v\n", reportJsonPath, err) | ||
} | ||
} | ||
|
||
return err | ||
}, | ||
} | ||
|
||
func downloadCatchpointFromAnyRelay(network string, round int, relayAddress string) (string, error) { | ||
var addrs []string | ||
if relayAddress != "" { | ||
addrs = []string{relayAddress} | ||
} else { | ||
//append relays | ||
dnsaddrs, err := tools.ReadFromSRV(context.Background(), "algobootstrap", "tcp", networkName, "", false) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "algobootstrap" probably should not be here since they not obliged to have catchpoints except few most recent ones. |
||
if err != nil || len(dnsaddrs) == 0 { | ||
return "", fmt.Errorf("unable to bootstrap records for '%s' : %v", networkName, err) | ||
} | ||
addrs = append(addrs, dnsaddrs...) | ||
// append archivers | ||
dnsaddrs, err = tools.ReadFromSRV(context.Background(), "archive", "tcp", networkName, "", false) | ||
if err == nil && len(dnsaddrs) > 0 { | ||
addrs = append(addrs, dnsaddrs...) | ||
} | ||
} | ||
|
||
for _, addr := range addrs { | ||
tarName, err := downloadCatchpoint(addr, round) | ||
if err != nil { | ||
reportInfof("failed to download catchpoint from '%s' : %v", addr, err) | ||
continue | ||
} | ||
return tarName, nil | ||
} | ||
return "", fmt.Errorf("catchpoint for round %d on network %s could not be downloaded from any relay", round, network) | ||
} | ||
|
||
func buildMerkleTrie(ctx context.Context, catchupAccessor ledger.CatchpointCatchupAccessor) (err error) { | ||
err = catchupAccessor.BuildMerkleTrie(ctx, func(uint64, uint64) {}) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Printf("\n Building Merkle Trie, this will take a few minutes...") | ||
var balanceHash, spverHash crypto.Digest | ||
balanceHash, spverHash, _, err = catchupAccessor.GetVerifyData(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Printf("done. \naccounts digest=%s, spver digest=%s\n\n", balanceHash, spverHash) | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package main | ||
|
||
import ( | ||
"crypto/sha256" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"runtime" | ||
"syscall" | ||
"time" | ||
|
||
"github.com/google/uuid" | ||
. "github.com/klauspost/cpuid/v2" | ||
Check failure on line 13 in cmd/catchpointdump/bench_report.go GitHub Actions / reviewdog-errors
|
||
) | ||
|
||
type benchStage struct { | ||
stage string | ||
start time.Time | ||
duration time.Duration | ||
cpuTimeNS int64 | ||
completed bool | ||
} | ||
|
||
type hostInfo struct { | ||
CpuCoreCnt int `json:"cores"` | ||
Check failure on line 25 in cmd/catchpointdump/bench_report.go GitHub Actions / reviewdog-errors
|
||
CpuLogicalCnt int `json:"log_cores"` | ||
Check failure on line 26 in cmd/catchpointdump/bench_report.go GitHub Actions / reviewdog-errors
|
||
CpuBaseMHz int64 `json:"base_mhz"` | ||
Check failure on line 27 in cmd/catchpointdump/bench_report.go GitHub Actions / reviewdog-errors
|
||
CpuMaxMHz int64 `json:"max_mhz"` | ||
CpuName string `json:"cpu_name"` | ||
CpuVendor string `json:"cpu_vendor"` | ||
MemMB int `json:"mem_mb"` | ||
OS string `json:"os"` | ||
ID uuid.UUID `json:"uuid"` | ||
} | ||
|
||
type benchReport struct { | ||
ReportID uuid.UUID `json:"report"` | ||
Stages []*benchStage `json:"stages"` | ||
HostInfo *hostInfo `json:"host"` | ||
// TODO: query cpu cores, bogomips and stuff (windows/mac compatible) | ||
} | ||
|
||
func (s *benchStage) MarshalJSON() ([]byte, error) { | ||
return json.Marshal(&struct { | ||
Stage string `json:"stage"` | ||
Duration int64 `json:"duration_sec"` | ||
CpuTime int64 `json:"cpu_time_sec"` | ||
}{ | ||
Stage: s.stage, | ||
Duration: int64(s.duration.Seconds()), | ||
CpuTime: s.cpuTimeNS / 1000000000, | ||
}) | ||
} | ||
|
||
func (bs *benchStage) String() string { | ||
Check failure on line 55 in cmd/catchpointdump/bench_report.go GitHub Actions / reviewdog-errors
|
||
return fmt.Sprintf(">> stage:%s duration_sec:%.1f duration_min:%.1f cpu_sec:%d", bs.stage, bs.duration.Seconds(), bs.duration.Minutes(), bs.cpuTimeNS/1000000000) | ||
} | ||
|
||
func maybeGetTotalMemory() uint64 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider moving to |
||
switch runtime.GOOS { | ||
case "linux": | ||
// Use sysinfo on Linux | ||
var si syscall.Sysinfo_t | ||
err := syscall.Sysinfo(&si) | ||
if err != nil { | ||
return 0 | ||
} | ||
return si.Totalram | ||
default: | ||
return 0 | ||
} | ||
} | ||
|
||
func gatherHostInfo() *hostInfo { | ||
nid := sha256.Sum256(uuid.NodeID()) | ||
uuid, _ := uuid.FromBytes(nid[0:16]) | ||
|
||
ni := &hostInfo{ | ||
CpuCoreCnt: CPU.PhysicalCores, | ||
CpuLogicalCnt: CPU.LogicalCores, | ||
CpuName: CPU.BrandName, | ||
CpuVendor: CPU.VendorID.String(), | ||
CpuMaxMHz: CPU.BoostFreq / 1_000_000, | ||
CpuBaseMHz: CPU.Hz / 1_000_000, | ||
MemMB: int(maybeGetTotalMemory()) / 1024 / 1024, | ||
ID: uuid, | ||
OS: runtime.GOOS, | ||
} | ||
|
||
return ni | ||
} | ||
|
||
func makeBenchmarkReport() *benchReport { | ||
uuid, _ := uuid.NewV7() | ||
return &benchReport{ | ||
Stages: make([]*benchStage, 0), | ||
HostInfo: gatherHostInfo(), | ||
ReportID: uuid, | ||
} | ||
} | ||
|
||
func GetCPU() int64 { | ||
Check failure on line 102 in cmd/catchpointdump/bench_report.go GitHub Actions / reviewdog-errors
|
||
usage := new(syscall.Rusage) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same, move to |
||
syscall.Getrusage(syscall.RUSAGE_SELF, usage) | ||
Check failure on line 104 in cmd/catchpointdump/bench_report.go GitHub Actions / reviewdog-errors
Check failure on line 104 in cmd/catchpointdump/bench_report.go GitHub Actions / build-windows
|
||
return usage.Utime.Nano() + usage.Stime.Nano() | ||
Check warning on line 105 in cmd/catchpointdump/bench_report.go Codecov / codecov/patchcmd/catchpointdump/bench_report.go#L102-L105
Check failure on line 105 in cmd/catchpointdump/bench_report.go GitHub Actions / build-windows
|
||
} | ||
|
||
func (br *benchReport) startStage(stage string) *benchStage { | ||
bs := &benchStage{ | ||
stage: stage, | ||
start: time.Now(), | ||
duration: 0, | ||
cpuTimeNS: GetCPU(), | ||
completed: false, | ||
} | ||
br.Stages = append(br.Stages, bs) | ||
return bs | ||
} | ||
|
||
func (bs *benchStage) completeStage() { | ||
Check failure on line 120 in cmd/catchpointdump/bench_report.go GitHub Actions / reviewdog-errors
|
||
bs.duration = time.Since(bs.start) | ||
bs.completed = true | ||
bs.cpuTimeNS = GetCPU() - bs.cpuTimeNS | ||
} | ||
|
||
func (br *benchReport) printReport() { | ||
fmt.Print("\nBenchmark report:\n") | ||
for i := range br.Stages { | ||
fmt.Println(br.Stages[i].String()) | ||
} | ||
} | ||
|
||
func (br *benchReport) saveReport(filename string) error { | ||
jsonData, err := json.MarshalIndent(br, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Write to file with permissions set to 0644 | ||
err = os.WriteFile(filename, jsonData, 0644) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.