From 16fad834a1067dc33ce867824cf851d10d1a2c87 Mon Sep 17 00:00:00 2001 From: Captain Date: Sun, 8 Aug 2021 14:53:55 +0000 Subject: [PATCH] DERO Stargate improved --- blockchain/blockchain.go | 433 +++++++-------- blockchain/mempool/mempool.go | 16 +- blockchain/miner_block.go | 54 +- blockchain/transaction_execute.go | 32 +- blockchain/transaction_verify.go | 30 +- cmd/derod/rpc_dero_getencryptedbalance.go | 32 ++ cmd/derod/rpc_dero_gettransactions.go | 18 +- cmd/derod/rpc_dero_sendrawtransaction.go | 4 +- cmd/derod/websocket_server.go | 26 +- cmd/explorer/explorer.go | 11 +- cmd/explorer/templates.go | 4 +- config/config.go | 8 +- config/version.go | 2 +- cryptography/crypto/proof_generate.go | 53 +- cryptography/crypto/proof_verify.go | 17 +- metrics/metrics.go | 30 +- p2p/chain_bootstrap.go | 2 +- p2p/connection_pool.go | 14 - p2p/controller.go | 5 +- p2p/rpc_notifications.go | 11 +- rpc/daemon_rpc.go | 7 +- transaction/transaction.go | 2 +- .../metrics/.github/workflows/main.yml | 33 ++ .../VictoriaMetrics/metrics/LICENSE | 22 + .../VictoriaMetrics/metrics/README.md | 104 ++++ .../VictoriaMetrics/metrics/counter.go | 77 +++ .../metrics/counter_example_test.go | 41 ++ .../VictoriaMetrics/metrics/counter_test.go | 76 +++ .../VictoriaMetrics/metrics/floatcounter.go | 82 +++ .../metrics/floatcounter_example_test.go | 41 ++ .../metrics/floatcounter_test.go | 76 +++ .../VictoriaMetrics/metrics/gauge.go | 67 +++ .../metrics/gauge_example_test.go | 41 ++ .../VictoriaMetrics/metrics/gauge_test.go | 64 +++ .../github.com/VictoriaMetrics/metrics/go.mod | 5 + .../github.com/VictoriaMetrics/metrics/go.sum | 4 + .../VictoriaMetrics/metrics/go_metrics.go | 64 +++ .../VictoriaMetrics/metrics/histogram.go | 230 ++++++++ .../metrics/histogram_example_test.go | 27 + .../VictoriaMetrics/metrics/histogram_test.go | 200 +++++++ .../metrics/histogram_timing_test.go | 17 + .../VictoriaMetrics/metrics/metrics.go | 112 ++++ .../metrics/metrics_example_test.go | 14 + .../VictoriaMetrics/metrics/metrics_test.go | 146 +++++ .../metrics/process_metrics_linux.go | 265 +++++++++ .../metrics/process_metrics_linux_test.go | 51 ++ .../metrics/process_metrics_other.go | 15 + .../github.com/VictoriaMetrics/metrics/set.go | 519 ++++++++++++++++++ .../metrics/set_example_test.go | 25 + .../VictoriaMetrics/metrics/set_test.go | 152 +++++ .../VictoriaMetrics/metrics/summary.go | 254 +++++++++ .../metrics/summary_example_test.go | 31 ++ .../VictoriaMetrics/metrics/summary_test.go | 155 ++++++ .../VictoriaMetrics/metrics/testdata/fd/0 | 0 .../VictoriaMetrics/metrics/testdata/fd/10 | 0 .../VictoriaMetrics/metrics/testdata/fd/2 | 0 .../VictoriaMetrics/metrics/testdata/fd/3 | 0 .../VictoriaMetrics/metrics/testdata/fd/5 | 0 .../VictoriaMetrics/metrics/testdata/limits | 17 + .../metrics/testdata/limits_bad | 1 + .../VictoriaMetrics/metrics/testdata/status | 115 ++++ .../metrics/testdata/status_bad | 115 ++++ .../VictoriaMetrics/metrics/validator.go | 84 +++ .../VictoriaMetrics/metrics/validator_test.go | 61 ++ .../github.com/valyala/fastrand/.travis.yml | 16 + .../github.com/valyala/fastrand/LICENSE | 21 + .../github.com/valyala/fastrand/README.md | 76 +++ .../github.com/valyala/fastrand/fastrand.go | 74 +++ .../vendor/github.com/valyala/fastrand/go.mod | 1 + .../github.com/valyala/histogram/LICENSE | 21 + .../github.com/valyala/histogram/README.md | 9 + .../github.com/valyala/histogram/go.mod | 5 + .../github.com/valyala/histogram/go.sum | 2 + .../github.com/valyala/histogram/histogram.go | 127 +++++ .../metrics/vendor/modules.txt | 4 + .../github.com/valyala/fastrand/.travis.yml | 16 + vendor/github.com/valyala/fastrand/LICENSE | 21 + vendor/github.com/valyala/fastrand/README.md | 76 +++ .../github.com/valyala/fastrand/fastrand.go | 74 +++ vendor/github.com/valyala/fastrand/go.mod | 1 + vendor/github.com/valyala/histogram/LICENSE | 21 + vendor/github.com/valyala/histogram/README.md | 9 + vendor/github.com/valyala/histogram/go.mod | 5 + vendor/github.com/valyala/histogram/go.sum | 2 + .../github.com/valyala/histogram/histogram.go | 127 +++++ .../valyala/histogram/histogram_test.go | 59 ++ .../histogram/histogram_timing_test.go | 25 + .../github.com/valyala/fastrand/.travis.yml | 16 + .../github.com/valyala/fastrand/LICENSE | 21 + .../github.com/valyala/fastrand/README.md | 76 +++ .../github.com/valyala/fastrand/fastrand.go | 74 +++ .../vendor/github.com/valyala/fastrand/go.mod | 1 + .../valyala/histogram/vendor/modules.txt | 2 + walletapi/daemon_communication.go | 37 +- walletapi/transaction_build.go | 22 +- walletapi/wallet_pool.go | 28 +- walletapi/wallet_transfer.go | 24 +- 97 files changed, 4911 insertions(+), 396 deletions(-) create mode 100644 vendor/github.com/VictoriaMetrics/metrics/.github/workflows/main.yml create mode 100644 vendor/github.com/VictoriaMetrics/metrics/LICENSE create mode 100644 vendor/github.com/VictoriaMetrics/metrics/README.md create mode 100644 vendor/github.com/VictoriaMetrics/metrics/counter.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/counter_example_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/counter_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/floatcounter.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/floatcounter_example_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/floatcounter_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/gauge.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/gauge_example_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/gauge_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/go.mod create mode 100644 vendor/github.com/VictoriaMetrics/metrics/go.sum create mode 100644 vendor/github.com/VictoriaMetrics/metrics/go_metrics.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/histogram.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/histogram_example_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/histogram_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/histogram_timing_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/metrics.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/metrics_example_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/metrics_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/set.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/set_example_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/set_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/summary.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/summary_example_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/summary_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/testdata/fd/0 create mode 100644 vendor/github.com/VictoriaMetrics/metrics/testdata/fd/10 create mode 100644 vendor/github.com/VictoriaMetrics/metrics/testdata/fd/2 create mode 100644 vendor/github.com/VictoriaMetrics/metrics/testdata/fd/3 create mode 100644 vendor/github.com/VictoriaMetrics/metrics/testdata/fd/5 create mode 100644 vendor/github.com/VictoriaMetrics/metrics/testdata/limits create mode 100644 vendor/github.com/VictoriaMetrics/metrics/testdata/limits_bad create mode 100644 vendor/github.com/VictoriaMetrics/metrics/testdata/status create mode 100644 vendor/github.com/VictoriaMetrics/metrics/testdata/status_bad create mode 100644 vendor/github.com/VictoriaMetrics/metrics/validator.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/validator_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/.travis.yml create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/LICENSE create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/README.md create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/fastrand.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/go.mod create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/LICENSE create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/README.md create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.mod create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.sum create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/histogram.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/modules.txt create mode 100644 vendor/github.com/valyala/fastrand/.travis.yml create mode 100644 vendor/github.com/valyala/fastrand/LICENSE create mode 100644 vendor/github.com/valyala/fastrand/README.md create mode 100644 vendor/github.com/valyala/fastrand/fastrand.go create mode 100644 vendor/github.com/valyala/fastrand/go.mod create mode 100644 vendor/github.com/valyala/histogram/LICENSE create mode 100644 vendor/github.com/valyala/histogram/README.md create mode 100644 vendor/github.com/valyala/histogram/go.mod create mode 100644 vendor/github.com/valyala/histogram/go.sum create mode 100644 vendor/github.com/valyala/histogram/histogram.go create mode 100644 vendor/github.com/valyala/histogram/histogram_test.go create mode 100644 vendor/github.com/valyala/histogram/histogram_timing_test.go create mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/.travis.yml create mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/LICENSE create mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/README.md create mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/fastrand.go create mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/go.mod create mode 100644 vendor/github.com/valyala/histogram/vendor/modules.txt diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 2783673..02fdeee 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -49,7 +49,6 @@ import hashicorp_lru "github.com/hashicorp/golang-lru" import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/errormsg" -import "github.com/prometheus/client_golang/prometheus" //import "github.com/deroproject/derosuite/address" import "github.com/deroproject/derohe/block" @@ -58,6 +57,7 @@ import "github.com/deroproject/derohe/transaction" import "github.com/deroproject/derohe/blockchain/mempool" import "github.com/deroproject/derohe/blockchain/regpool" import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/metrics" /* import "github.com/deroproject/derosuite/emission" @@ -67,7 +67,7 @@ import "github.com/deroproject/derosuite/crypto/ringct" import "github.com/deroproject/derosuite/checkpoints" -import "github.com/deroproject/derosuite/metrics" + import "github.com/deroproject/derosuite/blockchain/inputmaturity" */ @@ -276,16 +276,7 @@ func Blockchain_Start(params map[string]interface{}) (*Blockchain, error) { */ - /* - // register the metrics with the metrics registry - metrics.Registry.MustRegister(blockchain_tx_counter) - metrics.Registry.MustRegister(mempool_tx_counter) - metrics.Registry.MustRegister(mempool_tx_count) - metrics.Registry.MustRegister(block_size) - metrics.Registry.MustRegister(transaction_size) - metrics.Registry.MustRegister(block_tx_count) - metrics.Registry.MustRegister(block_processing_time) - */ + atomic.AddUint32(&globals.Subsystem_Active, 1) // increment subsystem return &chain, nil @@ -334,20 +325,24 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro } }() - blockchain_tx_counter.Add(float64(len(cbl.Bl.Tx_hashes))) - block_tx_count.Observe(float64(len(cbl.Bl.Tx_hashes))) - block_processing_time.Observe(float64(time.Now().Sub(processing_start).Round(time.Millisecond) / 1000000)) - - // tracks counters for tx_size + metrics.Blockchain_tx_counter.Add(len(cbl.Bl.Tx_hashes)) + metrics.Block_tx.Update(float64(len(cbl.Bl.Tx_hashes))) + metrics.Block_processing_time.UpdateDuration(processing_start) + // tracks counters for tx internals, do we need to serialize everytime, just for stats { complete_block_size := 0 for i := 0; i < len(cbl.Txs); i++ { tx_size := len(cbl.Txs[i].Serialize()) complete_block_size += tx_size - transaction_size.Observe(float64(tx_size)) + metrics.Transaction_size.Update(float64(tx_size)) + + metrics.Transaction_outputs.Update(float64(len(cbl.Txs[i].Payloads))) + if len(cbl.Txs[i].Payloads) >= 1 { + metrics.Transaction_ring_size.Update(float64(cbl.Txs[i].Payloads[0].Statement.RingSize)) + } } - block_size.Observe(float64(complete_block_size)) + metrics.Block_size.Update(float64(complete_block_size)) } }() @@ -586,49 +581,73 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro } } - // another check, whether the tx is build with the latest snapshot of balance tree + + // another check, whether the tx is build with the correct height reference { for i := 0; i < len(cbl.Txs); i++ { if cbl.Txs[i].TransactionType == transaction.NORMAL || cbl.Txs[i].TransactionType == transaction.BURN_TX || cbl.Txs[i].TransactionType == transaction.SC_TX { - if cbl.Txs[i].Height+1 != cbl.Bl.Height { + if cbl.Txs[i].Height % config.BLOCK_BATCH_SIZE != 0 { + block_logger.Warnf("invalid tx mined %s tx height not a multiple of %d", cbl.Txs[i].GetHash(), config.BLOCK_BATCH_SIZE) + return errormsg.ErrTXDoubleSpend, false + } + + if cbl.Txs[i].Height >= cbl.Bl.Height { // chain height should be more than tx block_logger.Warnf("invalid tx mined %s", cbl.Txs[i].GetHash()) return errormsg.ErrTXDoubleSpend, false } + if !Verify_Transaction_NonCoinbase_Height(cbl.Txs[i],cbl.Bl.Height ){ + block_logger.Warnf("invalid tx mined height issue %s %d %d", cbl.Txs[i].GetHash(),cbl.Txs[i].Height,cbl.Bl.Height ) + return errormsg.ErrTXDoubleSpend, false + } } } } + // another check, whether the tx contains any duplicate nonces within the block // block wide duplicate input detector { nonce_map := map[crypto.Hash]bool{} for i := 0; i < len(cbl.Txs); i++ { - if cbl.Txs[i].TransactionType == transaction.NORMAL || cbl.Txs[i].TransactionType == transaction.BURN_TX || cbl.Txs[i].TransactionType == transaction.SC_TX { - if _, ok := nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce()]; ok { - block_logger.Warnf("Double Spend attack within block %s", cbl.Txs[i].GetHash()) + if _, ok := nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce1()]; ok { + block_logger.Warnf("Double Spend attack within block nonce1 %s", cbl.Txs[i].GetHash()) return errormsg.ErrTXDoubleSpend, false } - nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce()] = true + nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce1()] = true + + if _, ok := nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce2()]; ok { + block_logger.Warnf("Double Spend attack within block nonce2 %s", cbl.Txs[i].GetHash()) + return errormsg.ErrTXDoubleSpend, false + } + nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce2()] = true } } } // we also need to reject if the the immediately reachable history, has spent the nonce // both the checks works on the basis of nonces and not on the basis of txhash - /* { - reachable_nonces := chain.BuildReachabilityNonces(bl) + reachable_nonces,err := chain.BuildNonces(bl.Tips) + if err != nil{ + return err,false + } for i := 0; i < len(cbl.Txs); i++ { // loop through all the TXs - if cbl.Txs[i].TransactionType == transaction.NORMAL { - if _, ok := reachable_nonces[cbl.Txs[i].Proof.Nonce()]; ok { + switch cbl.Txs[i].TransactionType { + case transaction.PREMINE,transaction.REGISTRATION, transaction.COINBASE: // these donot have nonces + + case transaction.NORMAL, transaction.BURN_TX, transaction.SC_TX: // these have nonces + + if reachable_nonces[cbl.Txs[i].Payloads[0].Proof.Nonce1()] || reachable_nonces[cbl.Txs[i].Payloads[0].Proof.Nonce1()] { block_logger.Warnf("Double spend attack tx %s is already mined, rejecting ", cbl.Txs[i].GetHash()) return errormsg.ErrTXDoubleSpend, false } + default: } } - }*/ + } + // we need to anyways verify the TXS since proofs are not covered by checksum { @@ -729,11 +748,53 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro base_topo_index = 0 } + nonce_map := map[crypto.Hash]bool{} + + // any blocks which have not changed their topo will be skipped using graviton trick skip := true for i := int64(0); i < int64(len(full_order)); i++ { + if i == 0 { + bl_prev, err1 := chain.Load_BL_FROM_ID(full_order[0]) + if err1 != nil { + block_logger.Debugf("Cannot load block %s for client protocol,probably DB/disk corruption", full_order[0]) + return errormsg.ErrInvalidBlock, false + } + nonce_map,err = chain.BuildNonces(bl_prev.Tips) + if err != nil { + panic(err) + } + } + + if i >= 1 { // load nonces for previous blocks + bl_prev_hash := full_order[i-1] + bl_prev, err1 := chain.Load_BL_FROM_ID(full_order[i-1]) + if err1 != nil { + block_logger.Debugf("Cannot load block %s for client protocol,probably DB/disk corruption", bl_prev_hash) + return errormsg.ErrInvalidBlock, false + } + + for i := 0; i < len(bl_prev.Tx_hashes); i++ { // load all tx one by one, no skipping + tx_bytes, err := chain.Store.Block_tx_store.ReadTX(bl_prev.Tx_hashes[i]) + if err != nil { + panic(fmt.Errorf("Cannot load tx for %s err %s", bl_prev.Tx_hashes[i], err)) + } + + var tx transaction.Transaction + if err = tx.DeserializeHeader(tx_bytes); err != nil { + panic(err) + } + + if !tx.IsRegistration(){ + // tx has been loaded, now lets get the nonce + nonce_map[tx.Payloads[0].Proof.Nonce1()] = true // add element to map for next check + nonce_map[tx.Payloads[0].Proof.Nonce2()] = true // add element to map for next check + } + } + } + // check whether the new block is at the same position at the last position current_topo_block := i + base_topo_index previous_topo_block := current_topo_block - 1 @@ -761,7 +822,7 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro bl_current_hash := full_order[i] bl_current, err1 := chain.Load_BL_FROM_ID(bl_current_hash) if err1 != nil { - block_logger.Debugf("Cannot load block %s for client protocol,probably DB corruption", bl_current_hash) + block_logger.Debugf("Cannot load block %s for client protocol,probably DB/disk corruption", bl_current_hash) return errormsg.ErrInvalidBlock, false } @@ -770,9 +831,6 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro height_current := chain.Calculate_Height_At_Tips(bl_current.Tips) hard_fork_version_current := chain.Get_Current_Version_at_Height(height_current) - // this version does not require client protocol as of now - // run full client protocol and find valid transactions - // rlog.Debugf("running client protocol for %s minertx %s topo %d", bl_current_hash, bl_current.Miner_TX.GetHash(), highest_topo) // generate miner TX rewards as per client protocol if hard_fork_version_current == 1 { @@ -780,7 +838,6 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro } var balance_tree, sc_meta *graviton.Tree - _ = sc_meta var ss *graviton.Snapshot if bl_current.Height == 0 { // if it's genesis block @@ -818,14 +875,14 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro fees_collected := uint64(0) - // side blocks only represent chain strenth , else they are are ignored + // side blocks not only represent chain strenth , they also represent extra data // this means they donot get any reward , 0 reward // their transactions are ignored //chain.Store.Topo_store.Write(i+base_topo_index, full_order[i],0, int64(bl_current.Height)) // write entry so as sideblock could work var data_trees []*graviton.Tree - if !chain.isblock_SideBlock_internal(full_order[i], current_topo_block, int64(bl_current.Height)) { + { sc_change_cache := map[crypto.Hash]*graviton.Tree{} // cache entire changes for entire block @@ -837,12 +894,23 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro if err = tx.DeserializeHeader(tx_bytes); err != nil { panic(err) } + + + if tx.TransactionType != transaction.REGISTRATION { + + if nonce_map[tx.Payloads[0].Proof.Nonce1()] || nonce_map[tx.Payloads[0].Proof.Nonce1()] { + continue // skip this tx, since it has been processed earlier + }else{ + nonce_map[tx.Payloads[0].Proof.Nonce1()] = true + nonce_map[tx.Payloads[0].Proof.Nonce2()] = true + } for t := range tx.Payloads { if !tx.Payloads[t].SCID.IsZero() { tree, _ := ss.GetTree(string(tx.Payloads[t].SCID[:])) sc_change_cache[tx.Payloads[t].SCID] = tree } } + } // we have loaded a tx successfully, now lets execute it tx_fees := chain.process_transaction(sc_change_cache, tx, balance_tree) @@ -886,11 +954,17 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro } - chain.process_miner_transaction(bl_current.Miner_TX, bl_current.Height == 0, balance_tree, fees_collected, bl_current.Height) - } else { - rlog.Debugf("this block is a side block block height %d blid %s ", chain.Load_Block_Height(full_order[i]), full_order[i]) - } + // if block is side block, pass all reward to devs, else pass it to original miner + side_block := chain.isblock_SideBlock_internal(full_order[i], current_topo_block, int64(bl_current.Height)) + if side_block{ + rlog.Debugf("this block is a side block block height %d blid %s ", chain.Load_Block_Height(full_order[i]), full_order[i]) + } + chain.process_miner_transaction(bl_current.Miner_TX, bl_current.Height == 0, balance_tree, fees_collected, bl_current.Height,side_block) + + + + } // we are here, means everything is okay, lets commit the update balance tree @@ -1135,53 +1209,6 @@ func (chain *Blockchain) BlockCheckSum(cbl *block.Complete_Block) []byte { return h.Sum(nil) } -// various counters/gauges which track a numer of metrics -// such as number of txs, number of inputs, number of outputs -// mempool total addition, current mempool size -// block processing time etcs - -// Try it once more, this time with a help string. -var blockchain_tx_counter = prometheus.NewCounter(prometheus.CounterOpts{ - Name: "blockchain_tx_counter", - Help: "Number of tx mined", -}) - -var mempool_tx_counter = prometheus.NewCounter(prometheus.CounterOpts{ - Name: "mempool_tx_counter", - Help: "Total number of tx added in mempool", -}) -var mempool_tx_count = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "mempool_tx_count", - Help: "Number of tx in mempool at this point", -}) - -// track block size about 2 MB -var block_size = prometheus.NewHistogram(prometheus.HistogramOpts{ - Name: "block_size_byte", - Help: "Block size in byte (complete)", - Buckets: prometheus.LinearBuckets(0, 102400, 10), // start block size 0, each 1 KB step, 2048 such buckets . -}) - -// track transaction size upto 500 KB -var transaction_size = prometheus.NewHistogram(prometheus.HistogramOpts{ - Name: "tx_size_byte", - Help: "TX size in byte", - Buckets: prometheus.LinearBuckets(0, 10240, 16), // start 0 byte, each 1024 byte, 512 such buckets. -}) - -// number of tx per block -var block_tx_count = prometheus.NewHistogram(prometheus.HistogramOpts{ - Name: "block_tx_count", - Help: "Number of TX in the block", - Buckets: prometheus.LinearBuckets(0, 20, 25), // start 0 byte, each 1024 byte, 1024 such buckets. -}) - -// -var block_processing_time = prometheus.NewHistogram(prometheus.HistogramOpts{ - Name: "block_processing_time_ms", - Help: "Block processing time milliseconds", - Buckets: prometheus.LinearBuckets(0, 100, 20), // start 0 ms, each 100 ms, 200 such buckets. -}) // this is the only entrypoint for new txs in the chain // add a transaction to MEMPOOL, @@ -1222,7 +1249,7 @@ func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) error { } // track counter for the amount of mempool tx - defer mempool_tx_count.Set(float64(len(chain.Mempool.Mempool_List_TX()))) + defer metrics.Mempool_tx_count.Set(uint64(len(chain.Mempool.Mempool_List_TX()))) txhash := tx.GetHash() @@ -1233,9 +1260,9 @@ func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) error { } chain_height := uint64(chain.Get_Height()) - if chain_height > tx.Height { - rlog.Tracef(2, "TX %s rejected since chain has already progressed", txhash) - return fmt.Errorf("TX %s rejected since chain has already progressed", txhash) + if !Verify_Transaction_NonCoinbase_Height(tx,chain_height) { + rlog.Tracef(2, "TX %s rejected since tx is too recent or too old", txhash) + return fmt.Errorf("TX %s rejected since tx is too recent or too old", txhash) } // quick check without calculating everything whether tx is in pool, if yes we do nothing @@ -1285,11 +1312,11 @@ func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) error { if chain.Mempool.Mempool_Add_TX(tx, 0) { // new tx come with 0 marker rlog.Tracef(2, "Successfully added tx %s to pool", txhash) - mempool_tx_counter.Inc() + metrics.Mempool_tx_counter.Inc() return nil } else { - rlog.Tracef(2, "TX %s rejected by pool", txhash) - return fmt.Errorf("TX %s rejected by pool", txhash) + rlog.Tracef(2, "TX %s rejected by pool by mempool", txhash) + return fmt.Errorf("TX %s rejected by pool by mempool", txhash) } } @@ -1394,14 +1421,18 @@ func (chain *Blockchain) isblock_SideBlock_internal(blid crypto.Hash, block_topo return false } -// this will return the tx combination as valid/invalid + +// this will return the block ids in which tx has been mined // this is not used as core consensus but reports only to user that his tx though in the blockchain is invalid // a tx is valid, if it exist in a block which is not a side block -func (chain *Blockchain) IS_TX_Valid(txhash crypto.Hash) (valid_blid crypto.Hash, invalid_blid []crypto.Hash, valid bool) { +// this is not part of consensus but only for support of explorer +func (chain *Blockchain) IS_TX_Mined(txhash crypto.Hash) (mined_blocks []crypto.Hash, state_block crypto.Hash, state_block_topo int64) { var tx_bytes []byte var err error + state_found := false + if tx_bytes, err = chain.Store.Block_tx_store.ReadTX(txhash); err != nil { return } @@ -1411,11 +1442,24 @@ func (chain *Blockchain) IS_TX_Valid(txhash crypto.Hash) (valid_blid crypto.Hash return } - blids, _ := chain.Store.Topo_store.binarySearchHeight(int64(tx.Height + 1)) + for i := int64(0); i < 2 *config.BLOCK_BATCH_SIZE;i++ { + blids, topos := chain.Store.Topo_store.binarySearchHeight(int64(tx.Height) + i ) - var exist_list []crypto.Hash + for j, blid := range blids { + if !state_found{ // keep finding state + merkle_hash, err := chain.Load_Merkle_Hash(topos[j]) + if err != nil { + panic(err) + } + + if len(tx.Payloads) >= 1 && merkle_hash == tx.Payloads[0].Statement.Roothash { + state_block = blid + state_block_topo = topos[j] + state_found = true + } + + } - for _, blid := range blids { bl, err := chain.Load_BL_FROM_ID(blid) if err != nil { return @@ -1423,123 +1467,19 @@ func (chain *Blockchain) IS_TX_Valid(txhash crypto.Hash) (valid_blid crypto.Hash for _, bltxhash := range bl.Tx_hashes { if bltxhash == txhash { - exist_list = append(exist_list, blid) + mined_blocks = append(mined_blocks, blid) break } } } - for _, blid := range exist_list { - if chain.Isblock_SideBlock(blid) { - invalid_blid = append(invalid_blid, blid) - } else { - valid_blid = blid - valid = true - } - } + + return } -/* - - -// runs the client protocol which includes the following operations -// if any TX are being duplicate or double-spend ignore them -// mark all the valid transactions as valid -// mark all invalid transactions as invalid -// calculate total fees based on valid TX -// we need NOT check ranges/ring signatures here, as they have been done already by earlier steps -func (chain *Blockchain) client_protocol(dbtx storage.DBTX, bl *block.Block, blid crypto.Hash, height int64, topoheight int64) (total_fees uint64) { - // run client protocol for all TXs - for i := range bl.Tx_hashes { - tx, err := chain.Load_TX_FROM_ID(dbtx, bl.Tx_hashes[i]) - if err != nil { - panic(fmt.Errorf("Cannot load tx for %x err %s ", bl.Tx_hashes[i], err)) - } - // mark TX found in this block also for explorer - chain.store_TX_in_Block(dbtx, blid, bl.Tx_hashes[i]) - - // check all key images as double spend, if double-spend detected mark invalid, else consider valid - if chain.Verify_Transaction_NonCoinbase_DoubleSpend_Check(dbtx, tx) { - - chain.consume_keyimages(dbtx, tx, height) // mark key images as consumed - total_fees += tx.RctSignature.Get_TX_Fee() - - chain.Store_TX_Height(dbtx, bl.Tx_hashes[i], topoheight) // link the tx with the topo height - - //mark tx found in this block is valid - chain.mark_TX(dbtx, blid, bl.Tx_hashes[i], true) - - } else { // TX is double spend or reincluded by 2 blocks simultaneously - rlog.Tracef(1,"Double spend TX is being ignored %s %s", blid, bl.Tx_hashes[i]) - chain.mark_TX(dbtx, blid, bl.Tx_hashes[i], false) - } - } - - return total_fees -} - -// this undoes everything that is done by client protocol -// NOTE: this will have any effect, only if client protocol has been run on this block earlier -func (chain *Blockchain) client_protocol_reverse(dbtx storage.DBTX, bl *block.Block, blid crypto.Hash) { - // run client protocol for all TXs - for i := range bl.Tx_hashes { - tx, err := chain.Load_TX_FROM_ID(dbtx, bl.Tx_hashes[i]) - if err != nil { - panic(fmt.Errorf("Cannot load tx for %x err %s ", bl.Tx_hashes[i], err)) - } - // only the valid TX must be revoked - if chain.IS_TX_Valid(dbtx, blid, bl.Tx_hashes[i]) { - chain.revoke_keyimages(dbtx, tx) // mark key images as not used - - chain.Store_TX_Height(dbtx, bl.Tx_hashes[i], -1) // unlink the tx with the topo height - - //mark tx found in this block is invalid - chain.mark_TX(dbtx, blid, bl.Tx_hashes[i], false) - - } else { // TX is double spend or reincluded by 2 blocks simultaneously - // invalid tx is related - } - } - - return -} - -// scavanger for transactions from rusty/stale tips to reinsert them into pool -func (chain *Blockchain) transaction_scavenger(dbtx storage.DBTX, blid crypto.Hash) { - defer func() { - if r := recover(); r != nil { - logger.Warnf("Recovered while transaction scavenging, Stack trace below ") - logger.Warnf("Stack trace \n%s", debug.Stack()) - } - }() - - logger.Debugf("scavenging transactions from blid %s", blid) - reachable_blocks := chain.BuildReachableBlocks(dbtx, []crypto.Hash{blid}) - reachable_blocks[blid] = true // add self - for k, _ := range reachable_blocks { - if chain.Is_Block_Orphan(k) { - bl, err := chain.Load_BL_FROM_ID(dbtx, k) - if err == nil { - for i := range bl.Tx_hashes { - tx, err := chain.Load_TX_FROM_ID(dbtx, bl.Tx_hashes[i]) - if err != nil { - rlog.Warnf("err while scavenging blid %s txid %s err %s", k, bl.Tx_hashes[i], err) - } else { - // add tx to pool, it will do whatever is necessarry - chain.Add_TX_To_Pool(tx) - } - } - } else { - rlog.Warnf("err while scavenging blid %s err %s", k, err) - } - } - } -} - -*/ // Finds whether a block is orphan // since we donot store any fields, we need to calculate/find the block as orphan // using an algorithm @@ -1548,14 +1488,6 @@ func (chain *Blockchain) Is_Block_Orphan(hash crypto.Hash) bool { return !chain.Is_Block_Topological_order(hash) } -// this is used to find if a tx is orphan, YES orphan TX -// these can occur during when they lie only in a side block -// so the TX becomes orphan ( chances are less may be less that .000001 % but they are there) -// if a tx is not valid in any of the blocks, it has been mined it is orphan -func (chain *Blockchain) Is_TX_Orphan(hash crypto.Hash) (result bool) { - _, _, result = chain.IS_TX_Valid(hash) - return !result -} // verifies whether we are lagging // return true if we need resync @@ -1628,6 +1560,74 @@ func (chain *Blockchain) Rewind_Chain(rewind_count int) (result bool) { return true } + +// used to check for nonce duplication, so as txs can avoid reexecution +// note that the tips might not be part of chain +func (chain *Blockchain) BuildNonces(tips []crypto.Hash) (map[crypto.Hash]bool, error) { + nonce_list := map[crypto.Hash]bool{} // contains a list of all reachable blocks + + var blocks_map = map[crypto.Hash]bool{} + var blocks_pending []crypto.Hash + for i := range tips { + blocks_pending = append(blocks_pending,tips[i]) + } + + height := chain.Calculate_Height_At_Tips(tips) // we are 1 higher than previous highest tip + + + for ; len(blocks_pending) > 0; { + + blid := blocks_pending[len(blocks_pending)-1] + blocks_pending = blocks_pending[:len(blocks_pending)-1] + + if _, processed := blocks_map[blid]; processed { + continue + } + blocks_map[blid] = true + + bl, err := chain.Load_BL_FROM_ID(blid) + if err != nil { + return nil,err + } + + if bl.Height == 0 { // if we reach genesis, we skip + continue + } + + if int64(bl.Height + (config.BLOCK_BATCH_SIZE*4)) < height { // if we are too deep, skip past + continue + } + + blocks_pending = append(blocks_pending,bl.Tips...) + + + for i := 0; i < len(bl.Tx_hashes); i++ { // load all tx one by one, no skipping + tx_bytes, err := chain.Store.Block_tx_store.ReadTX(bl.Tx_hashes[i]) + if err != nil { + panic(fmt.Errorf("Cannot load tx for %s err %s", bl.Tx_hashes[i], err)) + } + + var tx transaction.Transaction + if err = tx.DeserializeHeader(tx_bytes); err != nil { + panic(err) + } + + if !tx.IsRegistration(){ + + // tx has been loaded, now lets get the nonce + nonce_list[tx.Payloads[0].Proof.Nonce1()] = true // add element to map for next check + nonce_list[tx.Payloads[0].Proof.Nonce2()] = true // add element to map for next check + } + + } + + + } + + return nonce_list,nil +} + + // build reachability graph upto 2*config deeps to answer reachability queries func (chain *Blockchain) buildReachability_internal(reachmap map[crypto.Hash]bool, blid crypto.Hash, level int) { bl, err := chain.Load_BL_FROM_ID(blid) @@ -1731,7 +1731,8 @@ func (chain *Blockchain) BuildReachabilityNonces(bl *block.Block) map[crypto.Has } // tx has been loaded, now lets get the nonce - nonce_reach_map[tx.Payloads[0].Proof.Nonce()] = true // add element to map for next check + nonce_reach_map[tx.Payloads[0].Proof.Nonce1()] = true // add element to map for next check + nonce_reach_map[tx.Payloads[0].Proof.Nonce2()] = true // add element to map for next check } } return nonce_reach_map diff --git a/blockchain/mempool/mempool.go b/blockchain/mempool/mempool.go index f5c3d63..a49e720 100644 --- a/blockchain/mempool/mempool.go +++ b/blockchain/mempool/mempool.go @@ -31,6 +31,7 @@ import log "github.com/sirupsen/logrus" import "github.com/deroproject/derohe/transaction" import "github.com/deroproject/derohe/globals" +import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/cryptography/crypto" // this is only used for sorting and nothing else @@ -202,7 +203,7 @@ func (pool *Mempool) HouseKeeping(height uint64) { pool.txs.Range(func(k, value interface{}) bool { txhash := k.(crypto.Hash) v := value.(*mempool_object) - if height >= (v.Tx.Height + 1) { // if we have moved 1 heights, chances are reorg are almost nil + if height >= (v.Tx.Height + 3* config.BLOCK_BATCH_SIZE + 1) { // if we have moved 1 heights, chances are reorg are almost nil delete_list = append(delete_list, txhash) } return true @@ -283,7 +284,12 @@ func (pool *Mempool) Mempool_Add_TX(tx *transaction.Transaction, Height uint64) var object mempool_object tx_hash := crypto.Hash(tx.GetHash()) - if pool.Mempool_Keyimage_Spent(tx.Payloads[0].Proof.Nonce()) { + if pool.Mempool_Keyimage_Spent(tx.Payloads[0].Proof.Nonce1()) { + rlog.Debugf("Rejecting TX, since nonce already seen %x", tx_hash) + return false + } + + if pool.Mempool_Keyimage_Spent(tx.Payloads[0].Proof.Nonce2()) { rlog.Debugf("Rejecting TX, since nonce already seen %x", tx_hash) return false } @@ -300,7 +306,8 @@ func (pool *Mempool) Mempool_Add_TX(tx *transaction.Transaction, Height uint64) // pool.key_images.Store(tx.Vin[i].(transaction.Txin_to_key).K_image,true) // add element to map for next check // } - pool.key_images.Store(tx.Payloads[0].Proof.Nonce(), true) + pool.key_images.Store(tx.Payloads[0].Proof.Nonce1(), true) + pool.key_images.Store(tx.Payloads[0].Proof.Nonce2(), true) // we are here means we can add it to pool object.Tx = tx @@ -367,7 +374,8 @@ func (pool *Mempool) Mempool_Delete_TX(txid crypto.Hash) (tx *transaction.Transa // for i := 0; i < len(object.Tx.Vin); i++ { // pool.key_images.Delete(object.Tx.Vin[i].(transaction.Txin_to_key).K_image) // } - pool.key_images.Delete(tx.Payloads[0].Proof.Nonce()) + pool.key_images.Delete(tx.Payloads[0].Proof.Nonce1()) + pool.key_images.Delete(tx.Payloads[0].Proof.Nonce2()) //pool.sort_list() // sort and update pool list pool.modified = true // pool has been modified diff --git a/blockchain/miner_block.go b/blockchain/miner_block.go index d362a24..b1c68de 100644 --- a/blockchain/miner_block.go +++ b/blockchain/miner_block.go @@ -74,7 +74,6 @@ func (chain *Blockchain) Create_new_miner_block(miner_address rpc.Address, tx *t if err != nil { panic(err) } - } topoheight := chain.Load_TOPO_HEIGHT() @@ -116,6 +115,15 @@ func (chain *Blockchain) Create_new_miner_block(miner_address rpc.Address, tx *t sizeoftxs := uint64(0) // size of all non coinbase tx included within this block //fees_collected := uint64(0) + + nonce_map,err := chain.BuildNonces(bl.Tips) + if err != nil { + panic(err) + } + + local_nonce_map := map[crypto.Hash]bool{} + + _ = sizeoftxs // add upto 100 registration tx each registration tx is 99 bytes, so 100 tx will take 9900 bytes or 10KB @@ -129,9 +137,8 @@ func (chain *Blockchain) Create_new_miner_block(miner_address rpc.Address, tx *t _, err = balance_tree.Get(tx.MinerAddress[:]) if err != nil { if xerrors.Is(err, graviton.ErrNotFound) { // address needs registration - cbl.Txs = append(cbl.Txs, tx) - tx_hash_list_included = append(tx_hash_list_included, tx_hash_list_sorted[i]) + tx_hash_list_included = append(tx_hash_list_included, tx_hash_list_sorted[i]) } else { panic(err) } @@ -159,7 +166,7 @@ func (chain *Blockchain) Create_new_miner_block(miner_address rpc.Address, tx *t } tx := chain.Mempool.Mempool_Get_TX(tx_hash_list_sorted[i].Hash) - if tx != nil && int64(tx.Height)+1 == height { + if tx != nil && Verify_Transaction_NonCoinbase_Height(tx,uint64(height)) { /* // skip and delete any mempool tx @@ -180,10 +187,22 @@ func (chain *Blockchain) Create_new_miner_block(miner_address rpc.Address, tx *t continue } */ - rlog.Tracef(1, "Adding Top Sorted tx %s to Complete_Block current size %.2f KB max possible %.2f KB\n", tx_hash_list_sorted[i].Hash, float32(sizeoftxs+tx_hash_list_sorted[i].Size)/1024.0, float32(config.STARGATE_HE_MAX_BLOCK_SIZE)/1024.0) - sizeoftxs += tx_hash_list_sorted[i].Size - cbl.Txs = append(cbl.Txs, tx) - tx_hash_list_included = append(tx_hash_list_included, tx_hash_list_sorted[i].Hash) + + + if nonce_map[tx.Payloads[0].Proof.Nonce1()] || nonce_map[tx.Payloads[0].Proof.Nonce1()] || + local_nonce_map[tx.Payloads[0].Proof.Nonce1()] || local_nonce_map[tx.Payloads[0].Proof.Nonce1()] { + continue // skip this tx + } + + cbl.Txs = append(cbl.Txs, tx) + tx_hash_list_included = append(tx_hash_list_included, tx_hash_list_sorted[i].Hash) + local_nonce_map[tx.Payloads[0].Proof.Nonce1()] = true + local_nonce_map[tx.Payloads[0].Proof.Nonce2()] = true + rlog.Tracef(1, "Adding Top Sorted tx %s to Complete_Block current size %.2f KB max possible %.2f KB\n", tx_hash_list_sorted[i].Hash, float32(sizeoftxs+tx_hash_list_sorted[i].Size)/1024.0, float32(config.STARGATE_HE_MAX_BLOCK_SIZE)/1024.0) + sizeoftxs += tx_hash_list_sorted[i].Size + + + } } // any left over transactions, should be randomly selected @@ -204,12 +223,21 @@ func (chain *Blockchain) Create_new_miner_block(miner_address rpc.Address, tx *t } tx := chain.Mempool.Mempool_Get_TX(tx_hash_list_sorted[i].Hash) - if tx != nil && int64(tx.Height)+1 == height { + if tx != nil && Verify_Transaction_NonCoinbase_Height(tx, uint64(height)){ - rlog.Tracef(1, "Adding Random tx %s to Complete_Block current size %.2f KB max possible %.2f KB\n", tx_hash_list_sorted[i].Hash, float32(sizeoftxs+tx_hash_list_sorted[i].Size)/1024.0, float32(config.STARGATE_HE_MAX_BLOCK_SIZE)/1024.0) - sizeoftxs += tx_hash_list_sorted[i].Size - cbl.Txs = append(cbl.Txs, tx) - tx_hash_list_included = append(tx_hash_list_included, tx_hash_list_sorted[i].Hash) + + if nonce_map[tx.Payloads[0].Proof.Nonce1()] || nonce_map[tx.Payloads[0].Proof.Nonce1()] || + local_nonce_map[tx.Payloads[0].Proof.Nonce1()] || local_nonce_map[tx.Payloads[0].Proof.Nonce1()] { + continue // skip this tx + } + + cbl.Txs = append(cbl.Txs, tx) + tx_hash_list_included = append(tx_hash_list_included, tx_hash_list_sorted[i].Hash) + local_nonce_map[tx.Payloads[0].Proof.Nonce1()] = true + local_nonce_map[tx.Payloads[0].Proof.Nonce2()] = true + rlog.Tracef(1, "Adding Random tx %s to Complete_Block current size %.2f KB max possible %.2f KB\n", tx_hash_list_sorted[i].Hash, float32(sizeoftxs+tx_hash_list_sorted[i].Size)/1024.0, float32(config.STARGATE_HE_MAX_BLOCK_SIZE)/1024.0) + + sizeoftxs += tx_hash_list_sorted[i].Size } } diff --git a/blockchain/transaction_execute.go b/blockchain/transaction_execute.go index 6f47e61..424e331 100644 --- a/blockchain/transaction_execute.go +++ b/blockchain/transaction_execute.go @@ -54,7 +54,7 @@ func CalcBlockReward(height uint64) uint64 { } // process the miner tx, giving fees, miner rewatd etc -func (chain *Blockchain) process_miner_transaction(tx transaction.Transaction, genesis bool, balance_tree *graviton.Tree, fees uint64, height uint64) { +func (chain *Blockchain) process_miner_transaction(tx transaction.Transaction, genesis bool, balance_tree *graviton.Tree, fees uint64, height uint64,sideblock bool) { var acckey crypto.Point if err := acckey.DecodeCompressed(tx.MinerAddress[:]); err != nil { panic(err) @@ -69,29 +69,26 @@ func (chain *Blockchain) process_miner_transaction(tx transaction.Transaction, g // general coin base transaction base_reward := CalcBlockReward(uint64(height)) - full_reward := base_reward + fees + full_reward := base_reward+ fees - dev_reward := (full_reward * config.DEVSHARE) / 10000 // take % from reward - miner_reward := full_reward - dev_reward // it's value, do subtraction - { // giver miner reward - balance_serialized, err := balance_tree.Get(tx.MinerAddress[:]) - if err != nil { - panic(err) - } - balance := new(crypto.ElGamal).Deserialize(balance_serialized) - balance = balance.Plus(new(big.Int).SetUint64(miner_reward)) // add miners reward to miners balance homomorphically - balance_tree.Put(tx.MinerAddress[:], balance.Serialize()) // reserialize and store - } - - { // give devs reward + if sideblock {// give devs reward balance_serialized, err := balance_tree.Get(chain.Dev_Address_Bytes[:]) if err != nil { panic(err) } balance := new(crypto.ElGamal).Deserialize(balance_serialized) - balance = balance.Plus(new(big.Int).SetUint64(dev_reward)) // add devs reward to devs balance homomorphically + balance = balance.Plus(new(big.Int).SetUint64(full_reward)) // add devs reward to devs balance homomorphically balance_tree.Put(chain.Dev_Address_Bytes[:], balance.Serialize()) // reserialize and store + + }else{ // giver miner reward + balance_serialized, err := balance_tree.Get(tx.MinerAddress[:]) + if err != nil { + panic(err) + } + balance := new(crypto.ElGamal).Deserialize(balance_serialized) + balance = balance.Plus(new(big.Int).SetUint64(full_reward)) // add miners reward to miners balance homomorphically + balance_tree.Put(tx.MinerAddress[:], balance.Serialize()) // reserialize and store } return @@ -106,6 +103,9 @@ func (chain *Blockchain) process_transaction(changed map[crypto.Hash]*graviton.T switch tx.TransactionType { case transaction.REGISTRATION: + if _, err := balance_tree.Get(tx.MinerAddress[:]); err == nil { + return 0 + } if _, err := balance_tree.Get(tx.MinerAddress[:]); err != nil { if !xerrors.Is(err, graviton.ErrNotFound) { // any other err except not found panic panic(err) diff --git a/blockchain/transaction_verify.go b/blockchain/transaction_verify.go index a62ff01..31fd4bf 100644 --- a/blockchain/transaction_verify.go +++ b/blockchain/transaction_verify.go @@ -113,6 +113,34 @@ func (chain *Blockchain) Verify_Transaction_Coinbase(cbl *block.Complete_Block, return nil // success comes last } + +// only verifies height whether all height checks are good +func Verify_Transaction_NonCoinbase_Height(tx *transaction.Transaction, chain_height uint64) bool { + return Verify_Transaction_Height(tx.Height, chain_height) +} + + +func Verify_Transaction_Height(tx_height, chain_height uint64) bool{ + if tx_height % config.BLOCK_BATCH_SIZE != 0 { + return false + } + + if tx_height >= chain_height { + return false + } + + if chain_height-tx_height <= 5 { // we should be atleast 5 steps from top + return false + } + + comp := (chain_height / config.BLOCK_BATCH_SIZE) - (tx_height / config.BLOCK_BATCH_SIZE) + if comp ==0 || comp ==1 { + return true + }else{ + return false + } +} + // all non miner tx must be non-coinbase tx // each check is placed in a separate block of code, to avoid ambigous code or faulty checks // all check are placed and not within individual functions ( so as we cannot skip a check ) @@ -329,7 +357,7 @@ func (chain *Blockchain) Verify_Transaction_NonCoinbase(hf_version int64, tx *tr // at this point has been completely expanded, verify the tx statement for t := range tx.Payloads { - if !tx.Payloads[t].Proof.Verify(&tx.Payloads[t].Statement, tx.GetHash(), tx.Payloads[t].BurnValue) { + if !tx.Payloads[t].Proof.Verify(&tx.Payloads[t].Statement, tx.GetHash(), tx.Height, tx.Payloads[t].BurnValue) { fmt.Printf("Statement %+v\n", tx.Payloads[t].Statement) fmt.Printf("Proof %+v\n", tx.Payloads[t].Proof) diff --git a/cmd/derod/rpc_dero_getencryptedbalance.go b/cmd/derod/rpc_dero_getencryptedbalance.go index 88dcac7..458e195 100644 --- a/cmd/derod/rpc_dero_getencryptedbalance.go +++ b/cmd/derod/rpc_dero_getencryptedbalance.go @@ -27,6 +27,7 @@ import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/errormsg" import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/blockchain" //import "github.com/deroproject/derohe/dvm" //import "github.com/deroproject/derohe/cryptography/crypto" @@ -52,6 +53,37 @@ func (DERO_RPC_APIS) GetEncryptedBalance(ctx context.Context, p rpc.GetEncrypted topoheight = p.TopoHeight } + switch p.TopoHeight { + case rpc.RECENT_BATCH_BLOCK: // give data of specific point from where tx could be built + + chain_height := chain.Get_Height() + + var topo_list []int64 + for ;topoheight > 0; { + toporecord, err := chain.Store.Topo_store.Read(topoheight) + if err != nil { + panic(err) + } + + if blockchain.Verify_Transaction_Height(uint64(toporecord.Height), uint64(chain_height)){ + if chain_height - toporecord.Height <= (3*config.BLOCK_BATCH_SIZE)/2 { // give us enough leeway + topo_list=append(topo_list, topoheight) + } + } + + if chain_height-toporecord.Height >= 2 * config.BLOCK_BATCH_SIZE { + break; + } + + topoheight-- + } + topoheight = topo_list[len(topo_list)-1] + + case rpc.RECENT_BLOCK : fallthrough + default: + + } + toporecord, err := chain.Store.Topo_store.Read(topoheight) if err != nil { panic(err) diff --git a/cmd/derod/rpc_dero_gettransactions.go b/cmd/derod/rpc_dero_gettransactions.go index ac7228b..bc3d8f8 100644 --- a/cmd/derod/rpc_dero_gettransactions.go +++ b/cmd/derod/rpc_dero_gettransactions.go @@ -94,20 +94,17 @@ func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p rpc.GetTransaction_Pa } // also fill where the tx is found and in which block is valid and in which it is invalid - - valid_blid, invalid_blid, valid := chain.IS_TX_Valid(hash) + blid_list,state_block,state_block_topo := chain.IS_TX_Mined(hash) //logger.Infof(" tx %s related info valid_blid %s invalid_blid %+v valid %v ",hash, valid_blid, invalid_blid, valid) - if valid { - related.ValidBlock = valid_blid.String() - // topo height at which it was mined - topo_height := int64(chain.Load_Block_Topological_order(valid_blid)) - related.Block_Height = topo_height + if state_block_topo > 0 { + related.StateBlock = state_block.String() + related.Block_Height = state_block_topo if tx.TransactionType != transaction.REGISTRATION { // we must now fill in compressed ring members - if toporecord, err := chain.Store.Topo_store.Read(topo_height); err == nil { + if toporecord, err := chain.Store.Topo_store.Read(state_block_topo); err == nil { if ss, err := chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version); err == nil { if tx.TransactionType == transaction.SC_TX { @@ -141,7 +138,6 @@ func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p rpc.GetTransaction_Pa if tx.Payloads[t].SCID.IsZero() { tree, err = ss.GetTree(config.BALANCE_TREE) - } else { tree, err = ss.GetTree(string(tx.Payloads[t].SCID[:])) } @@ -166,8 +162,8 @@ func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p rpc.GetTransaction_Pa } } } - for i := range invalid_blid { - related.InvalidBlock = append(related.InvalidBlock, invalid_blid[i].String()) + for i := range blid_list { + related.MinedBlock = append(related.MinedBlock, blid_list[i].String()) } result.Txs_as_hex = append(result.Txs_as_hex, hex.EncodeToString(tx.Serialize())) diff --git a/cmd/derod/rpc_dero_sendrawtransaction.go b/cmd/derod/rpc_dero_sendrawtransaction.go index c92b0f8..94ffc70 100644 --- a/cmd/derod/rpc_dero_sendrawtransaction.go +++ b/cmd/derod/rpc_dero_sendrawtransaction.go @@ -64,8 +64,8 @@ func (DERO_RPC_APIS) SendRawTransaction(ctx context.Context, p rpc.SendRawTransa result.Status = "OK" rlog.Debugf("Incoming TXID %s from RPC Server successfully accepted by MEMPOOL", tx.GetHash()) } else { - err = fmt.Errorf("Transaction %s rejected by daemon err '%s'", tx.GetHash(), err) - rlog.Warnf("Incoming TXID %s from RPC Server rejected by POOL", tx.GetHash()) + rlog.Warnf("Incoming TXID %s from RPC Server rejected by POOL err '%s'", tx.GetHash(),err) + err = fmt.Errorf("Transaction %s rejected by daemon err '%s'", tx.GetHash(), err) } return } diff --git a/cmd/derod/websocket_server.go b/cmd/derod/websocket_server.go index cbf0d0b..e00eb2e 100644 --- a/cmd/derod/websocket_server.go +++ b/cmd/derod/websocket_server.go @@ -26,11 +26,13 @@ import "sync/atomic" import "context" import "strings" import "runtime/debug" +import "net/http/pprof" import "github.com/romana/rlog" import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/globals" +import "github.com/deroproject/derohe/metrics" import "github.com/deroproject/derohe/blockchain" import "github.com/deroproject/derohe/glue/rwc" @@ -164,12 +166,15 @@ func (r *RPCServer) Run() { r.Unlock() r.mux.HandleFunc("/json_rpc", translate_http_to_jsonrpc_and_vice_versa) + r.mux.HandleFunc("/metrics", metrics.WritePrometheus) // write all the metrics + r.mux.HandleFunc("/ws", ws_handler) r.mux.HandleFunc("/", hello) - //r.mux.Handle("/json_rpc", mr) - - // handle nasty http requests - //r.mux.HandleFunc("/getheight", getheight) + r.mux.HandleFunc("/debug/pprof/", pprof.Index) + r.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + r.mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + r.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + r.mux.HandleFunc("/debug/pprof/trace", pprof.Trace) //if DEBUG_MODE { // r.mux.HandleFunc("/debug/pprof/", pprof.Index) @@ -182,21 +187,8 @@ func (r *RPCServer) Run() { r.mux.HandleFunc("/debug/pprof/trace", pprof.Trace) */ - /* - // Register pprof handlers individually if required - r.mux.HandleFunc("/cdebug/pprof/", pprof.Index) - r.mux.HandleFunc("/cdebug/pprof/cmdline", pprof.Cmdline) - r.mux.HandleFunc("/cdebug/pprof/profile", pprof.Profile) - r.mux.HandleFunc("/cdebug/pprof/symbol", pprof.Symbol) - r.mux.HandleFunc("/cdebug/pprof/trace", pprof.Trace) - */ - // register metrics handler - // r.mux.HandleFunc("/metrics", prometheus.InstrumentHandler("dero", promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{}))) - //} - - //r.mux.HandleFunc("/json_rpc/debug", mr.ServeDebug) go Notify_Block_Addition() // process all blocks go Notify_Height_Changes() // gives notification of changed height diff --git a/cmd/explorer/explorer.go b/cmd/explorer/explorer.go index 8f3aa71..6592433 100644 --- a/cmd/explorer/explorer.go +++ b/cmd/explorer/explorer.go @@ -259,8 +259,8 @@ type txinfo struct { OutAddress []string // contains output secret key OutOffset []uint64 // contains index offsets Type string // ringct or ruffct ( bulletproof) - ValidBlock string // the tx is valid in which block - InvalidBlock []string // the tx is invalid in which block + StateBlock string // the tx is valid with reference to this block + MinedBlock []string // the tx is mined in which block Skipped bool // this is only valid, when a block is being listed Ring_size int Ring [][][]byte // contains entire ring in raw form @@ -399,9 +399,6 @@ func load_block_from_rpc(info *block_info, block_hash string, recursive bool) (e var tx txinfo err = load_tx_from_rpc(&tx, bl.Tx_hashes[i].String()) //TODO handle error fmt.Printf("loading tx %s err %s\n", bl.Tx_hashes[i].String(), err) - if tx.ValidBlock != bresult.Block_Header.Hash { // track skipped status - tx.Skipped = true - } info.Txs = append(info.Txs, tx) fees += tx.Feeuint64 size += tx.Sizeuint64 @@ -552,8 +549,8 @@ func load_tx_from_rpc(info *txinfo, txhash string) (err error) { info.Amount = fmt.Sprintf("%.05f", float64(uint64(tx_result.Txs[0].Reward))/100000) } - info.ValidBlock = tx_result.Txs[0].ValidBlock - info.InvalidBlock = tx_result.Txs[0].InvalidBlock + info.StateBlock = tx_result.Txs[0].StateBlock + info.MinedBlock = tx_result.Txs[0].MinedBlock info.Ring = tx_result.Txs[0].Ring diff --git a/cmd/explorer/templates.go b/cmd/explorer/templates.go index 276e05c..31fe679 100644 --- a/cmd/explorer/templates.go +++ b/cmd/explorer/templates.go @@ -308,9 +308,9 @@ var tx_template string = `{{define "tx"}}

Burns: {{.info.Burn_Value }} DERO

{{end}} -
Block: {{.info.ValidBlock}} (VALID)
+
Block: {{.info.StateBlock}} (Reference)
- {{range $i, $e := .info.InvalidBlock}} + {{range $i, $e := .info.MinedBlock}}
Block: {{$e}}
{{end}} diff --git a/config/config.go b/config/config.go index 4ab82cf..70f12f3 100644 --- a/config/config.go +++ b/config/config.go @@ -58,14 +58,16 @@ const MAINNET_MINIMUM_DIFFICULTY = uint64(800 * BLOCK_TIME) // 5 KH/s const TESTNET_BOOTSTRAP_DIFFICULTY = uint64(800 * BLOCK_TIME) // testnet bootstrap at 800 H/s const TESTNET_MINIMUM_DIFFICULTY = uint64(800 * BLOCK_TIME) // 800 H + +//this controls the batch size which controls till how many blocks incoming funds cannot be spend +const BLOCK_BATCH_SIZE = crypto.BLOCK_BATCH_SIZE + // this single parameter controls lots of various parameters // within the consensus, it should never go below 7 // if changed responsibly, we can have one second or lower blocks (ignoring chain bloat/size issues) // gives immense scalability, const STABLE_LIMIT = int64(8) -// reward percent that is shared between miners/dev -const DEVSHARE = uint64(600) // it's out of 10000, 600*100/10000 = 6%, 3% dev, 3% foundation // we can have number of chains running for testing reasons type CHAIN_CONFIG struct { @@ -104,7 +106,7 @@ var Mainnet = CHAIN_CONFIG{Name: "mainnet", } var Testnet = CHAIN_CONFIG{Name: "testnet", // testnet will always have last 3 bytes 0 - Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x26, 0x00, 0x00, 0x00}), + Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x26, 0x00, 0x02, 0x00}), P2P_Default_Port: 40401, RPC_Default_Port: 40402, Wallet_RPC_Default_Port: 40403, diff --git a/config/version.go b/config/version.go index 86af59b..37e4f8b 100644 --- a/config/version.go +++ b/config/version.go @@ -20,4 +20,4 @@ import "github.com/blang/semver" // right now it has to be manually changed // do we need to include git commitsha?? -var Version = semver.MustParse("3.2.14-1.DEROHE.STARGATE+28022021") +var Version = semver.MustParse("3.2.15-1.DEROHE.STARGATE+08082021") diff --git a/cryptography/crypto/proof_generate.go b/cryptography/crypto/proof_generate.go index e37d572..5f049d1 100644 --- a/cryptography/crypto/proof_generate.go +++ b/cryptography/crypto/proof_generate.go @@ -22,7 +22,7 @@ import "math/big" import "bytes" //import "crypto/rand" -//import "encoding/hex" +import "encoding/binary" import "github.com/deroproject/derohe/cryptography/bn256" @@ -30,6 +30,10 @@ import "github.com/deroproject/derohe/cryptography/bn256" //import "github.com/kubernetes/klog" + +// see comment in config package +const BLOCK_BATCH_SIZE = 10 + type Proof struct { BA *bn256.G1 BS *bn256.G1 @@ -39,6 +43,7 @@ type Proof struct { CLnG, CRnG, C_0G, DG, y_0G, gG, C_XG, y_XG []*bn256.G1 u *bn256.G1 + u1 *bn256.G1 f *FieldVector @@ -67,9 +72,12 @@ type IPWitness struct { } // this is based on roothash and user's secret key and thus is the basis of protection from a number of double spending attacks -func (p *Proof) Nonce() Hash { +func (p *Proof) Nonce1() Hash { return Keccak256(p.u.EncodeCompressed()) } +func (p *Proof) Nonce2() Hash { + return Keccak256(p.u1.EncodeCompressed()) +} func (p *Proof) Serialize(w *bytes.Buffer) { if p == nil { @@ -95,6 +103,7 @@ func (p *Proof) Serialize(w *bytes.Buffer) { } w.Write(p.u.EncodeCompressed()) + w.Write(p.u1.EncodeCompressed()) if len(p.CLnG) != len(p.f.vector) { /// panic(fmt.Sprintf("different size %d %d", len(p.CLnG), len(p.f.vector))) @@ -278,6 +287,16 @@ func (proof *Proof) Deserialize(r *bytes.Reader, length int) error { return err } + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.u1 = &p + } else { + return err + } + proof.f = &FieldVector{} //fmt.Printf("flen %d\n", flen ) @@ -438,12 +457,27 @@ func reverse(s string) string { return string(rns) } + +func HeightToPoint(height uint64) *bn256.G1 { + var input []byte + var h [8]byte + input = append(input, []byte(PROTOCOL_CONSTANT)...) + + binary.BigEndian.PutUint64(h[:], height) + input = append(input,h[:]...) + + + point := HashToPoint(HashtoNumber(input)) + return point +} + var params = NewGeneratorParams(128) // these can be pregenerated similarly as in DERO project -func GenerateProof(s *Statement, witness *Witness, u *bn256.G1, txid Hash, burn_value uint64) *Proof { +func GenerateProof(s *Statement, witness *Witness, u,u1 *bn256.G1, height uint64, txid Hash, burn_value uint64) *Proof { var proof Proof proof.u = u + proof.u1 = u1 statementhash := reducedhash(txid[:]) @@ -1106,17 +1140,9 @@ func GenerateProof(s *Statement, witness *Witness, u *bn256.G1, txid Hash, burn_ A_t := new(bn256.G1).ScalarMult(params.G, new(big.Int).Mod(new(big.Int).Neg(k_b), bn256.Order)) A_t = new(bn256.G1).Add(A_t, new(bn256.G1).ScalarMult(params.H, k_tau)) - A_u := new(bn256.G1) + A_u := new(bn256.G1).ScalarMult(HeightToPoint(height), k_sk) + A_u1 := new(bn256.G1).ScalarMult(HeightToPoint(height + BLOCK_BATCH_SIZE), k_sk) - { - var input []byte - input = append(input, []byte(PROTOCOL_CONSTANT)...) - input = append(input, s.Roothash[:]...) - - point := HashToPoint(HashtoNumber(input)) - - A_u = new(bn256.G1).ScalarMult(point, k_sk) - } // klog.V(2).Infof("A_y %s\n", A_y.String()) // klog.V(2).Infof("A_D %s\n", A_D.String()) @@ -1134,6 +1160,7 @@ func GenerateProof(s *Statement, witness *Witness, u *bn256.G1, txid Hash, burn_ input = append(input, A_X.Marshal()...) input = append(input, A_t.Marshal()...) input = append(input, A_u.Marshal()...) + input = append(input, A_u1.Marshal()...) proof.c = reducedhash(input) } diff --git a/cryptography/crypto/proof_verify.go b/cryptography/crypto/proof_verify.go index e4fd820..0c24d80 100644 --- a/cryptography/crypto/proof_verify.go +++ b/cryptography/crypto/proof_verify.go @@ -64,7 +64,7 @@ type ProtocolSupport struct { // sigma protocol type SigmaSupport struct { c *big.Int - A_y, A_D, A_b, A_X, A_t, A_u *bn256.G1 + A_y, A_D, A_b, A_X, A_t, A_u, A_u1 *bn256.G1 } // support structures are those which @@ -96,7 +96,7 @@ var gparams = NewGeneratorParams(128) // these can be pregenerated similarly as // verify proof // first generate supporting structures -func (proof *Proof) Verify(s *Statement, txid Hash, extra_value uint64) bool { +func (proof *Proof) Verify(s *Statement, txid Hash, height uint64, extra_value uint64) bool { var anonsupport AnonSupport var protsupport ProtocolSupport @@ -377,14 +377,14 @@ func (proof *Proof) Verify(s *Statement, txid Hash, extra_value uint64) bool { // klog.V(2).Infof("protsupport.tEval %s\n", protsupport.tEval.String()) { - var input []byte - input = append(input, []byte(PROTOCOL_CONSTANT)...) - input = append(input, s.Roothash[:]...) - - point := HashToPoint(HashtoNumber(input)) - + point := HeightToPoint(height) sigmasupport.A_u = new(bn256.G1).ScalarMult(point, proof.s_sk) sigmasupport.A_u.Add(new(bn256.G1).Set(sigmasupport.A_u), new(bn256.G1).ScalarMult(proof.u, proof_c_neg)) + + point = HeightToPoint(height+BLOCK_BATCH_SIZE) + sigmasupport.A_u1 = new(bn256.G1).ScalarMult(point, proof.s_sk) + sigmasupport.A_u1.Add(new(bn256.G1).Set(sigmasupport.A_u1), new(bn256.G1).ScalarMult(proof.u1, proof_c_neg)) + } // klog.V(2).Infof("A_y %s\n", sigmasupport.A_y.String()) @@ -403,6 +403,7 @@ func (proof *Proof) Verify(s *Statement, txid Hash, extra_value uint64) bool { input = append(input, sigmasupport.A_X.Marshal()...) input = append(input, sigmasupport.A_t.Marshal()...) input = append(input, sigmasupport.A_u.Marshal()...) + input = append(input, sigmasupport.A_u1.Marshal()...) // fmt.Printf("C calculation expected %s actual %s\n",proof.c.Text(16), reducedhash(input).Text(16) ) if reducedhash(input).Text(16) != proof.c.Text(16) { // we must fail here diff --git a/metrics/metrics.go b/metrics/metrics.go index baa269f..cc83412 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -18,14 +18,28 @@ package metrics -import "github.com/prometheus/client_golang/prometheus" +import "net/http" +import "github.com/VictoriaMetrics/metrics" -var Registry = prometheus.NewRegistry() +// these are exported by the daemon for various analysis -var DefaultRegisterer prometheus.Registerer = Registry -var DefaultGatherer prometheus.Gatherer = Registry -// register some default go collectors -func init() { - Registry.MustRegister(prometheus.NewGoCollector()) -} +var Blockchain_tx_counter = metrics.NewCounter(`blockchain_tx_counter`) +var Mempool_tx_counter = metrics.NewCounter(`mempool_tx_counter`) +var Mempool_tx_count = metrics.NewCounter(`mempool_tx_count`) // its actually a gauge +var Block_size = metrics.NewHistogram(`block_size`) +var Block_tx = metrics.NewHistogram(`block_tx`) +var Block_processing_time = metrics.NewHistogram(`block_processing_time`) +var Transaction_size = metrics.NewHistogram(`transaction_size`) +var Transaction_ring_size = metrics.NewHistogram(`transaction_ring_size`) +var Transaction_outputs = metrics.NewHistogram("transaction_outputs") // a single tx will give to so many people + +// we may need to expose various p2p stats, but currently they can be skipped + +var Block_propagation = metrics.NewHistogram(`block_propagation`) +var Transaction_propagation = metrics.NewHistogram(`transaction_propagation`) + + +func WritePrometheus(w http.ResponseWriter, req *http.Request){ + metrics.WritePrometheus(w, true) +} \ No newline at end of file diff --git a/p2p/chain_bootstrap.go b/p2p/chain_bootstrap.go index 760865e..7685d72 100644 --- a/p2p/chain_bootstrap.go +++ b/p2p/chain_bootstrap.go @@ -56,7 +56,7 @@ func (connection *Connection) bootstrap_chain() { // we will request top 60 blocks ctopo := connection.TopoHeight var topos []int64 - for i := ctopo - 20; i < ctopo; i++ { + for i := ctopo - 200; i < ctopo; i++ { topos = append(topos, i) } diff --git a/p2p/connection_pool.go b/p2p/connection_pool.go index 9510401..bf10996 100644 --- a/p2p/connection_pool.go +++ b/p2p/connection_pool.go @@ -39,7 +39,6 @@ import "github.com/romana/rlog" import "github.com/dustin/go-humanize" import log "github.com/sirupsen/logrus" import "github.com/paulbellamy/ratecounter" -import "github.com/prometheus/client_golang/prometheus" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/cryptography/crypto" @@ -115,19 +114,6 @@ func (c *Connection) exit() { } -// 300 such buckets can be used to track block propagation accuratly upto a minute -var block_propagation = prometheus.NewHistogram(prometheus.HistogramOpts{ - Name: "block_propagation_ms", - Help: "Block Propagation time milliseconds as detected by daemon", - Buckets: prometheus.LinearBuckets(0, 1000, 20), // start 0 ms, each 1000 ms, 20 such buckets. -}) - -// 300 such buckets can be used to track transaction propagation accurately upto a minute -var transaction_propagation = prometheus.NewHistogram(prometheus.HistogramOpts{ - Name: "tx_propagation_ms", - Help: "TX Propagation time milliseconds as detected by daemon", - Buckets: prometheus.LinearBuckets(0, 1000, 20), // start 0 ms, each 1000 ms, 20 such buckets. -}) var block_propagation_map sync.Map var tx_propagation_map sync.Map diff --git a/p2p/controller.go b/p2p/controller.go index da0f54b..add4f3c 100644 --- a/p2p/controller.go +++ b/p2p/controller.go @@ -43,7 +43,7 @@ import log "github.com/sirupsen/logrus" import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/blockchain" -import "github.com/deroproject/derohe/metrics" + var chain *blockchain.Blockchain // external reference to chain @@ -105,9 +105,6 @@ func P2P_Init(params map[string]interface{}) error { } } - // register the metrics with the metrics registry - metrics.Registry.MustRegister(block_propagation) - metrics.Registry.MustRegister(transaction_propagation) go P2P_Server_v2() // start accepting connections go P2P_engine() // start outgoing engine diff --git a/p2p/rpc_notifications.go b/p2p/rpc_notifications.go index 6baff35..8af49e3 100644 --- a/p2p/rpc_notifications.go +++ b/p2p/rpc_notifications.go @@ -26,6 +26,7 @@ import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/errormsg" import "github.com/deroproject/derohe/transaction" +import "github.com/deroproject/derohe/metrics" // notifies inventory func (c *Connection) NotifyINV(request ObjectList, response *Dummy) (err error) { @@ -104,9 +105,7 @@ func (c *Connection) NotifyTx(request Objects, response *Dummy) error { // track transaction propagation if first_time, ok := tx_propagation_map.Load(tx.GetHash()); ok { - // block already has a reference, take the time and observe the value - diff := time.Now().Sub(first_time.(time.Time)).Round(time.Millisecond) - transaction_propagation.Observe(float64(diff / 1000000)) + metrics.Transaction_propagation.UpdateDuration(first_time.(time.Time)) } else { tx_propagation_map.Store(tx.GetHash(), time.Now()) // if this is the first time, store the tx time } @@ -153,10 +152,8 @@ func (c *Connection) NotifyBlock(request Objects, response *Dummy) error { rlog.Infof("Incoming block Notification hash %s %s ", blid, globals.CTXString(c.logger)) // track block propagation - if first_time, ok := block_propagation_map.Load(blid); ok { - // block already has a reference, take the time and observe the value - diff := time.Now().Sub(first_time.(time.Time)).Round(time.Millisecond) - block_propagation.Observe(float64(diff / 1000000)) + if first_time, ok := block_propagation_map.Load(blid); ok { // block already has a reference, take the time and observe the value + metrics.Block_propagation.UpdateDuration(first_time.(time.Time)) } else { block_propagation_map.Store(blid, time.Now()) // if this is the first time, store the block } diff --git a/rpc/daemon_rpc.go b/rpc/daemon_rpc.go index 4a01b9d..899b7d3 100644 --- a/rpc/daemon_rpc.go +++ b/rpc/daemon_rpc.go @@ -123,6 +123,9 @@ type ( } ) +const RECENT_BLOCK = int64(-1) // will give most recent data +const RECENT_BATCH_BLOCK = int64(-2) // will give data from recent block batch for tx building + //get encrypted balance call type ( GetEncryptedBalance_Params struct { @@ -193,8 +196,8 @@ type ( In_pool bool `json:"in_pool"` Output_Indices []uint64 `json:"output_indices"` Tx_hash string `json:"tx_hash"` - ValidBlock string `json:"valid_block"` // TX is valid in this block - InvalidBlock []string `json:"invalid_block"` // TX is invalid in this block, 0 or more + StateBlock string `json:"state_block"` // TX is built in reference to this block + MinedBlock []string `json:"mined_block"` // TX is mined in this block, 1 or more Ring [][][]byte `json:"ring"` // ring members completed, since tx contains compressed Balance uint64 `json:"balance"` // if tx is SC, give SC balance at start Code string `json:"code"` // smart contract code at start diff --git a/transaction/transaction.go b/transaction/transaction.go index a685771..1bb427d 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -170,7 +170,7 @@ func (a *AssetPayload) UnmarshalProofs(r *bytes.Reader) (err error) { type Transaction struct { Transaction_Prefix // same as Transaction_Prefix - Payloads []AssetPayload // each transaction can have a number os payloads + Payloads []AssetPayload // each transaction can have a number of payloads } // this excludes the proof part, so it can pruned diff --git a/vendor/github.com/VictoriaMetrics/metrics/.github/workflows/main.yml b/vendor/github.com/VictoriaMetrics/metrics/.github/workflows/main.yml new file mode 100644 index 0000000..d8d8d12 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/.github/workflows/main.yml @@ -0,0 +1,33 @@ +name: main +on: + - push + - pull_request +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Setup Go + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go + - name: Code checkout + uses: actions/checkout@v1 + - name: Test + run: | + go test -v ./... -coverprofile=coverage.txt -covermode=atomic + go test -v ./... -race + - name: Build + run: | + GOOS=linux go build + GOOS=darwin go build + GOOS=freebsd go build + GOOS=windows go build + GOARCH=386 go build + - name: Publish coverage + uses: codecov/codecov-action@v1.0.6 + with: + token: ${{secrets.CODECOV_TOKEN}} + file: ./coverage.txt + diff --git a/vendor/github.com/VictoriaMetrics/metrics/LICENSE b/vendor/github.com/VictoriaMetrics/metrics/LICENSE new file mode 100644 index 0000000..539b7a4 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2019 VictoriaMetrics + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/VictoriaMetrics/metrics/README.md b/vendor/github.com/VictoriaMetrics/metrics/README.md new file mode 100644 index 0000000..5eef96a --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/README.md @@ -0,0 +1,104 @@ +[![Build Status](https://github.com/VictoriaMetrics/metrics/workflows/main/badge.svg)](https://github.com/VictoriaMetrics/metrics/actions) +[![GoDoc](https://godoc.org/github.com/VictoriaMetrics/metrics?status.svg)](http://godoc.org/github.com/VictoriaMetrics/metrics) +[![Go Report](https://goreportcard.com/badge/github.com/VictoriaMetrics/metrics)](https://goreportcard.com/report/github.com/VictoriaMetrics/metrics) +[![codecov](https://codecov.io/gh/VictoriaMetrics/metrics/branch/master/graph/badge.svg)](https://codecov.io/gh/VictoriaMetrics/metrics) + + +# metrics - lightweight package for exporting metrics in Prometheus format + + +### Features + +* Lightweight. Has minimal number of third-party dependencies and all these deps are small. + See [this article](https://medium.com/@valyala/stripping-dependency-bloat-in-victoriametrics-docker-image-983fb5912b0d) for details. +* Easy to use. See the [API docs](http://godoc.org/github.com/VictoriaMetrics/metrics). +* Fast. +* Allows exporting distinct metric sets via distinct endpoints. See [Set](http://godoc.org/github.com/VictoriaMetrics/metrics#Set). +* Supports [easy-to-use histograms](http://godoc.org/github.com/VictoriaMetrics/metrics#Histogram), which just work without any tuning. + Read more about VictoriaMetrics histograms at [this article](https://medium.com/@valyala/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350). + + +### Limitations + +* It doesn't implement advanced functionality from [github.com/prometheus/client_golang](https://godoc.org/github.com/prometheus/client_golang). + + +### Usage + +```go +import "github.com/VictoriaMetrics/metrics" + +// Register various time series. +// Time series name may contain labels in Prometheus format - see below. +var ( + // Register counter without labels. + requestsTotal = metrics.NewCounter("requests_total") + + // Register summary with a single label. + requestDuration = metrics.NewSummary(`requests_duration_seconds{path="/foobar/baz"}`) + + // Register gauge with two labels. + queueSize = metrics.NewGauge(`queue_size{queue="foobar",topic="baz"}`, func() float64 { + return float64(foobarQueue.Len()) + }) + + // Register histogram with a single label. + responseSize = metrics.NewHistogram(`response_size{path="/foo/bar"}`) +) + +// ... +func requestHandler() { + // Increment requestTotal counter. + requestsTotal.Inc() + + startTime := time.Now() + processRequest() + // Update requestDuration summary. + requestDuration.UpdateDuration(startTime) + + // Update responseSize histogram. + responseSize.Update(responseSize) +} + +// Expose the registered metrics at `/metrics` path. +http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { + metrics.WritePrometheus(w, true) +}) +``` + +See [docs](http://godoc.org/github.com/VictoriaMetrics/metrics) for more info. + + +### Users + +* `Metrics` has been extracted from [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) sources. + See [this article](https://medium.com/devopslinks/victoriametrics-creating-the-best-remote-storage-for-prometheus-5d92d66787ac) + for more info about `VictoriaMetrics`. + + +### FAQ + +#### Why the `metrics` API isn't compatible with `github.com/prometheus/client_golang`? + +Because the `github.com/prometheus/client_golang` is too complex and is hard to use. + + +#### Why the `metrics.WritePrometheus` doesn't expose documentation for each metric? + +Because this documentation is ignored by Prometheus. The documentation is for users. +Just give meaningful names to the exported metrics or add comments in the source code +or in other suitable place explaining each metric exposed from your application. + + +#### How to implement [CounterVec](https://godoc.org/github.com/prometheus/client_golang/prometheus#CounterVec) in `metrics`? + +Just use [GetOrCreateCounter](http://godoc.org/github.com/VictoriaMetrics/metrics#GetOrCreateCounter) +instead of `CounterVec.With`. See [this example](https://pkg.go.dev/github.com/VictoriaMetrics/metrics#example-Counter-Vec) for details. + + +#### Why [Histogram](http://godoc.org/github.com/VictoriaMetrics/metrics#Histogram) buckets contain `vmrange` labels instead of `le` labels like in Prometheus histograms? + +Buckets with `vmrange` labels occupy less disk space compared to Promethes-style buckets with `le` labels, +because `vmrange` buckets don't include counters for the previous ranges. [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) provides `prometheus_buckets` +function, which converts `vmrange` buckets to Prometheus-style buckets with `le` labels. This is useful for building heatmaps in Grafana. +Additionally, its' `histogram_quantile` function transparently handles histogram buckets with `vmrange` labels. diff --git a/vendor/github.com/VictoriaMetrics/metrics/counter.go b/vendor/github.com/VictoriaMetrics/metrics/counter.go new file mode 100644 index 0000000..a7d9549 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/counter.go @@ -0,0 +1,77 @@ +package metrics + +import ( + "fmt" + "io" + "sync/atomic" +) + +// NewCounter registers and returns new counter with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned counter is safe to use from concurrent goroutines. +func NewCounter(name string) *Counter { + return defaultSet.NewCounter(name) +} + +// Counter is a counter. +// +// It may be used as a gauge if Dec and Set are called. +type Counter struct { + n uint64 +} + +// Inc increments c. +func (c *Counter) Inc() { + atomic.AddUint64(&c.n, 1) +} + +// Dec decrements c. +func (c *Counter) Dec() { + atomic.AddUint64(&c.n, ^uint64(0)) +} + +// Add adds n to c. +func (c *Counter) Add(n int) { + atomic.AddUint64(&c.n, uint64(n)) +} + +// Get returns the current value for c. +func (c *Counter) Get() uint64 { + return atomic.LoadUint64(&c.n) +} + +// Set sets c value to n. +func (c *Counter) Set(n uint64) { + atomic.StoreUint64(&c.n, n) +} + +// marshalTo marshals c with the given prefix to w. +func (c *Counter) marshalTo(prefix string, w io.Writer) { + v := c.Get() + fmt.Fprintf(w, "%s %d\n", prefix, v) +} + +// GetOrCreateCounter returns registered counter with the given name +// or creates new counter if the registry doesn't contain counter with +// the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned counter is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewCounter instead of GetOrCreateCounter. +func GetOrCreateCounter(name string) *Counter { + return defaultSet.GetOrCreateCounter(name) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/counter_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/counter_example_test.go new file mode 100644 index 0000000..c485df6 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/counter_example_test.go @@ -0,0 +1,41 @@ +package metrics_test + +import ( + "fmt" + "github.com/VictoriaMetrics/metrics" +) + +func ExampleCounter() { + // Define a counter in global scope. + var c = metrics.NewCounter(`metric_total{label1="value1", label2="value2"}`) + + // Increment the counter when needed. + for i := 0; i < 10; i++ { + c.Inc() + } + n := c.Get() + fmt.Println(n) + + // Output: + // 10 +} + +func ExampleCounter_vec() { + for i := 0; i < 3; i++ { + // Dynamically construct metric name and pass it to GetOrCreateCounter. + name := fmt.Sprintf(`metric_total{label1=%q, label2="%d"}`, "value1", i) + metrics.GetOrCreateCounter(name).Add(i + 1) + } + + // Read counter values. + for i := 0; i < 3; i++ { + name := fmt.Sprintf(`metric_total{label1=%q, label2="%d"}`, "value1", i) + n := metrics.GetOrCreateCounter(name).Get() + fmt.Println(n) + } + + // Output: + // 1 + // 2 + // 3 +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/counter_test.go b/vendor/github.com/VictoriaMetrics/metrics/counter_test.go new file mode 100644 index 0000000..27c9264 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/counter_test.go @@ -0,0 +1,76 @@ +package metrics + +import ( + "fmt" + "testing" +) + +func TestCounterSerial(t *testing.T) { + name := "CounterSerial" + c := NewCounter(name) + c.Inc() + if n := c.Get(); n != 1 { + t.Fatalf("unexpected counter value; got %d; want 1", n) + } + c.Set(123) + if n := c.Get(); n != 123 { + t.Fatalf("unexpected counter value; got %d; want 123", n) + } + c.Dec() + if n := c.Get(); n != 122 { + t.Fatalf("unexpected counter value; got %d; want 122", n) + } + c.Add(3) + if n := c.Get(); n != 125 { + t.Fatalf("unexpected counter value; got %d; want 125", n) + } + + // Verify MarshalTo + testMarshalTo(t, c, "foobar", "foobar 125\n") +} + +func TestCounterConcurrent(t *testing.T) { + name := "CounterConcurrent" + c := NewCounter(name) + err := testConcurrent(func() error { + nPrev := c.Get() + for i := 0; i < 10; i++ { + c.Inc() + if n := c.Get(); n <= nPrev { + return fmt.Errorf("counter value must be greater than %d; got %d", nPrev, n) + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} + +func TestGetOrCreateCounterSerial(t *testing.T) { + name := "GetOrCreateCounterSerial" + if err := testGetOrCreateCounter(name); err != nil { + t.Fatal(err) + } +} + +func TestGetOrCreateCounterConcurrent(t *testing.T) { + name := "GetOrCreateCounterConcurrent" + err := testConcurrent(func() error { + return testGetOrCreateCounter(name) + }) + if err != nil { + t.Fatal(err) + } +} + +func testGetOrCreateCounter(name string) error { + c1 := GetOrCreateCounter(name) + for i := 0; i < 10; i++ { + c2 := GetOrCreateCounter(name) + if c1 != c2 { + return fmt.Errorf("unexpected counter returned; got %p; want %p", c2, c1) + } + } + return nil +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/floatcounter.go b/vendor/github.com/VictoriaMetrics/metrics/floatcounter.go new file mode 100644 index 0000000..d01dd85 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/floatcounter.go @@ -0,0 +1,82 @@ +package metrics + +import ( + "fmt" + "io" + "sync" +) + +// NewFloatCounter registers and returns new counter of float64 type with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned counter is safe to use from concurrent goroutines. +func NewFloatCounter(name string) *FloatCounter { + return defaultSet.NewFloatCounter(name) +} + +// FloatCounter is a float64 counter guarded by RWmutex. +// +// It may be used as a gauge if Add and Sub are called. +type FloatCounter struct { + mu sync.Mutex + n float64 +} + +// Add adds n to fc. +func (fc *FloatCounter) Add(n float64) { + fc.mu.Lock() + fc.n += n + fc.mu.Unlock() +} + +// Sub substracts n from fc. +func (fc *FloatCounter) Sub(n float64) { + fc.mu.Lock() + fc.n -= n + fc.mu.Unlock() +} + +// Get returns the current value for fc. +func (fc *FloatCounter) Get() float64 { + fc.mu.Lock() + n := fc.n + fc.mu.Unlock() + return n +} + +// Set sets fc value to n. +func (fc *FloatCounter) Set(n float64) { + fc.mu.Lock() + fc.n = n + fc.mu.Unlock() +} + +// marshalTo marshals fc with the given prefix to w. +func (fc *FloatCounter) marshalTo(prefix string, w io.Writer) { + v := fc.Get() + fmt.Fprintf(w, "%s %g\n", prefix, v) +} + +// GetOrCreateFloatCounter returns registered FloatCounter with the given name +// or creates new FloatCounter if the registry doesn't contain FloatCounter with +// the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned FloatCounter is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewFloatCounter instead of GetOrCreateFloatCounter. +func GetOrCreateFloatCounter(name string) *FloatCounter { + return defaultSet.GetOrCreateFloatCounter(name) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/floatcounter_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/floatcounter_example_test.go new file mode 100644 index 0000000..3f32770 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/floatcounter_example_test.go @@ -0,0 +1,41 @@ +package metrics_test + +import ( + "fmt" + "github.com/VictoriaMetrics/metrics" +) + +func ExampleFloatCounter() { + // Define a float64 counter in global scope. + var fc = metrics.NewFloatCounter(`float_metric_total{label1="value1", label2="value2"}`) + + // Add to the counter when needed. + for i := 0; i < 10; i++ { + fc.Add(1.01) + } + n := fc.Get() + fmt.Println(n) + + // Output: + // 10.1 +} + +func ExampleFloatCounter_vec() { + for i := 0; i < 3; i++ { + // Dynamically construct metric name and pass it to GetOrCreateFloatCounter. + name := fmt.Sprintf(`float_metric_total{label1=%q, label2="%d"}`, "value1", i) + metrics.GetOrCreateFloatCounter(name).Add(float64(i) + 1.01) + } + + // Read counter values. + for i := 0; i < 3; i++ { + name := fmt.Sprintf(`float_metric_total{label1=%q, label2="%d"}`, "value1", i) + n := metrics.GetOrCreateFloatCounter(name).Get() + fmt.Println(n) + } + + // Output: + // 1.01 + // 2.01 + // 3.01 +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/floatcounter_test.go b/vendor/github.com/VictoriaMetrics/metrics/floatcounter_test.go new file mode 100644 index 0000000..44931c3 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/floatcounter_test.go @@ -0,0 +1,76 @@ +package metrics + +import ( + "fmt" + "testing" +) + +func TestFloatCounterSerial(t *testing.T) { + name := "FloatCounterSerial" + c := NewFloatCounter(name) + c.Add(0.1) + if n := c.Get(); n != 0.1 { + t.Fatalf("unexpected counter value; got %f; want 0.1", n) + } + c.Set(123.00001) + if n := c.Get(); n != 123.00001 { + t.Fatalf("unexpected counter value; got %f; want 123.00001", n) + } + c.Sub(0.00001) + if n := c.Get(); n != 123 { + t.Fatalf("unexpected counter value; got %f; want 123", n) + } + c.Add(2.002) + if n := c.Get(); n != 125.002 { + t.Fatalf("unexpected counter value; got %f; want 125.002", n) + } + + // Verify MarshalTo + testMarshalTo(t, c, "foobar", "foobar 125.002\n") +} + +func TestFloatCounterConcurrent(t *testing.T) { + name := "FloatCounterConcurrent" + c := NewFloatCounter(name) + err := testConcurrent(func() error { + nPrev := c.Get() + for i := 0; i < 10; i++ { + c.Add(1.001) + if n := c.Get(); n <= nPrev { + return fmt.Errorf("counter value must be greater than %f; got %f", nPrev, n) + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} + +func TestGetOrCreateFloatCounterSerial(t *testing.T) { + name := "GetOrCreateFloatCounterSerial" + if err := testGetOrCreateCounter(name); err != nil { + t.Fatal(err) + } +} + +func TestGetOrCreateFloatCounterConcurrent(t *testing.T) { + name := "GetOrCreateFloatCounterConcurrent" + err := testConcurrent(func() error { + return testGetOrCreateFloatCounter(name) + }) + if err != nil { + t.Fatal(err) + } +} + +func testGetOrCreateFloatCounter(name string) error { + c1 := GetOrCreateFloatCounter(name) + for i := 0; i < 10; i++ { + c2 := GetOrCreateFloatCounter(name) + if c1 != c2 { + return fmt.Errorf("unexpected counter returned; got %p; want %p", c2, c1) + } + } + return nil +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/gauge.go b/vendor/github.com/VictoriaMetrics/metrics/gauge.go new file mode 100644 index 0000000..05bf147 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/gauge.go @@ -0,0 +1,67 @@ +package metrics + +import ( + "fmt" + "io" +) + +// NewGauge registers and returns gauge with the given name, which calls f +// to obtain gauge value. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// f must be safe for concurrent calls. +// +// The returned gauge is safe to use from concurrent goroutines. +// +// See also FloatCounter for working with floating-point values. +func NewGauge(name string, f func() float64) *Gauge { + return defaultSet.NewGauge(name, f) +} + +// Gauge is a float64 gauge. +// +// See also Counter, which could be used as a gauge with Set and Dec calls. +type Gauge struct { + f func() float64 +} + +// Get returns the current value for g. +func (g *Gauge) Get() float64 { + return g.f() +} + +func (g *Gauge) marshalTo(prefix string, w io.Writer) { + v := g.f() + if float64(int64(v)) == v { + // Marshal integer values without scientific notation + fmt.Fprintf(w, "%s %d\n", prefix, int64(v)) + } else { + fmt.Fprintf(w, "%s %g\n", prefix, v) + } +} + +// GetOrCreateGauge returns registered gauge with the given name +// or creates new gauge if the registry doesn't contain gauge with +// the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned gauge is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewGauge instead of GetOrCreateGauge. +// +// See also FloatCounter for working with floating-point values. +func GetOrCreateGauge(name string, f func() float64) *Gauge { + return defaultSet.GetOrCreateGauge(name, f) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/gauge_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/gauge_example_test.go new file mode 100644 index 0000000..0f0e440 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/gauge_example_test.go @@ -0,0 +1,41 @@ +package metrics_test + +import ( + "fmt" + "runtime" + + "github.com/VictoriaMetrics/metrics" +) + +func ExampleGauge() { + // Define a gauge exporting the number of goroutines. + var g = metrics.NewGauge(`goroutines_count`, func() float64 { + return float64(runtime.NumGoroutine()) + }) + + // Obtain gauge value. + fmt.Println(g.Get()) +} + +func ExampleGauge_vec() { + for i := 0; i < 3; i++ { + // Dynamically construct metric name and pass it to GetOrCreateGauge. + name := fmt.Sprintf(`metric{label1=%q, label2="%d"}`, "value1", i) + iLocal := i + metrics.GetOrCreateGauge(name, func() float64 { + return float64(iLocal + 1) + }) + } + + // Read counter values. + for i := 0; i < 3; i++ { + name := fmt.Sprintf(`metric{label1=%q, label2="%d"}`, "value1", i) + n := metrics.GetOrCreateGauge(name, func() float64 { return 0 }).Get() + fmt.Println(n) + } + + // Output: + // 1 + // 2 + // 3 +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/gauge_test.go b/vendor/github.com/VictoriaMetrics/metrics/gauge_test.go new file mode 100644 index 0000000..3f6e5cd --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/gauge_test.go @@ -0,0 +1,64 @@ +package metrics + +import ( + "fmt" + "sync" + "testing" +) + +func TestGaugeError(t *testing.T) { + expectPanic(t, "NewGauge_nil_callback", func() { + NewGauge("NewGauge_nil_callback", nil) + }) + expectPanic(t, "GetOrCreateGauge_nil_callback", func() { + GetOrCreateGauge("GetOrCreateGauge_nil_callback", nil) + }) +} + +func TestGaugeSerial(t *testing.T) { + name := "GaugeSerial" + n := 1.23 + var nLock sync.Mutex + g := NewGauge(name, func() float64 { + nLock.Lock() + defer nLock.Unlock() + n++ + return n + }) + for i := 0; i < 10; i++ { + if nn := g.Get(); nn != n { + t.Fatalf("unexpected gauge value; got %v; want %v", nn, n) + } + } + + // Verify marshalTo + testMarshalTo(t, g, "foobar", "foobar 12.23\n") + + // Verify big numbers marshaling + n = 1234567899 + testMarshalTo(t, g, "prefix", "prefix 1234567900\n") +} + +func TestGaugeConcurrent(t *testing.T) { + name := "GaugeConcurrent" + var n int + var nLock sync.Mutex + g := NewGauge(name, func() float64 { + nLock.Lock() + defer nLock.Unlock() + n++ + return float64(n) + }) + err := testConcurrent(func() error { + nPrev := g.Get() + for i := 0; i < 10; i++ { + if n := g.Get(); n <= nPrev { + return fmt.Errorf("gauge value must be greater than %v; got %v", nPrev, n) + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/go.mod b/vendor/github.com/VictoriaMetrics/metrics/go.mod new file mode 100644 index 0000000..a66c19b --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/go.mod @@ -0,0 +1,5 @@ +module github.com/VictoriaMetrics/metrics + +require github.com/valyala/histogram v1.1.2 + +go 1.12 diff --git a/vendor/github.com/VictoriaMetrics/metrics/go.sum b/vendor/github.com/VictoriaMetrics/metrics/go.sum new file mode 100644 index 0000000..b121944 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/go.sum @@ -0,0 +1,4 @@ +github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= +github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= +github.com/valyala/histogram v1.1.2 h1:vOk5VrGjMBIoPR5k6wA8vBaC8toeJ8XO0yfRjFEc1h8= +github.com/valyala/histogram v1.1.2/go.mod h1:CZAr6gK9dbD7hYx2s8WSPh0p5x5wETjC+2b3PJVtEdg= diff --git a/vendor/github.com/VictoriaMetrics/metrics/go_metrics.go b/vendor/github.com/VictoriaMetrics/metrics/go_metrics.go new file mode 100644 index 0000000..f8b6067 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/go_metrics.go @@ -0,0 +1,64 @@ +package metrics + +import ( + "fmt" + "io" + "runtime" + + "github.com/valyala/histogram" +) + +func writeGoMetrics(w io.Writer) { + var ms runtime.MemStats + runtime.ReadMemStats(&ms) + fmt.Fprintf(w, "go_memstats_alloc_bytes %d\n", ms.Alloc) + fmt.Fprintf(w, "go_memstats_alloc_bytes_total %d\n", ms.TotalAlloc) + fmt.Fprintf(w, "go_memstats_buck_hash_sys_bytes %d\n", ms.BuckHashSys) + fmt.Fprintf(w, "go_memstats_frees_total %d\n", ms.Frees) + fmt.Fprintf(w, "go_memstats_gc_cpu_fraction %g\n", ms.GCCPUFraction) + fmt.Fprintf(w, "go_memstats_gc_sys_bytes %d\n", ms.GCSys) + fmt.Fprintf(w, "go_memstats_heap_alloc_bytes %d\n", ms.HeapAlloc) + fmt.Fprintf(w, "go_memstats_heap_idle_bytes %d\n", ms.HeapIdle) + fmt.Fprintf(w, "go_memstats_heap_inuse_bytes %d\n", ms.HeapInuse) + fmt.Fprintf(w, "go_memstats_heap_objects %d\n", ms.HeapObjects) + fmt.Fprintf(w, "go_memstats_heap_released_bytes %d\n", ms.HeapReleased) + fmt.Fprintf(w, "go_memstats_heap_sys_bytes %d\n", ms.HeapSys) + fmt.Fprintf(w, "go_memstats_last_gc_time_seconds %g\n", float64(ms.LastGC)/1e9) + fmt.Fprintf(w, "go_memstats_lookups_total %d\n", ms.Lookups) + fmt.Fprintf(w, "go_memstats_mallocs_total %d\n", ms.Mallocs) + fmt.Fprintf(w, "go_memstats_mcache_inuse_bytes %d\n", ms.MCacheInuse) + fmt.Fprintf(w, "go_memstats_mcache_sys_bytes %d\n", ms.MCacheSys) + fmt.Fprintf(w, "go_memstats_mspan_inuse_bytes %d\n", ms.MSpanInuse) + fmt.Fprintf(w, "go_memstats_mspan_sys_bytes %d\n", ms.MSpanSys) + fmt.Fprintf(w, "go_memstats_next_gc_bytes %d\n", ms.NextGC) + fmt.Fprintf(w, "go_memstats_other_sys_bytes %d\n", ms.OtherSys) + fmt.Fprintf(w, "go_memstats_stack_inuse_bytes %d\n", ms.StackInuse) + fmt.Fprintf(w, "go_memstats_stack_sys_bytes %d\n", ms.StackSys) + fmt.Fprintf(w, "go_memstats_sys_bytes %d\n", ms.Sys) + + fmt.Fprintf(w, "go_cgo_calls_count %d\n", runtime.NumCgoCall()) + fmt.Fprintf(w, "go_cpu_count %d\n", runtime.NumCPU()) + + gcPauses := histogram.NewFast() + for _, pauseNs := range ms.PauseNs[:] { + gcPauses.Update(float64(pauseNs) / 1e9) + } + phis := []float64{0, 0.25, 0.5, 0.75, 1} + quantiles := make([]float64, 0, len(phis)) + for i, q := range gcPauses.Quantiles(quantiles[:0], phis) { + fmt.Fprintf(w, `go_gc_duration_seconds{quantile="%g"} %g`+"\n", phis[i], q) + } + fmt.Fprintf(w, `go_gc_duration_seconds_sum %g`+"\n", float64(ms.PauseTotalNs)/1e9) + fmt.Fprintf(w, `go_gc_duration_seconds_count %d`+"\n", ms.NumGC) + fmt.Fprintf(w, `go_gc_forced_count %d`+"\n", ms.NumForcedGC) + + fmt.Fprintf(w, `go_gomaxprocs %d`+"\n", runtime.GOMAXPROCS(0)) + fmt.Fprintf(w, `go_goroutines %d`+"\n", runtime.NumGoroutine()) + numThread, _ := runtime.ThreadCreateProfile(nil) + fmt.Fprintf(w, `go_threads %d`+"\n", numThread) + + // Export build details. + fmt.Fprintf(w, "go_info{version=%q} 1\n", runtime.Version()) + fmt.Fprintf(w, "go_info_ext{compiler=%q, GOARCH=%q, GOOS=%q, GOROOT=%q} 1\n", + runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT()) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/histogram.go b/vendor/github.com/VictoriaMetrics/metrics/histogram.go new file mode 100644 index 0000000..b0e8d57 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/histogram.go @@ -0,0 +1,230 @@ +package metrics + +import ( + "fmt" + "io" + "math" + "sync" + "time" +) + +const ( + e10Min = -9 + e10Max = 18 + bucketsPerDecimal = 18 + decimalBucketsCount = e10Max - e10Min + bucketsCount = decimalBucketsCount * bucketsPerDecimal +) + +var bucketMultiplier = math.Pow(10, 1.0/bucketsPerDecimal) + +// Histogram is a histogram for non-negative values with automatically created buckets. +// +// See https://medium.com/@valyala/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350 +// +// Each bucket contains a counter for values in the given range. +// Each non-empty bucket is exposed via the following metric: +// +// _bucket{,vmrange="..."} +// +// Where: +// +// - is the metric name passed to NewHistogram +// - is optional tags for the , which are passed to NewHistogram +// - and - start and end values for the given bucket +// - - the number of hits to the given bucket during Update* calls +// +// Histogram buckets can be converted to Prometheus-like buckets with `le` labels +// with `prometheus_buckets(_bucket)` function from PromQL extensions in VictoriaMetrics. +// (see https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/MetricsQL ): +// +// prometheus_buckets(request_duration_bucket) +// +// Time series produced by the Histogram have better compression ratio comparing to +// Prometheus histogram buckets with `le` labels, since they don't include counters +// for all the previous buckets. +// +// Zero histogram is usable. +type Histogram struct { + // Mu gurantees synchronous update for all the counters and sum. + mu sync.Mutex + + decimalBuckets [decimalBucketsCount]*[bucketsPerDecimal]uint64 + + lower uint64 + upper uint64 + + sum float64 +} + +// Reset resets the given histogram. +func (h *Histogram) Reset() { + h.mu.Lock() + for _, db := range h.decimalBuckets[:] { + if db == nil { + continue + } + for i := range db[:] { + db[i] = 0 + } + } + h.lower = 0 + h.upper = 0 + h.sum = 0 + h.mu.Unlock() +} + +// Update updates h with v. +// +// Negative values and NaNs are ignored. +func (h *Histogram) Update(v float64) { + if math.IsNaN(v) || v < 0 { + // Skip NaNs and negative values. + return + } + bucketIdx := (math.Log10(v) - e10Min) * bucketsPerDecimal + h.mu.Lock() + h.sum += v + if bucketIdx < 0 { + h.lower++ + } else if bucketIdx >= bucketsCount { + h.upper++ + } else { + idx := uint(bucketIdx) + if bucketIdx == float64(idx) && idx > 0 { + // Edge case for 10^n values, which must go to the lower bucket + // according to Prometheus logic for `le`-based histograms. + idx-- + } + decimalBucketIdx := idx / bucketsPerDecimal + offset := idx % bucketsPerDecimal + db := h.decimalBuckets[decimalBucketIdx] + if db == nil { + var b [bucketsPerDecimal]uint64 + db = &b + h.decimalBuckets[decimalBucketIdx] = db + } + db[offset]++ + } + h.mu.Unlock() +} + +// VisitNonZeroBuckets calls f for all buckets with non-zero counters. +// +// vmrange contains "..." string with bucket bounds. The lower bound +// isn't included in the bucket, while the upper bound is included. +// This is required to be compatible with Prometheus-style histogram buckets +// with `le` (less or equal) labels. +func (h *Histogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) { + h.mu.Lock() + if h.lower > 0 { + f(lowerBucketRange, h.lower) + } + for decimalBucketIdx, db := range h.decimalBuckets[:] { + if db == nil { + continue + } + for offset, count := range db[:] { + if count > 0 { + bucketIdx := decimalBucketIdx*bucketsPerDecimal + offset + vmrange := getVMRange(bucketIdx) + f(vmrange, count) + } + } + } + if h.upper > 0 { + f(upperBucketRange, h.upper) + } + h.mu.Unlock() +} + +// NewHistogram creates and returns new histogram with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +func NewHistogram(name string) *Histogram { + return defaultSet.NewHistogram(name) +} + +// GetOrCreateHistogram returns registered histogram with the given name +// or creates new histogram if the registry doesn't contain histogram with +// the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewHistogram instead of GetOrCreateHistogram. +func GetOrCreateHistogram(name string) *Histogram { + return defaultSet.GetOrCreateHistogram(name) +} + +// UpdateDuration updates request duration based on the given startTime. +func (h *Histogram) UpdateDuration(startTime time.Time) { + d := time.Since(startTime).Seconds() + h.Update(d) +} + +func getVMRange(bucketIdx int) string { + bucketRangesOnce.Do(initBucketRanges) + return bucketRanges[bucketIdx] +} + +func initBucketRanges() { + v := math.Pow10(e10Min) + start := fmt.Sprintf("%.3e", v) + for i := 0; i < bucketsCount; i++ { + v *= bucketMultiplier + end := fmt.Sprintf("%.3e", v) + bucketRanges[i] = start + "..." + end + start = end + } +} + +var ( + lowerBucketRange = fmt.Sprintf("0...%.3e", math.Pow10(e10Min)) + upperBucketRange = fmt.Sprintf("%.3e...+Inf", math.Pow10(e10Max)) + + bucketRanges [bucketsCount]string + bucketRangesOnce sync.Once +) + +func (h *Histogram) marshalTo(prefix string, w io.Writer) { + countTotal := uint64(0) + h.VisitNonZeroBuckets(func(vmrange string, count uint64) { + tag := fmt.Sprintf("vmrange=%q", vmrange) + metricName := addTag(prefix, tag) + name, labels := splitMetricName(metricName) + fmt.Fprintf(w, "%s_bucket%s %d\n", name, labels, count) + countTotal += count + }) + if countTotal == 0 { + return + } + name, labels := splitMetricName(prefix) + sum := h.getSum() + if float64(int64(sum)) == sum { + fmt.Fprintf(w, "%s_sum%s %d\n", name, labels, int64(sum)) + } else { + fmt.Fprintf(w, "%s_sum%s %g\n", name, labels, sum) + } + fmt.Fprintf(w, "%s_count%s %d\n", name, labels, countTotal) +} + +func (h *Histogram) getSum() float64 { + h.mu.Lock() + sum := h.sum + h.mu.Unlock() + return sum +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/histogram_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/histogram_example_test.go new file mode 100644 index 0000000..358050e --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/histogram_example_test.go @@ -0,0 +1,27 @@ +package metrics_test + +import ( + "fmt" + "time" + + "github.com/VictoriaMetrics/metrics" +) + +func ExampleHistogram() { + // Define a histogram in global scope. + var h = metrics.NewHistogram(`request_duration_seconds{path="/foo/bar"}`) + + // Update the histogram with the duration of processRequest call. + startTime := time.Now() + processRequest() + h.UpdateDuration(startTime) +} + +func ExampleHistogram_vec() { + for i := 0; i < 3; i++ { + // Dynamically construct metric name and pass it to GetOrCreateHistogram. + name := fmt.Sprintf(`response_size_bytes{path=%q}`, "/foo/bar") + response := processRequest() + metrics.GetOrCreateHistogram(name).Update(float64(len(response))) + } +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/histogram_test.go b/vendor/github.com/VictoriaMetrics/metrics/histogram_test.go new file mode 100644 index 0000000..dea989b --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/histogram_test.go @@ -0,0 +1,200 @@ +package metrics + +import ( + "bytes" + "fmt" + "math" + "reflect" + "strings" + "testing" + "time" +) + +func TestGetVMRange(t *testing.T) { + f := func(bucketIdx int, vmrangeExpected string) { + t.Helper() + vmrange := getVMRange(bucketIdx) + if vmrange != vmrangeExpected { + t.Fatalf("unexpected vmrange for bucketIdx=%d; got %s; want %s", bucketIdx, vmrange, vmrangeExpected) + } + } + f(0, "1.000e-09...1.136e-09") + f(1, "1.136e-09...1.292e-09") + f(bucketsPerDecimal-1, "8.799e-09...1.000e-08") + f(bucketsPerDecimal, "1.000e-08...1.136e-08") + f(bucketsPerDecimal*(-e10Min)-1, "8.799e-01...1.000e+00") + f(bucketsPerDecimal*(-e10Min), "1.000e+00...1.136e+00") + f(bucketsPerDecimal*(e10Max-e10Min)-1, "8.799e+17...1.000e+18") +} + +func TestHistogramSerial(t *testing.T) { + name := `TestHistogramSerial` + h := NewHistogram(name) + + // Verify that the histogram is invisible in the output of WritePrometheus when it has no data. + var bb bytes.Buffer + WritePrometheus(&bb, false) + result := bb.String() + if strings.Contains(result, name) { + t.Fatalf("histogram %s shouldn't be visible in the WritePrometheus output; got\n%s", name, result) + } + + // Write data to histogram + for i := 98; i < 218; i++ { + h.Update(float64(i)) + } + + // Make sure the histogram prints _bucket on marshalTo call + testMarshalTo(t, h, "prefix", `prefix_bucket{vmrange="8.799e+01...1.000e+02"} 3 +prefix_bucket{vmrange="1.000e+02...1.136e+02"} 13 +prefix_bucket{vmrange="1.136e+02...1.292e+02"} 16 +prefix_bucket{vmrange="1.292e+02...1.468e+02"} 17 +prefix_bucket{vmrange="1.468e+02...1.668e+02"} 20 +prefix_bucket{vmrange="1.668e+02...1.896e+02"} 23 +prefix_bucket{vmrange="1.896e+02...2.154e+02"} 26 +prefix_bucket{vmrange="2.154e+02...2.448e+02"} 2 +prefix_sum 18900 +prefix_count 120 +`) + testMarshalTo(t, h, ` m{foo="bar"}`, ` m_bucket{foo="bar",vmrange="8.799e+01...1.000e+02"} 3 + m_bucket{foo="bar",vmrange="1.000e+02...1.136e+02"} 13 + m_bucket{foo="bar",vmrange="1.136e+02...1.292e+02"} 16 + m_bucket{foo="bar",vmrange="1.292e+02...1.468e+02"} 17 + m_bucket{foo="bar",vmrange="1.468e+02...1.668e+02"} 20 + m_bucket{foo="bar",vmrange="1.668e+02...1.896e+02"} 23 + m_bucket{foo="bar",vmrange="1.896e+02...2.154e+02"} 26 + m_bucket{foo="bar",vmrange="2.154e+02...2.448e+02"} 2 + m_sum{foo="bar"} 18900 + m_count{foo="bar"} 120 +`) + + // Verify Reset + h.Reset() + bb.Reset() + WritePrometheus(&bb, false) + result = bb.String() + if strings.Contains(result, name) { + t.Fatalf("unexpected histogram %s in the WritePrometheus output; got\n%s", name, result) + } + + // Verify supported ranges + for e10 := -100; e10 < 100; e10++ { + for offset := 0; offset < bucketsPerDecimal; offset++ { + m := 1 + math.Pow(bucketMultiplier, float64(offset)) + f1 := m * math.Pow10(e10) + h.Update(f1) + f2 := (m + 0.5*bucketMultiplier) * math.Pow10(e10) + h.Update(f2) + f3 := (m + 2*bucketMultiplier) * math.Pow10(e10) + h.Update(f3) + } + } + h.UpdateDuration(time.Now().Add(-time.Minute)) + + // Verify edge cases + h.Update(0) + h.Update(math.Inf(1)) + h.Update(math.Inf(-1)) + h.Update(math.NaN()) + h.Update(-123) + // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1096 + h.Update(math.Float64frombits(0x3e112e0be826d695)) + + // Make sure the histogram becomes visible in the output of WritePrometheus, + // since now it contains values. + bb.Reset() + WritePrometheus(&bb, false) + result = bb.String() + if !strings.Contains(result, name) { + t.Fatalf("missing histogram %s in the WritePrometheus output; got\n%s", name, result) + } +} + +func TestHistogramConcurrent(t *testing.T) { + name := "HistogramConcurrent" + h := NewHistogram(name) + err := testConcurrent(func() error { + for f := 0.6; f < 1.4; f += 0.1 { + h.Update(f) + } + return nil + }) + if err != nil { + t.Fatal(err) + } + testMarshalTo(t, h, "prefix", `prefix_bucket{vmrange="5.995e-01...6.813e-01"} 5 +prefix_bucket{vmrange="6.813e-01...7.743e-01"} 5 +prefix_bucket{vmrange="7.743e-01...8.799e-01"} 5 +prefix_bucket{vmrange="8.799e-01...1.000e+00"} 10 +prefix_bucket{vmrange="1.000e+00...1.136e+00"} 5 +prefix_bucket{vmrange="1.136e+00...1.292e+00"} 5 +prefix_bucket{vmrange="1.292e+00...1.468e+00"} 5 +prefix_sum 38 +prefix_count 40 +`) + + var labels []string + var counts []uint64 + h.VisitNonZeroBuckets(func(label string, count uint64) { + labels = append(labels, label) + counts = append(counts, count) + }) + labelsExpected := []string{ + "5.995e-01...6.813e-01", + "6.813e-01...7.743e-01", + "7.743e-01...8.799e-01", + "8.799e-01...1.000e+00", + "1.000e+00...1.136e+00", + "1.136e+00...1.292e+00", + "1.292e+00...1.468e+00", + } + if !reflect.DeepEqual(labels, labelsExpected) { + t.Fatalf("unexpected labels; got %v; want %v", labels, labelsExpected) + } + countsExpected := []uint64{5, 5, 5, 10, 5, 5, 5} + if !reflect.DeepEqual(counts, countsExpected) { + t.Fatalf("unexpected counts; got %v; want %v", counts, countsExpected) + } +} + +func TestHistogramWithTags(t *testing.T) { + name := `TestHistogram{tag="foo"}` + h := NewHistogram(name) + h.Update(123) + + var bb bytes.Buffer + WritePrometheus(&bb, false) + result := bb.String() + namePrefixWithTag := `TestHistogram_bucket{tag="foo",vmrange="1.136e+02...1.292e+02"} 1` + "\n" + if !strings.Contains(result, namePrefixWithTag) { + t.Fatalf("missing histogram %s in the WritePrometheus output; got\n%s", namePrefixWithTag, result) + } +} + +func TestGetOrCreateHistogramSerial(t *testing.T) { + name := "GetOrCreateHistogramSerial" + if err := testGetOrCreateHistogram(name); err != nil { + t.Fatal(err) + } +} + +func TestGetOrCreateHistogramConcurrent(t *testing.T) { + name := "GetOrCreateHistogramConcurrent" + err := testConcurrent(func() error { + return testGetOrCreateHistogram(name) + }) + if err != nil { + t.Fatal(err) + } +} + +func testGetOrCreateHistogram(name string) error { + h1 := GetOrCreateHistogram(name) + for i := 0; i < 10; i++ { + h2 := GetOrCreateHistogram(name) + if h1 != h2 { + return fmt.Errorf("unexpected histogram returned; got %p; want %p", h2, h1) + } + } + return nil +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/histogram_timing_test.go b/vendor/github.com/VictoriaMetrics/metrics/histogram_timing_test.go new file mode 100644 index 0000000..cee323f --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/histogram_timing_test.go @@ -0,0 +1,17 @@ +package metrics + +import ( + "testing" +) + +func BenchmarkHistogramUpdate(b *testing.B) { + h := GetOrCreateHistogram("BenchmarkHistogramUpdate") + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + h.Update(float64(i)) + i++ + } + }) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/metrics.go b/vendor/github.com/VictoriaMetrics/metrics/metrics.go new file mode 100644 index 0000000..c28c036 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/metrics.go @@ -0,0 +1,112 @@ +// Package metrics implements Prometheus-compatible metrics for applications. +// +// This package is lightweight alternative to https://github.com/prometheus/client_golang +// with simpler API and smaller dependencies. +// +// Usage: +// +// 1. Register the required metrics via New* functions. +// 2. Expose them to `/metrics` page via WritePrometheus. +// 3. Update the registered metrics during application lifetime. +// +// The package has been extracted from https://victoriametrics.com/ +package metrics + +import ( + "io" +) + +type namedMetric struct { + name string + metric metric +} + +type metric interface { + marshalTo(prefix string, w io.Writer) +} + +var defaultSet = NewSet() + +// WritePrometheus writes all the registered metrics in Prometheus format to w. +// +// If exposeProcessMetrics is true, then various `go_*` and `process_*` metrics +// are exposed for the current process. +// +// The WritePrometheus func is usually called inside "/metrics" handler: +// +// http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { +// metrics.WritePrometheus(w, true) +// }) +// +func WritePrometheus(w io.Writer, exposeProcessMetrics bool) { + defaultSet.WritePrometheus(w) + if exposeProcessMetrics { + WriteProcessMetrics(w) + } +} + +// WriteProcessMetrics writes additional process metrics in Prometheus format to w. +// +// The following `go_*` and `process_*` metrics are exposed for the currently +// running process. Below is a short description for the exposed `process_*` metrics: +// +// - process_cpu_seconds_system_total - CPU time spent in syscalls +// - process_cpu_seconds_user_total - CPU time spent in userspace +// - process_cpu_seconds_total - CPU time spent by the process +// - process_major_pagefaults_total - page faults resulted in disk IO +// - process_minor_pagefaults_total - page faults resolved without disk IO +// - process_resident_memory_bytes - recently accessed memory (aka RSS or resident memory) +// - process_resident_memory_peak_bytes - the maximum RSS memory usage +// - process_resident_memory_anon_bytes - RSS for memory-mapped files +// - process_resident_memory_file_bytes - RSS for memory allocated by the process +// - process_resident_memory_shared_bytes - RSS for memory shared between multiple processes +// - process_virtual_memory_bytes - virtual memory usage +// - process_virtual_memory_peak_bytes - the maximum virtual memory usage +// - process_num_threads - the number of threads +// - process_start_time_seconds - process start time as unix timestamp +// +// - process_io_read_bytes_total - the number of bytes read via syscalls +// - process_io_written_bytes_total - the number of bytes written via syscalls +// - process_io_read_syscalls_total - the number of read syscalls +// - process_io_write_syscalls_total - the number of write syscalls +// - process_io_storage_read_bytes_total - the number of bytes actually read from disk +// - process_io_storage_written_bytes_total - the number of bytes actually written to disk +// +// - go_memstats_alloc_bytes - memory usage for Go objects in the heap +// - go_memstats_alloc_bytes_total - the cumulative counter for total size of allocated Go objects +// - go_memstats_frees_total - the cumulative counter for number of freed Go objects +// - go_memstats_gc_cpu_fraction - the fraction of CPU spent in Go garbage collector +// - go_memstats_gc_sys_bytes - the size of Go garbage collector metadata +// - go_memstats_heap_alloc_bytes - the same as go_memstats_alloc_bytes +// - go_memstats_heap_idle_bytes - idle memory ready for new Go object allocations +// - go_memstats_heap_objects - the number of Go objects in the heap +// - go_memstats_heap_sys_bytes - memory requested for Go objects from the OS +// - go_memstats_mallocs_total - the number of allocations for Go objects +// - go_memstats_next_gc_bytes - the target heap size when the next garbage collection should start +// - go_memstats_stack_inuse_bytes - memory used for goroutine stacks +// - go_memstats_stack_sys_bytes - memory requested fromthe OS for goroutine stacks +// - go_memstats_sys_bytes - memory requested by Go runtime from the OS +// +// The WriteProcessMetrics func is usually called in combination with writing Set metrics +// inside "/metrics" handler: +// +// http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { +// mySet.WritePrometheus(w) +// metrics.WriteProcessMetrics(w) +// }) +// +// See also WrteFDMetrics. +func WriteProcessMetrics(w io.Writer) { + writeGoMetrics(w) + writeProcessMetrics(w) +} + +// WriteFDMetrics writes `process_max_fds` and `process_open_fds` metrics to w. +func WriteFDMetrics(w io.Writer) { + writeFDMetrics(w) +} + +// UnregisterMetric removes metric with the given name from default set. +func UnregisterMetric(name string) bool { + return defaultSet.UnregisterMetric(name) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/metrics_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/metrics_example_test.go new file mode 100644 index 0000000..0ac08ae --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/metrics_example_test.go @@ -0,0 +1,14 @@ +package metrics_test + +import ( + "net/http" + + "github.com/VictoriaMetrics/metrics" +) + +func ExampleWritePrometheus() { + // Export all the registered metrics in Prometheus format at `/metrics` http path. + http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { + metrics.WritePrometheus(w, true) + }) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/metrics_test.go b/vendor/github.com/VictoriaMetrics/metrics/metrics_test.go new file mode 100644 index 0000000..baa624c --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/metrics_test.go @@ -0,0 +1,146 @@ +package metrics + +import ( + "bytes" + "fmt" + "testing" + "time" +) + +func TestInvalidName(t *testing.T) { + f := func(name string) { + t.Helper() + expectPanic(t, fmt.Sprintf("NewCounter(%q)", name), func() { NewCounter(name) }) + expectPanic(t, fmt.Sprintf("NewGauge(%q)", name), func() { NewGauge(name, func() float64 { return 0 }) }) + expectPanic(t, fmt.Sprintf("NewSummary(%q)", name), func() { NewSummary(name) }) + expectPanic(t, fmt.Sprintf("GetOrCreateCounter(%q)", name), func() { GetOrCreateCounter(name) }) + expectPanic(t, fmt.Sprintf("GetOrCreateGauge(%q)", name), func() { GetOrCreateGauge(name, func() float64 { return 0 }) }) + expectPanic(t, fmt.Sprintf("GetOrCreateSummary(%q)", name), func() { GetOrCreateSummary(name) }) + expectPanic(t, fmt.Sprintf("GetOrCreateHistogram(%q)", name), func() { GetOrCreateHistogram(name) }) + } + f("") + f("foo{") + f("foo}") + f("foo{bar") + f("foo{bar=") + f(`foo{bar="`) + f(`foo{bar="baz`) + f(`foo{bar="baz"`) + f(`foo{bar="baz",`) + f(`foo{bar="baz",}`) +} + +func TestDoubleRegister(t *testing.T) { + t.Run("NewCounter", func(t *testing.T) { + name := "NewCounterDoubleRegister" + NewCounter(name) + expectPanic(t, name, func() { NewCounter(name) }) + }) + t.Run("NewGauge", func(t *testing.T) { + name := "NewGaugeDoubleRegister" + NewGauge(name, func() float64 { return 0 }) + expectPanic(t, name, func() { NewGauge(name, func() float64 { return 0 }) }) + }) + t.Run("NewSummary", func(t *testing.T) { + name := "NewSummaryDoubleRegister" + NewSummary(name) + expectPanic(t, name, func() { NewSummary(name) }) + }) + t.Run("NewHistogram", func(t *testing.T) { + name := "NewHistogramDoubleRegister" + NewHistogram(name) + expectPanic(t, name, func() { NewSummary(name) }) + }) +} + +func TestGetOrCreateNotCounter(t *testing.T) { + name := "GetOrCreateNotCounter" + NewSummary(name) + expectPanic(t, name, func() { GetOrCreateCounter(name) }) +} + +func TestGetOrCreateNotGauge(t *testing.T) { + name := "GetOrCreateNotGauge" + NewCounter(name) + expectPanic(t, name, func() { GetOrCreateGauge(name, func() float64 { return 0 }) }) +} + +func TestGetOrCreateNotSummary(t *testing.T) { + name := "GetOrCreateNotSummary" + NewCounter(name) + expectPanic(t, name, func() { GetOrCreateSummary(name) }) +} + +func TestGetOrCreateNotHistogram(t *testing.T) { + name := "GetOrCreateNotHistogram" + NewCounter(name) + expectPanic(t, name, func() { GetOrCreateHistogram(name) }) +} + +func TestWritePrometheusSerial(t *testing.T) { + if err := testWritePrometheus(); err != nil { + t.Fatal(err) + } +} + +func TestWritePrometheusConcurrent(t *testing.T) { + if err := testConcurrent(testWritePrometheus); err != nil { + t.Fatal(err) + } +} + +func testWritePrometheus() error { + var bb bytes.Buffer + WritePrometheus(&bb, false) + resultWithoutProcessMetrics := bb.String() + bb.Reset() + WritePrometheus(&bb, true) + resultWithProcessMetrics := bb.String() + if len(resultWithProcessMetrics) <= len(resultWithoutProcessMetrics) { + return fmt.Errorf("result with process metrics must contain more data than the result without process metrics; got\n%q\nvs\n%q", + resultWithProcessMetrics, resultWithoutProcessMetrics) + } + return nil +} + +func expectPanic(t *testing.T, context string, f func()) { + t.Helper() + defer func() { + t.Helper() + if r := recover(); r == nil { + t.Fatalf("expecting panic in %s", context) + } + }() + f() +} + +func testConcurrent(f func() error) error { + const concurrency = 5 + resultsCh := make(chan error, concurrency) + for i := 0; i < concurrency; i++ { + go func() { + resultsCh <- f() + }() + } + for i := 0; i < concurrency; i++ { + select { + case err := <-resultsCh: + if err != nil { + return fmt.Errorf("unexpected error: %s", err) + } + case <-time.After(time.Second * 5): + return fmt.Errorf("timeout") + } + } + return nil +} + +func testMarshalTo(t *testing.T, m metric, prefix, resultExpected string) { + t.Helper() + var bb bytes.Buffer + m.marshalTo(prefix, &bb) + result := bb.String() + if result != resultExpected { + t.Fatalf("unexpected marshaled metric;\ngot\n%q\nwant\n%q", result, resultExpected) + } +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go new file mode 100644 index 0000000..12b5de8 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go @@ -0,0 +1,265 @@ +package metrics + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "strconv" + "strings" + "time" +) + +// See https://github.com/prometheus/procfs/blob/a4ac0826abceb44c40fc71daed2b301db498b93e/proc_stat.go#L40 . +const userHZ = 100 + +// See http://man7.org/linux/man-pages/man5/proc.5.html +type procStat struct { + State byte + Ppid int + Pgrp int + Session int + TtyNr int + Tpgid int + Flags uint + Minflt uint + Cminflt uint + Majflt uint + Cmajflt uint + Utime uint + Stime uint + Cutime int + Cstime int + Priority int + Nice int + NumThreads int + ItrealValue int + Starttime uint64 + Vsize uint + Rss int +} + +func writeProcessMetrics(w io.Writer) { + statFilepath := "/proc/self/stat" + data, err := ioutil.ReadFile(statFilepath) + if err != nil { + log.Printf("ERROR: cannot open %s: %s", statFilepath, err) + return + } + // Search for the end of command. + n := bytes.LastIndex(data, []byte(") ")) + if n < 0 { + log.Printf("ERROR: cannot find command in parentheses in %q read from %s", data, statFilepath) + return + } + data = data[n+2:] + + var p procStat + bb := bytes.NewBuffer(data) + _, err = fmt.Fscanf(bb, "%c %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", + &p.State, &p.Ppid, &p.Pgrp, &p.Session, &p.TtyNr, &p.Tpgid, &p.Flags, &p.Minflt, &p.Cminflt, &p.Majflt, &p.Cmajflt, + &p.Utime, &p.Stime, &p.Cutime, &p.Cstime, &p.Priority, &p.Nice, &p.NumThreads, &p.ItrealValue, &p.Starttime, &p.Vsize, &p.Rss) + if err != nil { + log.Printf("ERROR: cannot parse %q read from %s: %s", data, statFilepath, err) + return + } + + // It is expensive obtaining `process_open_fds` when big number of file descriptors is opened, + // so don't do it here. + // See writeFDMetrics instead. + + utime := float64(p.Utime) / userHZ + stime := float64(p.Stime) / userHZ + fmt.Fprintf(w, "process_cpu_seconds_system_total %g\n", stime) + fmt.Fprintf(w, "process_cpu_seconds_total %g\n", utime+stime) + fmt.Fprintf(w, "process_cpu_seconds_user_total %g\n", utime) + fmt.Fprintf(w, "process_major_pagefaults_total %d\n", p.Majflt) + fmt.Fprintf(w, "process_minor_pagefaults_total %d\n", p.Minflt) + fmt.Fprintf(w, "process_num_threads %d\n", p.NumThreads) + fmt.Fprintf(w, "process_resident_memory_bytes %d\n", p.Rss*4096) + fmt.Fprintf(w, "process_start_time_seconds %d\n", startTimeSeconds) + fmt.Fprintf(w, "process_virtual_memory_bytes %d\n", p.Vsize) + writeProcessMemMetrics(w) + writeIOMetrics(w) +} + +func writeIOMetrics(w io.Writer) { + ioFilepath := "/proc/self/io" + data, err := ioutil.ReadFile(ioFilepath) + if err != nil { + log.Printf("ERROR: cannot open %q: %s", ioFilepath, err) + } + getInt := func(s string) int64 { + n := strings.IndexByte(s, ' ') + if n < 0 { + log.Printf("ERROR: cannot find whitespace in %q at %q", s, ioFilepath) + return 0 + } + v, err := strconv.ParseInt(s[n+1:], 10, 64) + if err != nil { + log.Printf("ERROR: cannot parse %q at %q: %s", s, ioFilepath, err) + return 0 + } + return v + } + var rchar, wchar, syscr, syscw, readBytes, writeBytes int64 + lines := strings.Split(string(data), "\n") + for _, s := range lines { + s = strings.TrimSpace(s) + switch { + case strings.HasPrefix(s, "rchar: "): + rchar = getInt(s) + case strings.HasPrefix(s, "wchar: "): + wchar = getInt(s) + case strings.HasPrefix(s, "syscr: "): + syscr = getInt(s) + case strings.HasPrefix(s, "syscw: "): + syscw = getInt(s) + case strings.HasPrefix(s, "read_bytes: "): + readBytes = getInt(s) + case strings.HasPrefix(s, "write_bytes: "): + writeBytes = getInt(s) + } + } + fmt.Fprintf(w, "process_io_read_bytes_total %d\n", rchar) + fmt.Fprintf(w, "process_io_written_bytes_total %d\n", wchar) + fmt.Fprintf(w, "process_io_read_syscalls_total %d\n", syscr) + fmt.Fprintf(w, "process_io_write_syscalls_total %d\n", syscw) + fmt.Fprintf(w, "process_io_storage_read_bytes_total %d\n", readBytes) + fmt.Fprintf(w, "process_io_storage_written_bytes_total %d\n", writeBytes) +} + +var startTimeSeconds = time.Now().Unix() + +// writeFDMetrics writes process_max_fds and process_open_fds metrics to w. +func writeFDMetrics(w io.Writer) { + totalOpenFDs, err := getOpenFDsCount("/proc/self/fd") + if err != nil { + log.Printf("ERROR: cannot determine open file descriptors count: %s", err) + return + } + maxOpenFDs, err := getMaxFilesLimit("/proc/self/limits") + if err != nil { + log.Printf("ERROR: cannot determine the limit on open file descritors: %s", err) + return + } + fmt.Fprintf(w, "process_max_fds %d\n", maxOpenFDs) + fmt.Fprintf(w, "process_open_fds %d\n", totalOpenFDs) +} + +func getOpenFDsCount(path string) (uint64, error) { + f, err := os.Open(path) + if err != nil { + return 0, err + } + defer f.Close() + var totalOpenFDs uint64 + for { + names, err := f.Readdirnames(512) + if err == io.EOF { + break + } + if err != nil { + return 0, fmt.Errorf("unexpected error at Readdirnames: %s", err) + } + totalOpenFDs += uint64(len(names)) + } + return totalOpenFDs, nil +} + +func getMaxFilesLimit(path string) (uint64, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return 0, err + } + lines := strings.Split(string(data), "\n") + const prefix = "Max open files" + for _, s := range lines { + if !strings.HasPrefix(s, prefix) { + continue + } + text := strings.TrimSpace(s[len(prefix):]) + // Extract soft limit. + n := strings.IndexByte(text, ' ') + if n < 0 { + return 0, fmt.Errorf("cannot extract soft limit from %q", s) + } + text = text[:n] + if text == "unlimited" { + return 1<<64 - 1, nil + } + limit, err := strconv.ParseUint(text, 10, 64) + if err != nil { + return 0, fmt.Errorf("cannot parse soft limit from %q: %s", s, err) + } + return limit, nil + } + return 0, fmt.Errorf("cannot find max open files limit") +} + +// https://man7.org/linux/man-pages/man5/procfs.5.html +type memStats struct { + vmPeak uint64 + rssPeak uint64 + rssAnon uint64 + rssFile uint64 + rssShmem uint64 +} + +func writeProcessMemMetrics(w io.Writer) { + ms, err := getMemStats("/proc/self/status") + if err != nil { + log.Printf("ERROR: cannot determine memory status: %s", err) + return + } + fmt.Fprintf(w, "process_virtual_memory_peak_bytes %d\n", ms.vmPeak) + fmt.Fprintf(w, "process_resident_memory_peak_bytes %d\n", ms.rssPeak) + fmt.Fprintf(w, "process_resident_memory_anon_bytes %d\n", ms.rssAnon) + fmt.Fprintf(w, "process_resident_memory_file_bytes %d\n", ms.rssFile) + fmt.Fprintf(w, "process_resident_memory_shared_bytes %d\n", ms.rssShmem) + +} + +func getMemStats(path string) (*memStats, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + var ms memStats + lines := strings.Split(string(data), "\n") + for _, s := range lines { + if !strings.HasPrefix(s, "Vm") && !strings.HasPrefix(s, "Rss") { + continue + } + // Extract key value. + line := strings.Fields(s) + if len(line) != 3 { + return nil, fmt.Errorf("unexpected number of fields found in %q; got %d; want %d", s, len(line), 3) + } + memStatName := line[0] + memStatValue := line[1] + value, err := strconv.ParseUint(memStatValue, 10, 64) + if err != nil { + return nil, fmt.Errorf("cannot parse number from %q: %w", s, err) + } + if line[2] != "kB" { + return nil, fmt.Errorf("expecting kB value in %q; got %q", s, line[2]) + } + value *= 1024 + switch memStatName { + case "VmPeak:": + ms.vmPeak = value + case "VmHWM:": + ms.rssPeak = value + case "RssAnon:": + ms.rssAnon = value + case "RssFile:": + ms.rssFile = value + case "RssShmem:": + ms.rssShmem = value + } + } + return &ms, nil +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux_test.go b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux_test.go new file mode 100644 index 0000000..88dac03 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux_test.go @@ -0,0 +1,51 @@ +package metrics + +import "testing" + +func TestGetMaxFilesLimit(t *testing.T) { + f := func(want uint64, path string, wantErr bool) { + t.Helper() + got, err := getMaxFilesLimit(path) + if err != nil && !wantErr { + t.Fatalf("unexpected error: %v", err) + } + if got != want { + t.Fatalf("unexpected result: %d, want: %d at getMaxFilesLimit", got, want) + } + + } + f(1024, "testdata/limits", false) + f(0, "testdata/bad_path", true) + f(0, "testdata/limits_bad", true) +} + +func TestGetOpenFDsCount(t *testing.T) { + f := func(want uint64, path string, wantErr bool) { + t.Helper() + got, err := getOpenFDsCount(path) + if (err != nil && !wantErr) || (err == nil && wantErr) { + t.Fatalf("unexpected error: %v", err) + } + if got != want { + t.Fatalf("unexpected result: %d, want: %d at getOpenFDsCount", got, want) + } + } + f(5, "testdata/fd/", false) + f(0, "testdata/fd/0", true) + f(0, "testdata/limits", true) +} + +func TestGetMemStats(t *testing.T) { + f := func(want memStats, path string, wantErr bool) { + t.Helper() + got, err := getMemStats(path) + if (err != nil && !wantErr) || (err == nil && wantErr) { + t.Fatalf("unexpected error: %v", err) + } + if got != nil && *got != want { + t.Fatalf("unexpected result: %d, want: %d at getMemStats", *got, want) + } + } + f(memStats{vmPeak: 2130489344, rssPeak: 200679424, rssAnon: 121602048, rssFile: 11362304}, "testdata/status", false) + f(memStats{}, "testdata/status_bad", true) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go new file mode 100644 index 0000000..5e6ac93 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go @@ -0,0 +1,15 @@ +// +build !linux + +package metrics + +import ( + "io" +) + +func writeProcessMetrics(w io.Writer) { + // TODO: implement it +} + +func writeFDMetrics(w io.Writer) { + // TODO: implement it. +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/set.go b/vendor/github.com/VictoriaMetrics/metrics/set.go new file mode 100644 index 0000000..69b4de8 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/set.go @@ -0,0 +1,519 @@ +package metrics + +import ( + "bytes" + "fmt" + "io" + "sort" + "sync" + "time" +) + +// Set is a set of metrics. +// +// Metrics belonging to a set are exported separately from global metrics. +// +// Set.WritePrometheus must be called for exporting metrics from the set. +type Set struct { + mu sync.Mutex + a []*namedMetric + m map[string]*namedMetric + summaries []*Summary +} + +// NewSet creates new set of metrics. +func NewSet() *Set { + return &Set{ + m: make(map[string]*namedMetric), + } +} + +// WritePrometheus writes all the metrics from s to w in Prometheus format. +func (s *Set) WritePrometheus(w io.Writer) { + // Collect all the metrics in in-memory buffer in order to prevent from long locking due to slow w. + var bb bytes.Buffer + lessFunc := func(i, j int) bool { + return s.a[i].name < s.a[j].name + } + s.mu.Lock() + for _, sm := range s.summaries { + sm.updateQuantiles() + } + if !sort.SliceIsSorted(s.a, lessFunc) { + sort.Slice(s.a, lessFunc) + } + sa := append([]*namedMetric(nil), s.a...) + s.mu.Unlock() + + // Call marshalTo without the global lock, since certain metric types such as Gauge + // can call a callback, which, in turn, can try calling s.mu.Lock again. + for _, nm := range sa { + nm.metric.marshalTo(nm.name, &bb) + } + w.Write(bb.Bytes()) +} + +// NewHistogram creates and returns new histogram in s with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +func (s *Set) NewHistogram(name string) *Histogram { + h := &Histogram{} + s.registerMetric(name, h) + return h +} + +// GetOrCreateHistogram returns registered histogram in s with the given name +// or creates new histogram if s doesn't contain histogram with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewHistogram instead of GetOrCreateHistogram. +func (s *Set) GetOrCreateHistogram(name string) *Histogram { + s.mu.Lock() + nm := s.m[name] + s.mu.Unlock() + if nm == nil { + // Slow path - create and register missing histogram. + if err := validateMetric(name); err != nil { + panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) + } + nmNew := &namedMetric{ + name: name, + metric: &Histogram{}, + } + s.mu.Lock() + nm = s.m[name] + if nm == nil { + nm = nmNew + s.m[name] = nm + s.a = append(s.a, nm) + } + s.mu.Unlock() + } + h, ok := nm.metric.(*Histogram) + if !ok { + panic(fmt.Errorf("BUG: metric %q isn't a Histogram. It is %T", name, nm.metric)) + } + return h +} + +// NewCounter registers and returns new counter with the given name in the s. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned counter is safe to use from concurrent goroutines. +func (s *Set) NewCounter(name string) *Counter { + c := &Counter{} + s.registerMetric(name, c) + return c +} + +// GetOrCreateCounter returns registered counter in s with the given name +// or creates new counter if s doesn't contain counter with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned counter is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewCounter instead of GetOrCreateCounter. +func (s *Set) GetOrCreateCounter(name string) *Counter { + s.mu.Lock() + nm := s.m[name] + s.mu.Unlock() + if nm == nil { + // Slow path - create and register missing counter. + if err := validateMetric(name); err != nil { + panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) + } + nmNew := &namedMetric{ + name: name, + metric: &Counter{}, + } + s.mu.Lock() + nm = s.m[name] + if nm == nil { + nm = nmNew + s.m[name] = nm + s.a = append(s.a, nm) + } + s.mu.Unlock() + } + c, ok := nm.metric.(*Counter) + if !ok { + panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric)) + } + return c +} + +// NewFloatCounter registers and returns new FloatCounter with the given name in the s. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned FloatCounter is safe to use from concurrent goroutines. +func (s *Set) NewFloatCounter(name string) *FloatCounter { + c := &FloatCounter{} + s.registerMetric(name, c) + return c +} + +// GetOrCreateFloatCounter returns registered FloatCounter in s with the given name +// or creates new FloatCounter if s doesn't contain FloatCounter with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned FloatCounter is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewFloatCounter instead of GetOrCreateFloatCounter. +func (s *Set) GetOrCreateFloatCounter(name string) *FloatCounter { + s.mu.Lock() + nm := s.m[name] + s.mu.Unlock() + if nm == nil { + // Slow path - create and register missing counter. + if err := validateMetric(name); err != nil { + panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) + } + nmNew := &namedMetric{ + name: name, + metric: &FloatCounter{}, + } + s.mu.Lock() + nm = s.m[name] + if nm == nil { + nm = nmNew + s.m[name] = nm + s.a = append(s.a, nm) + } + s.mu.Unlock() + } + c, ok := nm.metric.(*FloatCounter) + if !ok { + panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric)) + } + return c +} + +// NewGauge registers and returns gauge with the given name in s, which calls f +// to obtain gauge value. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// f must be safe for concurrent calls. +// +// The returned gauge is safe to use from concurrent goroutines. +func (s *Set) NewGauge(name string, f func() float64) *Gauge { + if f == nil { + panic(fmt.Errorf("BUG: f cannot be nil")) + } + g := &Gauge{ + f: f, + } + s.registerMetric(name, g) + return g +} + +// GetOrCreateGauge returns registered gauge with the given name in s +// or creates new gauge if s doesn't contain gauge with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned gauge is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewGauge instead of GetOrCreateGauge. +func (s *Set) GetOrCreateGauge(name string, f func() float64) *Gauge { + s.mu.Lock() + nm := s.m[name] + s.mu.Unlock() + if nm == nil { + // Slow path - create and register missing gauge. + if f == nil { + panic(fmt.Errorf("BUG: f cannot be nil")) + } + if err := validateMetric(name); err != nil { + panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) + } + nmNew := &namedMetric{ + name: name, + metric: &Gauge{ + f: f, + }, + } + s.mu.Lock() + nm = s.m[name] + if nm == nil { + nm = nmNew + s.m[name] = nm + s.a = append(s.a, nm) + } + s.mu.Unlock() + } + g, ok := nm.metric.(*Gauge) + if !ok { + panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric)) + } + return g +} + +// NewSummary creates and returns new summary with the given name in s. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned summary is safe to use from concurrent goroutines. +func (s *Set) NewSummary(name string) *Summary { + return s.NewSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles) +} + +// NewSummaryExt creates and returns new summary in s with the given name, +// window and quantiles. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned summary is safe to use from concurrent goroutines. +func (s *Set) NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary { + if err := validateMetric(name); err != nil { + panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) + } + sm := newSummary(window, quantiles) + + s.mu.Lock() + // defer will unlock in case of panic + // checks in tests + defer s.mu.Unlock() + + s.mustRegisterLocked(name, sm) + registerSummaryLocked(sm) + s.registerSummaryQuantilesLocked(name, sm) + s.summaries = append(s.summaries, sm) + return sm +} + +// GetOrCreateSummary returns registered summary with the given name in s +// or creates new summary if s doesn't contain summary with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned summary is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewSummary instead of GetOrCreateSummary. +func (s *Set) GetOrCreateSummary(name string) *Summary { + return s.GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles) +} + +// GetOrCreateSummaryExt returns registered summary with the given name, +// window and quantiles in s or creates new summary if s doesn't +// contain summary with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned summary is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt. +func (s *Set) GetOrCreateSummaryExt(name string, window time.Duration, quantiles []float64) *Summary { + s.mu.Lock() + nm := s.m[name] + s.mu.Unlock() + if nm == nil { + // Slow path - create and register missing summary. + if err := validateMetric(name); err != nil { + panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) + } + sm := newSummary(window, quantiles) + nmNew := &namedMetric{ + name: name, + metric: sm, + } + s.mu.Lock() + nm = s.m[name] + if nm == nil { + nm = nmNew + s.m[name] = nm + s.a = append(s.a, nm) + registerSummaryLocked(sm) + s.registerSummaryQuantilesLocked(name, sm) + } + s.summaries = append(s.summaries, sm) + s.mu.Unlock() + } + sm, ok := nm.metric.(*Summary) + if !ok { + panic(fmt.Errorf("BUG: metric %q isn't a Summary. It is %T", name, nm.metric)) + } + if sm.window != window { + panic(fmt.Errorf("BUG: invalid window requested for the summary %q; requested %s; need %s", name, window, sm.window)) + } + if !isEqualQuantiles(sm.quantiles, quantiles) { + panic(fmt.Errorf("BUG: invalid quantiles requested from the summary %q; requested %v; need %v", name, quantiles, sm.quantiles)) + } + return sm +} + +func (s *Set) registerSummaryQuantilesLocked(name string, sm *Summary) { + for i, q := range sm.quantiles { + quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q)) + qv := &quantileValue{ + sm: sm, + idx: i, + } + s.mustRegisterLocked(quantileValueName, qv) + } +} + +func (s *Set) registerMetric(name string, m metric) { + if err := validateMetric(name); err != nil { + panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) + } + s.mu.Lock() + // defer will unlock in case of panic + // checks in test + defer s.mu.Unlock() + s.mustRegisterLocked(name, m) +} + +// mustRegisterLocked registers given metric with +// the given name. Panics if the given name was +// already registered before. +func (s *Set) mustRegisterLocked(name string, m metric) { + nm, ok := s.m[name] + if !ok { + nm = &namedMetric{ + name: name, + metric: m, + } + s.m[name] = nm + s.a = append(s.a, nm) + } + if ok { + panic(fmt.Errorf("BUG: metric %q is already registered", name)) + } +} + +// UnregisterMetric removes metric with the given name from s. +// +// True is returned if the metric has been removed. +// False is returned if the given metric is missing in s. +func (s *Set) UnregisterMetric(name string) bool { + s.mu.Lock() + defer s.mu.Unlock() + + nm, ok := s.m[name] + if !ok { + return false + } + m := nm.metric + + delete(s.m, name) + + deleteFromList := func(metricName string) { + for i, nm := range s.a { + if nm.name == metricName { + s.a = append(s.a[:i], s.a[i+1:]...) + return + } + } + panic(fmt.Errorf("BUG: cannot find metric %q in the list of registered metrics", name)) + } + + // remove metric from s.a + deleteFromList(name) + + sm, ok := m.(*Summary) + if !ok { + // There is no need in cleaning up summary. + return true + } + + // cleanup registry from per-quantile metrics + for _, q := range sm.quantiles { + quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q)) + delete(s.m, quantileValueName) + deleteFromList(quantileValueName) + } + + // Remove sm from s.summaries + found := false + for i, xsm := range s.summaries { + if xsm == sm { + s.summaries = append(s.summaries[:i], s.summaries[i+1:]...) + found = true + break + } + } + if !found { + panic(fmt.Errorf("BUG: cannot find summary %q in the list of registered summaries", name)) + } + unregisterSummary(sm) + return true +} + +// ListMetricNames returns a list of all the metrics in s. +func (s *Set) ListMetricNames() []string { + var list []string + for name := range s.m { + list = append(list, name) + } + return list +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/set_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/set_example_test.go new file mode 100644 index 0000000..50845d3 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/set_example_test.go @@ -0,0 +1,25 @@ +package metrics_test + +import ( + "bytes" + "fmt" + "github.com/VictoriaMetrics/metrics" +) + +func ExampleSet() { + // Create a set with a counter + s := metrics.NewSet() + sc := s.NewCounter("set_counter") + sc.Inc() + s.NewGauge(`set_gauge{foo="bar"}`, func() float64 { return 42 }) + + // Dump metrics from s. + var bb bytes.Buffer + s.WritePrometheus(&bb) + fmt.Printf("set metrics:\n%s\n", bb.String()) + + // Output: + // set metrics: + // set_counter 1 + // set_gauge{foo="bar"} 42 +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/set_test.go b/vendor/github.com/VictoriaMetrics/metrics/set_test.go new file mode 100644 index 0000000..c954d87 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/set_test.go @@ -0,0 +1,152 @@ +package metrics + +import ( + "fmt" + "sync" + "testing" + "time" +) + +func TestNewSet(t *testing.T) { + var ss []*Set + for i := 0; i < 10; i++ { + s := NewSet() + ss = append(ss, s) + } + for i := 0; i < 10; i++ { + s := ss[i] + for j := 0; j < 10; j++ { + c := s.NewCounter(fmt.Sprintf("counter_%d", j)) + c.Inc() + if n := c.Get(); n != 1 { + t.Fatalf("unexpected counter value; got %d; want %d", n, 1) + } + g := s.NewGauge(fmt.Sprintf("gauge_%d", j), func() float64 { return 123 }) + if v := g.Get(); v != 123 { + t.Fatalf("unexpected gauge value; got %v; want %v", v, 123) + } + sm := s.NewSummary(fmt.Sprintf("summary_%d", j)) + if sm == nil { + t.Fatalf("NewSummary returned nil") + } + h := s.NewHistogram(fmt.Sprintf("histogram_%d", j)) + if h == nil { + t.Fatalf("NewHistogram returned nil") + } + } + } +} + +func TestSetListMetricNames(t *testing.T) { + s := NewSet() + expect := []string{"cnt1", "cnt2", "cnt3"} + // Initialize a few counters + for _, n := range expect { + c := s.NewCounter(n) + c.Inc() + } + + list := s.ListMetricNames() + + if len(list) != len(expect) { + t.Fatalf("Metrics count is wrong for listing") + } + for _, e := range expect { + found := false + for _, n := range list { + if e == n { + found = true + } + } + if !found { + t.Fatalf("Metric %s not found in listing", e) + } + } +} + +func TestSetUnregisterMetric(t *testing.T) { + s := NewSet() + const cName, smName = "counter_1", "summary_1" + // Initialize a few metrics + c := s.NewCounter(cName) + c.Inc() + sm := s.NewSummary(smName) + sm.Update(1) + + // Unregister existing metrics + if !s.UnregisterMetric(cName) { + t.Fatalf("UnregisterMetric(%s) must return true", cName) + } + if !s.UnregisterMetric(smName) { + t.Fatalf("UnregisterMetric(%s) must return true", smName) + } + + // Unregister twice must return false + if s.UnregisterMetric(cName) { + t.Fatalf("UnregisterMetric(%s) must return false on unregistered metric", cName) + } + if s.UnregisterMetric(smName) { + t.Fatalf("UnregisterMetric(%s) must return false on unregistered metric", smName) + } + + // verify that registry is empty + if len(s.m) != 0 { + t.Fatalf("expected metrics map to be empty; got %d elements", len(s.m)) + } + if len(s.a) != 0 { + t.Fatalf("expected metrics list to be empty; got %d elements", len(s.a)) + } + + // Validate metrics are removed + ok := false + for _, n := range s.ListMetricNames() { + if n == cName || n == smName { + ok = true + } + } + if ok { + t.Fatalf("Metric counter_1 and summary_1 must not be listed anymore after unregister") + } + + // re-register with the same names supposed + // to be successful + s.NewCounter(cName).Inc() + s.NewSummary(smName).Update(float64(1)) +} + +// TestRegisterUnregister tests concurrent access to +// metrics during registering and unregistering. +// Should be tested specifically with `-race` enabled. +func TestRegisterUnregister(t *testing.T) { + const ( + workers = 16 + iterations = 1e3 + ) + wg := sync.WaitGroup{} + wg.Add(workers) + for n := 0; n < workers; n++ { + go func() { + defer wg.Done() + now := time.Now() + for i := 0; i < iterations; i++ { + iteration := i % 5 + counter := fmt.Sprintf(`counter{iteration="%d"}`, iteration) + GetOrCreateCounter(counter).Add(i) + UnregisterMetric(counter) + + histogram := fmt.Sprintf(`histogram{iteration="%d"}`, iteration) + GetOrCreateHistogram(histogram).UpdateDuration(now) + UnregisterMetric(histogram) + + gauge := fmt.Sprintf(`gauge{iteration="%d"}`, iteration) + GetOrCreateGauge(gauge, func() float64 { return 1 }) + UnregisterMetric(gauge) + + summary := fmt.Sprintf(`summary{iteration="%d"}`, iteration) + GetOrCreateSummary(summary).Update(float64(i)) + UnregisterMetric(summary) + } + }() + } + wg.Wait() +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/summary.go b/vendor/github.com/VictoriaMetrics/metrics/summary.go new file mode 100644 index 0000000..0f01e9a --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/summary.go @@ -0,0 +1,254 @@ +package metrics + +import ( + "fmt" + "io" + "math" + "strings" + "sync" + "time" + + "github.com/valyala/histogram" +) + +const defaultSummaryWindow = 5 * time.Minute + +var defaultSummaryQuantiles = []float64{0.5, 0.9, 0.97, 0.99, 1} + +// Summary implements summary. +type Summary struct { + mu sync.Mutex + + curr *histogram.Fast + next *histogram.Fast + + quantiles []float64 + quantileValues []float64 + + sum float64 + count uint64 + + window time.Duration +} + +// NewSummary creates and returns new summary with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned summary is safe to use from concurrent goroutines. +func NewSummary(name string) *Summary { + return defaultSet.NewSummary(name) +} + +// NewSummaryExt creates and returns new summary with the given name, +// window and quantiles. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned summary is safe to use from concurrent goroutines. +func NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary { + return defaultSet.NewSummaryExt(name, window, quantiles) +} + +func newSummary(window time.Duration, quantiles []float64) *Summary { + // Make a copy of quantiles in order to prevent from their modification by the caller. + quantiles = append([]float64{}, quantiles...) + validateQuantiles(quantiles) + sm := &Summary{ + curr: histogram.NewFast(), + next: histogram.NewFast(), + quantiles: quantiles, + quantileValues: make([]float64, len(quantiles)), + window: window, + } + return sm +} + +func validateQuantiles(quantiles []float64) { + for _, q := range quantiles { + if q < 0 || q > 1 { + panic(fmt.Errorf("BUG: quantile must be in the range [0..1]; got %v", q)) + } + } +} + +// Update updates the summary. +func (sm *Summary) Update(v float64) { + sm.mu.Lock() + sm.curr.Update(v) + sm.next.Update(v) + sm.sum += v + sm.count++ + sm.mu.Unlock() +} + +// UpdateDuration updates request duration based on the given startTime. +func (sm *Summary) UpdateDuration(startTime time.Time) { + d := time.Since(startTime).Seconds() + sm.Update(d) +} + +func (sm *Summary) marshalTo(prefix string, w io.Writer) { + // Marshal only *_sum and *_count values. + // Quantile values should be already updated by the caller via sm.updateQuantiles() call. + // sm.quantileValues will be marshaled later via quantileValue.marshalTo. + sm.mu.Lock() + sum := sm.sum + count := sm.count + sm.mu.Unlock() + + if count > 0 { + name, filters := splitMetricName(prefix) + if float64(int64(sum)) == sum { + // Marshal integer sum without scientific notation + fmt.Fprintf(w, "%s_sum%s %d\n", name, filters, int64(sum)) + } else { + fmt.Fprintf(w, "%s_sum%s %g\n", name, filters, sum) + } + fmt.Fprintf(w, "%s_count%s %d\n", name, filters, count) + } +} + +func splitMetricName(name string) (string, string) { + n := strings.IndexByte(name, '{') + if n < 0 { + return name, "" + } + return name[:n], name[n:] +} + +func (sm *Summary) updateQuantiles() { + sm.mu.Lock() + sm.quantileValues = sm.curr.Quantiles(sm.quantileValues[:0], sm.quantiles) + sm.mu.Unlock() +} + +// GetOrCreateSummary returns registered summary with the given name +// or creates new summary if the registry doesn't contain summary with +// the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned summary is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewSummary instead of GetOrCreateSummary. +func GetOrCreateSummary(name string) *Summary { + return defaultSet.GetOrCreateSummary(name) +} + +// GetOrCreateSummaryExt returns registered summary with the given name, +// window and quantiles or creates new summary if the registry doesn't +// contain summary with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned summary is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt. +func GetOrCreateSummaryExt(name string, window time.Duration, quantiles []float64) *Summary { + return defaultSet.GetOrCreateSummaryExt(name, window, quantiles) +} + +func isEqualQuantiles(a, b []float64) bool { + // Do not use relfect.DeepEqual, since it is slower than the direct comparison. + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +type quantileValue struct { + sm *Summary + idx int +} + +func (qv *quantileValue) marshalTo(prefix string, w io.Writer) { + qv.sm.mu.Lock() + v := qv.sm.quantileValues[qv.idx] + qv.sm.mu.Unlock() + if !math.IsNaN(v) { + fmt.Fprintf(w, "%s %g\n", prefix, v) + } +} + +func addTag(name, tag string) string { + if len(name) == 0 || name[len(name)-1] != '}' { + return fmt.Sprintf("%s{%s}", name, tag) + } + return fmt.Sprintf("%s,%s}", name[:len(name)-1], tag) +} + +func registerSummaryLocked(sm *Summary) { + window := sm.window + summariesLock.Lock() + summaries[window] = append(summaries[window], sm) + if len(summaries[window]) == 1 { + go summariesSwapCron(window) + } + summariesLock.Unlock() +} + +func unregisterSummary(sm *Summary) { + window := sm.window + summariesLock.Lock() + sms := summaries[window] + found := false + for i, xsm := range sms { + if xsm == sm { + sms = append(sms[:i], sms[i+1:]...) + found = true + break + } + } + if !found { + panic(fmt.Errorf("BUG: cannot find registered summary %p", sm)) + } + summaries[window] = sms + summariesLock.Unlock() +} + +func summariesSwapCron(window time.Duration) { + for { + time.Sleep(window / 2) + summariesLock.Lock() + for _, sm := range summaries[window] { + sm.mu.Lock() + tmp := sm.curr + sm.curr = sm.next + sm.next = tmp + sm.next.Reset() + sm.mu.Unlock() + } + summariesLock.Unlock() + } +} + +var ( + summaries = map[time.Duration][]*Summary{} + summariesLock sync.Mutex +) diff --git a/vendor/github.com/VictoriaMetrics/metrics/summary_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/summary_example_test.go new file mode 100644 index 0000000..09dc57f --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/summary_example_test.go @@ -0,0 +1,31 @@ +package metrics_test + +import ( + "fmt" + "time" + + "github.com/VictoriaMetrics/metrics" +) + +func ExampleSummary() { + // Define a summary in global scope. + var s = metrics.NewSummary(`request_duration_seconds{path="/foo/bar"}`) + + // Update the summary with the duration of processRequest call. + startTime := time.Now() + processRequest() + s.UpdateDuration(startTime) +} + +func ExampleSummary_vec() { + for i := 0; i < 3; i++ { + // Dynamically construct metric name and pass it to GetOrCreateSummary. + name := fmt.Sprintf(`response_size_bytes{path=%q}`, "/foo/bar") + response := processRequest() + metrics.GetOrCreateSummary(name).Update(float64(len(response))) + } +} + +func processRequest() string { + return "foobar" +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/summary_test.go b/vendor/github.com/VictoriaMetrics/metrics/summary_test.go new file mode 100644 index 0000000..9182a65 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/summary_test.go @@ -0,0 +1,155 @@ +package metrics + +import ( + "bytes" + "fmt" + "strings" + "testing" + "time" +) + +func TestSummarySerial(t *testing.T) { + name := `TestSummarySerial` + s := NewSummary(name) + + // Verify that the summary isn't visible in the output of WritePrometheus, + // since it doesn't contain any values yet. + var bb bytes.Buffer + WritePrometheus(&bb, false) + result := bb.String() + if strings.Contains(result, name) { + t.Fatalf("summary %s shouldn't be visible in the WritePrometheus output; got\n%s", name, result) + } + + // Write data to summary + for i := 0; i < 2000; i++ { + s.Update(float64(i)) + t := time.Now() + s.UpdateDuration(t.Add(-time.Millisecond * time.Duration(i))) + } + + // Make sure the summary prints _sum and _count on marshalTo call + testMarshalTo(t, s, "prefix", fmt.Sprintf("prefix_sum %g\nprefix_count %d\n", s.sum, s.count)) + testMarshalTo(t, s, `m{foo="bar"}`, fmt.Sprintf("m_sum{foo=\"bar\"} %g\nm_count{foo=\"bar\"} %d\n", s.sum, s.count)) + + // Verify s.quantileValues + s.updateQuantiles() + if s.quantileValues[len(s.quantileValues)-1] != 1999 { + t.Fatalf("unexpected quantileValues[last]; got %v; want %v", s.quantileValues[len(s.quantileValues)-1], 1999) + } + + // Make sure the summary becomes visible in the output of WritePrometheus, + // since now it contains values. + bb.Reset() + WritePrometheus(&bb, false) + result = bb.String() + if !strings.Contains(result, name) { + t.Fatalf("missing summary %s in the WritePrometheus output; got\n%s", name, result) + } +} + +func TestSummaryConcurrent(t *testing.T) { + name := "SummaryConcurrent" + s := NewSummary(name) + err := testConcurrent(func() error { + for i := 0; i < 10; i++ { + s.Update(float64(i)) + } + return nil + }) + if err != nil { + t.Fatal(err) + } + testMarshalTo(t, s, "prefix", "prefix_sum 225\nprefix_count 50\n") +} + +func TestSummaryWithTags(t *testing.T) { + name := `TestSummary{tag="foo"}` + s := NewSummary(name) + s.Update(123) + + var bb bytes.Buffer + WritePrometheus(&bb, false) + result := bb.String() + namePrefixWithTag := `TestSummary{tag="foo",quantile="` + if !strings.Contains(result, namePrefixWithTag) { + t.Fatalf("missing summary prefix %s in the WritePrometheus output; got\n%s", namePrefixWithTag, result) + } +} + +func TestSummaryInvalidQuantiles(t *testing.T) { + name := "SummaryInvalidQuantiles" + expectPanic(t, name, func() { + NewSummaryExt(name, time.Minute, []float64{123, -234}) + }) +} + +func TestSummarySmallWindow(t *testing.T) { + name := "SummarySmallWindow" + window := time.Millisecond * 20 + quantiles := []float64{0.1, 0.2, 0.3} + s := NewSummaryExt(name, window, quantiles) + for i := 0; i < 2000; i++ { + s.Update(123) + } + // Wait for window update and verify that the summary has been cleared. + time.Sleep(2 * window) + var bb bytes.Buffer + WritePrometheus(&bb, false) + result := bb.String() + // _sum and _count are present in the output. + // Only {quantile} shouldn't be present. + name += "{" + if strings.Contains(result, name) { + t.Fatalf("summary %s cannot be present in the WritePrometheus output; got\n%s", name, result) + } +} + +func TestGetOrCreateSummaryInvalidWindow(t *testing.T) { + name := "GetOrCreateSummaryInvalidWindow" + GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles) + expectPanic(t, name, func() { + GetOrCreateSummaryExt(name, defaultSummaryWindow/2, defaultSummaryQuantiles) + }) +} + +func TestGetOrCreateSummaryInvalidQuantiles(t *testing.T) { + name := "GetOrCreateSummaryInvalidQuantiles" + GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles) + expectPanic(t, name, func() { + GetOrCreateSummaryExt(name, defaultSummaryWindow, []float64{0.1, 0.2}) + }) + quantiles := append([]float64{}, defaultSummaryQuantiles...) + quantiles[len(quantiles)-1] /= 2 + expectPanic(t, name, func() { + GetOrCreateSummaryExt(name, defaultSummaryWindow, quantiles) + }) +} + +func TestGetOrCreateSummarySerial(t *testing.T) { + name := "GetOrCreateSummarySerial" + if err := testGetOrCreateSummary(name); err != nil { + t.Fatal(err) + } +} + +func TestGetOrCreateSummaryConcurrent(t *testing.T) { + name := "GetOrCreateSummaryConcurrent" + err := testConcurrent(func() error { + return testGetOrCreateSummary(name) + }) + if err != nil { + t.Fatal(err) + } +} + +func testGetOrCreateSummary(name string) error { + s1 := GetOrCreateSummary(name) + for i := 0; i < 10; i++ { + s2 := GetOrCreateSummary(name) + if s1 != s2 { + return fmt.Errorf("unexpected summary returned; got %p; want %p", s2, s1) + } + } + return nil +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/0 b/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/0 new file mode 100644 index 0000000..e69de29 diff --git a/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/10 b/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/10 new file mode 100644 index 0000000..e69de29 diff --git a/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/2 b/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/2 new file mode 100644 index 0000000..e69de29 diff --git a/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/3 b/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/3 new file mode 100644 index 0000000..e69de29 diff --git a/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/5 b/vendor/github.com/VictoriaMetrics/metrics/testdata/fd/5 new file mode 100644 index 0000000..e69de29 diff --git a/vendor/github.com/VictoriaMetrics/metrics/testdata/limits b/vendor/github.com/VictoriaMetrics/metrics/testdata/limits new file mode 100644 index 0000000..fb520d3 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/testdata/limits @@ -0,0 +1,17 @@ +Limit Soft Limit Hard Limit Units +Max cpu time unlimited unlimited seconds +Max file size unlimited unlimited bytes +Max data size unlimited unlimited bytes +Max stack size 8388608 unlimited bytes +Max core file size 0 unlimited bytes +Max resident set unlimited unlimited bytes +Max processes 127458 127458 processes +Max open files 1024 1048576 files +Max locked memory 67108864 67108864 bytes +Max address space unlimited unlimited bytes +Max file locks unlimited unlimited locks +Max pending signals 127458 127458 signals +Max msgqueue size 819200 819200 bytes +Max nice priority 0 0 +Max realtime priority 0 0 +Max realtime timeout unlimited unlimited us \ No newline at end of file diff --git a/vendor/github.com/VictoriaMetrics/metrics/testdata/limits_bad b/vendor/github.com/VictoriaMetrics/metrics/testdata/limits_bad new file mode 100644 index 0000000..d54162d --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/testdata/limits_bad @@ -0,0 +1 @@ +Limit Soft Limit Hard Limit Units \ No newline at end of file diff --git a/vendor/github.com/VictoriaMetrics/metrics/testdata/status b/vendor/github.com/VictoriaMetrics/metrics/testdata/status new file mode 100644 index 0000000..d67a15e --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/testdata/status @@ -0,0 +1,115 @@ +Name: victoria-metric +Umask: 0022 +State: S (sleeping) +Tgid: 1 +Ngid: 0 +Pid: 1 +PPid: 0 +TracerPid: 0 +Uid: 0 0 0 0 +Gid: 0 0 0 0 +FDSize: 256 +Groups: 1 2 3 4 6 10 11 20 26 27 +NStgid: 1 +NSpid: 1 +NSpgid: 1 +NSsid: 1 +VmPeak: 2080548 kB +VmSize: 2080464 kB +VmLck: 0 kB +VmPin: 0 kB +VmHWM: 195976 kB +VmRSS: 105212 kB +RssAnon: 94092 kB +RssFile: 11120 kB +RssShmem: 0 kB +VmData: 632076 kB +VmStk: 132 kB +VmExe: 7004 kB +VmLib: 8 kB +VmPTE: 940 kB +VmSwap: 0 kB +HugetlbPages: 0 kB +CoreDumping: 0 +THP_enabled: 1 +Threads: 14 +SigQ: 1/127458 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: fffffffc3bfa3a00 +SigIgn: 0000000000000000 +SigCgt: fffffffdffc1feff +CapInh: 00000000a80425fb +CapPrm: 00000000a80425fb +CapEff: 00000000a80425fb +CapBnd: 00000000a80425fb +CapAmb: 0000000000000000 +NoNewPrivs: 0 +Seccomp: 0 +Speculation_Store_Bypass: thread vulnerable +Cpus_allowed: ff +Cpus_allowed_list: 0-7 +Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 +Mems_allowed_list: 0 +voluntary_ctxt_switches: 82 +nonvoluntary_ctxt_switches: 21 +/ # cat /proc/1/stat +stat statm status +/ # cat /proc/1/statm +520122 27057 2780 1751 0 158052 0 +/ # cat /proc/1/status +Name: victoria-metric +Umask: 0022 +State: S (sleeping) +Tgid: 1 +Ngid: 0 +Pid: 1 +PPid: 0 +TracerPid: 0 +Uid: 0 0 0 0 +Gid: 0 0 0 0 +FDSize: 256 +Groups: 1 2 3 4 6 10 11 20 26 27 +NStgid: 1 +NSpid: 1 +NSpgid: 1 +NSsid: 1 +VmPeak: 2080556 kB +VmSize: 2080520 kB +VmLck: 0 kB +VmPin: 0 kB +VmHWM: 195976 kB +VmRSS: 129848 kB +RssAnon: 118752 kB +RssFile: 11096 kB +RssShmem: 0 kB +VmData: 633020 kB +VmStk: 132 kB +VmExe: 7004 kB +VmLib: 8 kB +VmPTE: 984 kB +VmSwap: 0 kB +HugetlbPages: 0 kB +CoreDumping: 0 +THP_enabled: 1 +Threads: 14 +SigQ: 1/127458 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: fffffffc3bfa3a00 +SigIgn: 0000000000000000 +SigCgt: fffffffdffc1feff +CapInh: 00000000a80425fb +CapPrm: 00000000a80425fb +CapEff: 00000000a80425fb +CapBnd: 00000000a80425fb +CapAmb: 0000000000000000 +NoNewPrivs: 0 +Seccomp: 0 +Speculation_Store_Bypass: thread vulnerable +Cpus_allowed: ff +Cpus_allowed_list: 0-7 +Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 +Mems_allowed_list: 0 +voluntary_ctxt_switches: 82 +nonvoluntary_ctxt_switches: 21 \ No newline at end of file diff --git a/vendor/github.com/VictoriaMetrics/metrics/testdata/status_bad b/vendor/github.com/VictoriaMetrics/metrics/testdata/status_bad new file mode 100644 index 0000000..24cf8cb --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/testdata/status_bad @@ -0,0 +1,115 @@ +Name: victoria-metric +Umask: 0022 +State: S (sleeping) +Tgid: 1 +Ngid: 0 +Pid: 1 +PPid: 0 +TracerPid: 0 +Uid: 0 0 0 0 +Gid: 0 0 0 0 +FDSize: 256 +Groups: 1 2 3 4 6 10 11 20 26 27 +NStgid: 1 +NSpid: 1 +NSpgid: 1 +NSsid: 1 +VmPeak: 2080548 kB +VmSize: 2080464 kB +VmLck: 0 kB +VmPin: 0 kB +VmHWM: 195976 kB +VmRSS: 105212 kB +RssAnon: 94092 kB +RssFile: 11120 kB +RssShmem: 0 kB +VmData: 632076 kB +VmStk: 132 kB +VmExe: 7004 kB +VmLib: 8 kB +VmPTE: 940 kB +VmSwap: 0 kB +HugetlbPages: 0 kB +CoreDumping: 0 +THP_enabled: 1 +Threads: 14 +SigQ: 1/127458 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: fffffffc3bfa3a00 +SigIgn: 0000000000000000 +SigCgt: fffffffdffc1feff +CapInh: 00000000a80425fb +CapPrm: 00000000a80425fb +CapEff: 00000000a80425fb +CapBnd: 00000000a80425fb +CapAmb: 0000000000000000 +NoNewPrivs: 0 +Seccomp: 0 +Speculation_Store_Bypass: thread vulnerable +Cpus_allowed: ff +Cpus_allowed_list: 0-7 +Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 +Mems_allowed_list: 0 +voluntary_ctxt_switches: 82 +nonvoluntary_ctxt_switches: 21 +/ # cat /proc/1/stat +stat statm status +/ # cat /proc/1/statm +520122 27057 2780 1751 0 158052 0 +/ # cat /proc/1/status +Name: victoria-metric +Umask: 0022 +State: S (sleeping) +Tgid: 1 +Ngid: 0 +Pid: 1 +PPid: 0 +TracerPid: 0 +Uid: 0 0 0 0 +Gid: 0 0 0 0 +FDSize: 256 +Groups: 1 2 3 4 6 10 11 20 26 27 +NStgid: 1 +NSpid: 1 +NSpgid: 1 +NSsid: 1 +VmPeak: 2080556 kB +VmSize: 2080520 kB as +VmLck: 0 kB +VmPin: 0 kB +VmHWM: 195976 kB +VmRSS: 129848 kB +RssAnon: 118752 kB +RssFile: 11096 kB +RssShmem: 0 kB +VmData: 633020 kB +VmStk: 132 kB +VmExe: 7004 kB +VmLib: 8 kB +VmPTE: 984 kB +VmSwap: 0 kB +HugetlbPages: 0 kB +CoreDumping: 0 +THP_enabled: 1 +Threads: 14 +SigQ: 1/127458 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: fffffffc3bfa3a00 fsa +SigIgn: 0000000000000000 +SigCgt: fffffffdffc1feff +CapInh: 00000000a80425fb +CapPrm: 00000000a80425fb +CapEff: 00000000a80425fb +CapBnd: 00000000a80425fb +CapAmb: 0000000000000000 +NoNewPrivs: 0 +Seccomp: 0 +Speculation_Store_Bypass: thread vulnerable +Cpus_allowed: ff +Cpus_allowed_list: 0-7 +Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 +Mems_allowed_list: 0 +voluntary_ctxt_switches: 82 +nonvoluntary_ctxt_switches: 21 \ No newline at end of file diff --git a/vendor/github.com/VictoriaMetrics/metrics/validator.go b/vendor/github.com/VictoriaMetrics/metrics/validator.go new file mode 100644 index 0000000..9960189 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/validator.go @@ -0,0 +1,84 @@ +package metrics + +import ( + "fmt" + "regexp" + "strings" +) + +func validateMetric(s string) error { + if len(s) == 0 { + return fmt.Errorf("metric cannot be empty") + } + n := strings.IndexByte(s, '{') + if n < 0 { + return validateIdent(s) + } + ident := s[:n] + s = s[n+1:] + if err := validateIdent(ident); err != nil { + return err + } + if len(s) == 0 || s[len(s)-1] != '}' { + return fmt.Errorf("missing closing curly brace at the end of %q", ident) + } + return validateTags(s[:len(s)-1]) +} + +func validateTags(s string) error { + if len(s) == 0 { + return nil + } + for { + n := strings.IndexByte(s, '=') + if n < 0 { + return fmt.Errorf("missing `=` after %q", s) + } + ident := s[:n] + s = s[n+1:] + if err := validateIdent(ident); err != nil { + return err + } + if len(s) == 0 || s[0] != '"' { + return fmt.Errorf("missing starting `\"` for %q value; tail=%q", ident, s) + } + s = s[1:] + again: + n = strings.IndexByte(s, '"') + if n < 0 { + return fmt.Errorf("missing trailing `\"` for %q value; tail=%q", ident, s) + } + m := n + for m > 0 && s[m-1] == '\\' { + m-- + } + if (n-m)%2 == 1 { + s = s[n+1:] + goto again + } + s = s[n+1:] + if len(s) == 0 { + return nil + } + if !strings.HasPrefix(s, ",") { + return fmt.Errorf("missing `,` after %q value; tail=%q", ident, s) + } + s = skipSpace(s[1:]) + } +} + +func skipSpace(s string) string { + for len(s) > 0 && s[0] == ' ' { + s = s[1:] + } + return s +} + +func validateIdent(s string) error { + if !identRegexp.MatchString(s) { + return fmt.Errorf("invalid identifier %q", s) + } + return nil +} + +var identRegexp = regexp.MustCompile("^[a-zA-Z_:.][a-zA-Z0-9_:.]*$") diff --git a/vendor/github.com/VictoriaMetrics/metrics/validator_test.go b/vendor/github.com/VictoriaMetrics/metrics/validator_test.go new file mode 100644 index 0000000..8a5d734 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/validator_test.go @@ -0,0 +1,61 @@ +package metrics + +import ( + "testing" +) + +func TestValidateMetricSuccess(t *testing.T) { + f := func(s string) { + t.Helper() + if err := validateMetric(s); err != nil { + t.Fatalf("cannot validate %q: %s", s, err) + } + } + f("a") + f("_9:8") + f("a{}") + f(`a{foo="bar"}`) + f(`foo{bar="baz", x="y\"z"}`) + f(`foo{bar="b}az"}`) + f(`:foo:bar{bar="a",baz="b"}`) + f(`some.foo{bar="baz"}`) +} + +func TestValidateMetricError(t *testing.T) { + f := func(s string) { + t.Helper() + if err := validateMetric(s); err == nil { + t.Fatalf("expecting non-nil error when validating %q", s) + } + } + f("") + f("{}") + + // superflouos space + f("a ") + f(" a") + f(" a ") + f("a {}") + f("a{} ") + f("a{ }") + f(`a{foo ="bar"}`) + f(`a{ foo="bar"}`) + f(`a{foo= "bar"}`) + f(`a{foo="bar" }`) + f(`a{foo="bar" ,baz="a"}`) + + // invalid tags + f("a{foo}") + f("a{=}") + f(`a{=""}`) + f(`a{`) + f(`a}`) + f(`a{foo=}`) + f(`a{foo="`) + f(`a{foo="}`) + f(`a{foo="bar",}`) + f(`a{foo="bar", x`) + f(`a{foo="bar", x=`) + f(`a{foo="bar", x="`) + f(`a{foo="bar", x="}`) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/.travis.yml b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/.travis.yml new file mode 100644 index 0000000..336ccde --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/.travis.yml @@ -0,0 +1,16 @@ +language: go + +go: + - 1.7 + - 1.8 + +script: + # build test for supported platforms + - GOOS=linux go build + - GOOS=darwin go build + - GOOS=freebsd go build + - GOARCH=386 go build + + # run tests on a standard platform + - go test -v ./... + diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/LICENSE b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/LICENSE new file mode 100644 index 0000000..a2b05f6 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Aliaksandr Valialkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/README.md b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/README.md new file mode 100644 index 0000000..3d384c6 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/README.md @@ -0,0 +1,76 @@ +[![Build Status](https://travis-ci.org/valyala/fastrand.svg)](https://travis-ci.org/valyala/fastrand) +[![GoDoc](https://godoc.org/github.com/valyala/fastrand?status.svg)](http://godoc.org/github.com/valyala/fastrand) +[![Go Report](https://goreportcard.com/badge/github.com/valyala/fastrand)](https://goreportcard.com/report/github.com/valyala/fastrand) + + +# fastrand + +Fast pseudorandom number generator. + + +# Features + +- Optimized for speed. +- Performance scales on multiple CPUs. + +# How does it work? + +It abuses [sync.Pool](https://golang.org/pkg/sync/#Pool) for maintaining +"per-CPU" pseudorandom number generators. + +TODO: firgure out how to use real per-CPU pseudorandom number generators. + + +# Benchmark results + + +``` +$ GOMAXPROCS=1 go test -bench=. github.com/valyala/fastrand +goos: linux +goarch: amd64 +pkg: github.com/valyala/fastrand +BenchmarkUint32n 50000000 29.7 ns/op +BenchmarkRNGUint32n 200000000 6.50 ns/op +BenchmarkRNGUint32nWithLock 100000000 21.5 ns/op +BenchmarkMathRandInt31n 50000000 31.8 ns/op +BenchmarkMathRandRNGInt31n 100000000 17.9 ns/op +BenchmarkMathRandRNGInt31nWithLock 50000000 30.2 ns/op +PASS +ok github.com/valyala/fastrand 10.634s +``` + +``` +$ GOMAXPROCS=2 go test -bench=. github.com/valyala/fastrand +goos: linux +goarch: amd64 +pkg: github.com/valyala/fastrand +BenchmarkUint32n-2 100000000 17.6 ns/op +BenchmarkRNGUint32n-2 500000000 3.36 ns/op +BenchmarkRNGUint32nWithLock-2 50000000 32.0 ns/op +BenchmarkMathRandInt31n-2 20000000 51.2 ns/op +BenchmarkMathRandRNGInt31n-2 100000000 11.0 ns/op +BenchmarkMathRandRNGInt31nWithLock-2 20000000 91.0 ns/op +PASS +ok github.com/valyala/fastrand 9.543s +``` + +``` +$ GOMAXPROCS=4 go test -bench=. github.com/valyala/fastrand +goos: linux +goarch: amd64 +pkg: github.com/valyala/fastrand +BenchmarkUint32n-4 100000000 14.2 ns/op +BenchmarkRNGUint32n-4 500000000 3.30 ns/op +BenchmarkRNGUint32nWithLock-4 20000000 88.7 ns/op +BenchmarkMathRandInt31n-4 10000000 145 ns/op +BenchmarkMathRandRNGInt31n-4 200000000 8.35 ns/op +BenchmarkMathRandRNGInt31nWithLock-4 20000000 102 ns/op +PASS +ok github.com/valyala/fastrand 11.534s +``` + +As you can see, [fastrand.Uint32n](https://godoc.org/github.com/valyala/fastrand#Uint32n) +scales on multiple CPUs, while [rand.Int31n](https://golang.org/pkg/math/rand/#Int31n) +doesn't scale. Their performance is comparable on `GOMAXPROCS=1`, +but `fastrand.Uint32n` runs 3x faster than `rand.Int31n` on `GOMAXPROCS=2` +and 10x faster than `rand.Int31n` on `GOMAXPROCS=4`. diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/fastrand.go b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/fastrand.go new file mode 100644 index 0000000..3ea9177 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/fastrand.go @@ -0,0 +1,74 @@ +// Package fastrand implements fast pesudorandom number generator +// that should scale well on multi-CPU systems. +// +// Use crypto/rand instead of this package for generating +// cryptographically secure random numbers. +package fastrand + +import ( + "sync" + "time" +) + +// Uint32 returns pseudorandom uint32. +// +// It is safe calling this function from concurrent goroutines. +func Uint32() uint32 { + v := rngPool.Get() + if v == nil { + v = &RNG{} + } + r := v.(*RNG) + x := r.Uint32() + rngPool.Put(r) + return x +} + +var rngPool sync.Pool + +// Uint32n returns pseudorandom uint32 in the range [0..maxN). +// +// It is safe calling this function from concurrent goroutines. +func Uint32n(maxN uint32) uint32 { + x := Uint32() + // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + return uint32((uint64(x) * uint64(maxN)) >> 32) +} + +// RNG is a pseudorandom number generator. +// +// It is unsafe to call RNG methods from concurrent goroutines. +type RNG struct { + x uint32 +} + +// Uint32 returns pseudorandom uint32. +// +// It is unsafe to call this method from concurrent goroutines. +func (r *RNG) Uint32() uint32 { + for r.x == 0 { + r.x = getRandomUint32() + } + + // See https://en.wikipedia.org/wiki/Xorshift + x := r.x + x ^= x << 13 + x ^= x >> 17 + x ^= x << 5 + r.x = x + return x +} + +// Uint32n returns pseudorandom uint32 in the range [0..maxN). +// +// It is unsafe to call this method from concurrent goroutines. +func (r *RNG) Uint32n(maxN uint32) uint32 { + x := r.Uint32() + // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + return uint32((uint64(x) * uint64(maxN)) >> 32) +} + +func getRandomUint32() uint32 { + x := time.Now().UnixNano() + return uint32((x >> 32) ^ x) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/go.mod b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/go.mod new file mode 100644 index 0000000..958910b --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/go.mod @@ -0,0 +1 @@ +module github.com/valyala/fastrand diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/LICENSE b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/LICENSE new file mode 100644 index 0000000..902bcad --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Aliaksandr Valialkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/README.md b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/README.md new file mode 100644 index 0000000..6d4eb59 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/README.md @@ -0,0 +1,9 @@ +[![GoDoc](https://godoc.org/github.com/valyala/histogram?status.svg)](http://godoc.org/github.com/valyala/histogram) +[![Go Report](https://goreportcard.com/badge/github.com/valyala/histogram)](https://goreportcard.com/report/github.com/valyala/histogram) + + +# histogram + +Fast histograms for Go. + +See [docs](https://godoc.org/github.com/valyala/histogram). diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.mod b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.mod new file mode 100644 index 0000000..984efbe --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.mod @@ -0,0 +1,5 @@ +module github.com/valyala/histogram + +go 1.12 + +require github.com/valyala/fastrand v1.0.0 diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.sum b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.sum new file mode 100644 index 0000000..2b3e848 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.sum @@ -0,0 +1,2 @@ +github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= +github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/histogram.go b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/histogram.go new file mode 100644 index 0000000..e90dd70 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/histogram.go @@ -0,0 +1,127 @@ +// Package histogram provides building blocks for fast histograms. +package histogram + +import ( + "math" + "sort" + "sync" + + "github.com/valyala/fastrand" +) + +var ( + infNeg = math.Inf(-1) + infPos = math.Inf(1) + nan = math.NaN() +) + +// Fast is a fast histogram. +// +// It cannot be used from concurrently running goroutines without +// external synchronization. +type Fast struct { + max float64 + min float64 + count uint64 + + a []float64 + tmp []float64 + rng fastrand.RNG +} + +// NewFast returns new fast histogram. +func NewFast() *Fast { + f := &Fast{} + f.Reset() + return f +} + +// Reset resets the histogram. +func (f *Fast) Reset() { + f.max = infNeg + f.min = infPos + f.count = 0 + if len(f.a) > 0 { + f.a = f.a[:0] + f.tmp = f.tmp[:0] + } else { + // Free up memory occupied by unused histogram. + f.a = nil + f.tmp = nil + } +} + +// Update updates the f with v. +func (f *Fast) Update(v float64) { + if v > f.max { + f.max = v + } + if v < f.min { + f.min = v + } + + f.count++ + if len(f.a) < maxSamples { + f.a = append(f.a, v) + return + } + if n := int(f.rng.Uint32n(uint32(f.count))); n < len(f.a) { + f.a[n] = v + } +} + +const maxSamples = 1000 + +// Quantile returns the quantile value for the given phi. +func (f *Fast) Quantile(phi float64) float64 { + f.tmp = append(f.tmp[:0], f.a...) + sort.Float64s(f.tmp) + return f.quantile(phi) +} + +// Quantiles appends quantile values to dst for the given phis. +func (f *Fast) Quantiles(dst, phis []float64) []float64 { + f.tmp = append(f.tmp[:0], f.a...) + sort.Float64s(f.tmp) + for _, phi := range phis { + q := f.quantile(phi) + dst = append(dst, q) + } + return dst +} + +func (f *Fast) quantile(phi float64) float64 { + if len(f.tmp) == 0 || math.IsNaN(phi) { + return nan + } + if phi <= 0 { + return f.min + } + if phi >= 1 { + return f.max + } + idx := uint(phi*float64(len(f.tmp)-1) + 0.5) + if idx >= uint(len(f.tmp)) { + idx = uint(len(f.tmp) - 1) + } + return f.tmp[idx] +} + +// GetFast returns a histogram from a pool. +func GetFast() *Fast { + v := fastPool.Get() + if v == nil { + return NewFast() + } + return v.(*Fast) +} + +// PutFast puts hf to the pool. +// +// hf cannot be used after this call. +func PutFast(f *Fast) { + f.Reset() + fastPool.Put(f) +} + +var fastPool sync.Pool diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/modules.txt b/vendor/github.com/VictoriaMetrics/metrics/vendor/modules.txt new file mode 100644 index 0000000..f915051 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/modules.txt @@ -0,0 +1,4 @@ +# github.com/valyala/fastrand v1.0.0 +github.com/valyala/fastrand +# github.com/valyala/histogram v1.1.2 +github.com/valyala/histogram diff --git a/vendor/github.com/valyala/fastrand/.travis.yml b/vendor/github.com/valyala/fastrand/.travis.yml new file mode 100644 index 0000000..336ccde --- /dev/null +++ b/vendor/github.com/valyala/fastrand/.travis.yml @@ -0,0 +1,16 @@ +language: go + +go: + - 1.7 + - 1.8 + +script: + # build test for supported platforms + - GOOS=linux go build + - GOOS=darwin go build + - GOOS=freebsd go build + - GOARCH=386 go build + + # run tests on a standard platform + - go test -v ./... + diff --git a/vendor/github.com/valyala/fastrand/LICENSE b/vendor/github.com/valyala/fastrand/LICENSE new file mode 100644 index 0000000..a2b05f6 --- /dev/null +++ b/vendor/github.com/valyala/fastrand/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Aliaksandr Valialkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/valyala/fastrand/README.md b/vendor/github.com/valyala/fastrand/README.md new file mode 100644 index 0000000..3d384c6 --- /dev/null +++ b/vendor/github.com/valyala/fastrand/README.md @@ -0,0 +1,76 @@ +[![Build Status](https://travis-ci.org/valyala/fastrand.svg)](https://travis-ci.org/valyala/fastrand) +[![GoDoc](https://godoc.org/github.com/valyala/fastrand?status.svg)](http://godoc.org/github.com/valyala/fastrand) +[![Go Report](https://goreportcard.com/badge/github.com/valyala/fastrand)](https://goreportcard.com/report/github.com/valyala/fastrand) + + +# fastrand + +Fast pseudorandom number generator. + + +# Features + +- Optimized for speed. +- Performance scales on multiple CPUs. + +# How does it work? + +It abuses [sync.Pool](https://golang.org/pkg/sync/#Pool) for maintaining +"per-CPU" pseudorandom number generators. + +TODO: firgure out how to use real per-CPU pseudorandom number generators. + + +# Benchmark results + + +``` +$ GOMAXPROCS=1 go test -bench=. github.com/valyala/fastrand +goos: linux +goarch: amd64 +pkg: github.com/valyala/fastrand +BenchmarkUint32n 50000000 29.7 ns/op +BenchmarkRNGUint32n 200000000 6.50 ns/op +BenchmarkRNGUint32nWithLock 100000000 21.5 ns/op +BenchmarkMathRandInt31n 50000000 31.8 ns/op +BenchmarkMathRandRNGInt31n 100000000 17.9 ns/op +BenchmarkMathRandRNGInt31nWithLock 50000000 30.2 ns/op +PASS +ok github.com/valyala/fastrand 10.634s +``` + +``` +$ GOMAXPROCS=2 go test -bench=. github.com/valyala/fastrand +goos: linux +goarch: amd64 +pkg: github.com/valyala/fastrand +BenchmarkUint32n-2 100000000 17.6 ns/op +BenchmarkRNGUint32n-2 500000000 3.36 ns/op +BenchmarkRNGUint32nWithLock-2 50000000 32.0 ns/op +BenchmarkMathRandInt31n-2 20000000 51.2 ns/op +BenchmarkMathRandRNGInt31n-2 100000000 11.0 ns/op +BenchmarkMathRandRNGInt31nWithLock-2 20000000 91.0 ns/op +PASS +ok github.com/valyala/fastrand 9.543s +``` + +``` +$ GOMAXPROCS=4 go test -bench=. github.com/valyala/fastrand +goos: linux +goarch: amd64 +pkg: github.com/valyala/fastrand +BenchmarkUint32n-4 100000000 14.2 ns/op +BenchmarkRNGUint32n-4 500000000 3.30 ns/op +BenchmarkRNGUint32nWithLock-4 20000000 88.7 ns/op +BenchmarkMathRandInt31n-4 10000000 145 ns/op +BenchmarkMathRandRNGInt31n-4 200000000 8.35 ns/op +BenchmarkMathRandRNGInt31nWithLock-4 20000000 102 ns/op +PASS +ok github.com/valyala/fastrand 11.534s +``` + +As you can see, [fastrand.Uint32n](https://godoc.org/github.com/valyala/fastrand#Uint32n) +scales on multiple CPUs, while [rand.Int31n](https://golang.org/pkg/math/rand/#Int31n) +doesn't scale. Their performance is comparable on `GOMAXPROCS=1`, +but `fastrand.Uint32n` runs 3x faster than `rand.Int31n` on `GOMAXPROCS=2` +and 10x faster than `rand.Int31n` on `GOMAXPROCS=4`. diff --git a/vendor/github.com/valyala/fastrand/fastrand.go b/vendor/github.com/valyala/fastrand/fastrand.go new file mode 100644 index 0000000..3ea9177 --- /dev/null +++ b/vendor/github.com/valyala/fastrand/fastrand.go @@ -0,0 +1,74 @@ +// Package fastrand implements fast pesudorandom number generator +// that should scale well on multi-CPU systems. +// +// Use crypto/rand instead of this package for generating +// cryptographically secure random numbers. +package fastrand + +import ( + "sync" + "time" +) + +// Uint32 returns pseudorandom uint32. +// +// It is safe calling this function from concurrent goroutines. +func Uint32() uint32 { + v := rngPool.Get() + if v == nil { + v = &RNG{} + } + r := v.(*RNG) + x := r.Uint32() + rngPool.Put(r) + return x +} + +var rngPool sync.Pool + +// Uint32n returns pseudorandom uint32 in the range [0..maxN). +// +// It is safe calling this function from concurrent goroutines. +func Uint32n(maxN uint32) uint32 { + x := Uint32() + // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + return uint32((uint64(x) * uint64(maxN)) >> 32) +} + +// RNG is a pseudorandom number generator. +// +// It is unsafe to call RNG methods from concurrent goroutines. +type RNG struct { + x uint32 +} + +// Uint32 returns pseudorandom uint32. +// +// It is unsafe to call this method from concurrent goroutines. +func (r *RNG) Uint32() uint32 { + for r.x == 0 { + r.x = getRandomUint32() + } + + // See https://en.wikipedia.org/wiki/Xorshift + x := r.x + x ^= x << 13 + x ^= x >> 17 + x ^= x << 5 + r.x = x + return x +} + +// Uint32n returns pseudorandom uint32 in the range [0..maxN). +// +// It is unsafe to call this method from concurrent goroutines. +func (r *RNG) Uint32n(maxN uint32) uint32 { + x := r.Uint32() + // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + return uint32((uint64(x) * uint64(maxN)) >> 32) +} + +func getRandomUint32() uint32 { + x := time.Now().UnixNano() + return uint32((x >> 32) ^ x) +} diff --git a/vendor/github.com/valyala/fastrand/go.mod b/vendor/github.com/valyala/fastrand/go.mod new file mode 100644 index 0000000..958910b --- /dev/null +++ b/vendor/github.com/valyala/fastrand/go.mod @@ -0,0 +1 @@ +module github.com/valyala/fastrand diff --git a/vendor/github.com/valyala/histogram/LICENSE b/vendor/github.com/valyala/histogram/LICENSE new file mode 100644 index 0000000..902bcad --- /dev/null +++ b/vendor/github.com/valyala/histogram/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Aliaksandr Valialkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/valyala/histogram/README.md b/vendor/github.com/valyala/histogram/README.md new file mode 100644 index 0000000..6d4eb59 --- /dev/null +++ b/vendor/github.com/valyala/histogram/README.md @@ -0,0 +1,9 @@ +[![GoDoc](https://godoc.org/github.com/valyala/histogram?status.svg)](http://godoc.org/github.com/valyala/histogram) +[![Go Report](https://goreportcard.com/badge/github.com/valyala/histogram)](https://goreportcard.com/report/github.com/valyala/histogram) + + +# histogram + +Fast histograms for Go. + +See [docs](https://godoc.org/github.com/valyala/histogram). diff --git a/vendor/github.com/valyala/histogram/go.mod b/vendor/github.com/valyala/histogram/go.mod new file mode 100644 index 0000000..984efbe --- /dev/null +++ b/vendor/github.com/valyala/histogram/go.mod @@ -0,0 +1,5 @@ +module github.com/valyala/histogram + +go 1.12 + +require github.com/valyala/fastrand v1.0.0 diff --git a/vendor/github.com/valyala/histogram/go.sum b/vendor/github.com/valyala/histogram/go.sum new file mode 100644 index 0000000..2b3e848 --- /dev/null +++ b/vendor/github.com/valyala/histogram/go.sum @@ -0,0 +1,2 @@ +github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= +github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= diff --git a/vendor/github.com/valyala/histogram/histogram.go b/vendor/github.com/valyala/histogram/histogram.go new file mode 100644 index 0000000..e90dd70 --- /dev/null +++ b/vendor/github.com/valyala/histogram/histogram.go @@ -0,0 +1,127 @@ +// Package histogram provides building blocks for fast histograms. +package histogram + +import ( + "math" + "sort" + "sync" + + "github.com/valyala/fastrand" +) + +var ( + infNeg = math.Inf(-1) + infPos = math.Inf(1) + nan = math.NaN() +) + +// Fast is a fast histogram. +// +// It cannot be used from concurrently running goroutines without +// external synchronization. +type Fast struct { + max float64 + min float64 + count uint64 + + a []float64 + tmp []float64 + rng fastrand.RNG +} + +// NewFast returns new fast histogram. +func NewFast() *Fast { + f := &Fast{} + f.Reset() + return f +} + +// Reset resets the histogram. +func (f *Fast) Reset() { + f.max = infNeg + f.min = infPos + f.count = 0 + if len(f.a) > 0 { + f.a = f.a[:0] + f.tmp = f.tmp[:0] + } else { + // Free up memory occupied by unused histogram. + f.a = nil + f.tmp = nil + } +} + +// Update updates the f with v. +func (f *Fast) Update(v float64) { + if v > f.max { + f.max = v + } + if v < f.min { + f.min = v + } + + f.count++ + if len(f.a) < maxSamples { + f.a = append(f.a, v) + return + } + if n := int(f.rng.Uint32n(uint32(f.count))); n < len(f.a) { + f.a[n] = v + } +} + +const maxSamples = 1000 + +// Quantile returns the quantile value for the given phi. +func (f *Fast) Quantile(phi float64) float64 { + f.tmp = append(f.tmp[:0], f.a...) + sort.Float64s(f.tmp) + return f.quantile(phi) +} + +// Quantiles appends quantile values to dst for the given phis. +func (f *Fast) Quantiles(dst, phis []float64) []float64 { + f.tmp = append(f.tmp[:0], f.a...) + sort.Float64s(f.tmp) + for _, phi := range phis { + q := f.quantile(phi) + dst = append(dst, q) + } + return dst +} + +func (f *Fast) quantile(phi float64) float64 { + if len(f.tmp) == 0 || math.IsNaN(phi) { + return nan + } + if phi <= 0 { + return f.min + } + if phi >= 1 { + return f.max + } + idx := uint(phi*float64(len(f.tmp)-1) + 0.5) + if idx >= uint(len(f.tmp)) { + idx = uint(len(f.tmp) - 1) + } + return f.tmp[idx] +} + +// GetFast returns a histogram from a pool. +func GetFast() *Fast { + v := fastPool.Get() + if v == nil { + return NewFast() + } + return v.(*Fast) +} + +// PutFast puts hf to the pool. +// +// hf cannot be used after this call. +func PutFast(f *Fast) { + f.Reset() + fastPool.Put(f) +} + +var fastPool sync.Pool diff --git a/vendor/github.com/valyala/histogram/histogram_test.go b/vendor/github.com/valyala/histogram/histogram_test.go new file mode 100644 index 0000000..d1b5a12 --- /dev/null +++ b/vendor/github.com/valyala/histogram/histogram_test.go @@ -0,0 +1,59 @@ +package histogram + +import ( + "math" + "testing" +) + +func TestFastUnderflow(t *testing.T) { + f := GetFast() + defer PutFast(f) + + q := f.Quantile(0.5) + if !math.IsNaN(q) { + t.Fatalf("unexpected quantile for empty histogram; got %v; want %v", q, nan) + } + + for i := 0; i < maxSamples; i++ { + f.Update(float64(i)) + } + qs := f.Quantiles(nil, []float64{0, 0.5, 1}) + if qs[0] != 0 { + t.Fatalf("unexpected quantile value for phi=0; got %v; want %v", qs[0], 0) + } + if qs[1] != maxSamples/2 { + t.Fatalf("unexpected quantile value for phi=0.5; got %v; want %v", qs[1], maxSamples/2) + } + if qs[2] != maxSamples-1 { + t.Fatalf("unexpected quantile value for phi=1; got %v; want %v", qs[2], maxSamples-1) + } +} + +func TestFastOverflow(t *testing.T) { + f := GetFast() + defer PutFast(f) + + for i := 0; i < maxSamples*10; i++ { + f.Update(float64(i)) + } + qs := f.Quantiles(nil, []float64{0, 0.5, 0.9999, 1}) + if qs[0] != 0 { + t.Fatalf("unexpected quantile value for phi=0; got %v; want %v", qs[0], 0) + } + + median := float64(maxSamples*10-1) / 2 + if qs[1] < median*0.9 || qs[1] > median*1.1 { + t.Fatalf("unexpected quantile value for phi=0.5; got %v; want %v", qs[1], median) + } + if qs[2] < maxSamples*10*0.9 { + t.Fatalf("unexpected quantile value for phi=0.9999; got %v; want %v", qs[2], maxSamples*10*0.9) + } + if qs[3] != maxSamples*10-1 { + t.Fatalf("unexpected quantile value for phi=1; got %v; want %v", qs[3], maxSamples*10-1) + } + + q := f.Quantile(nan) + if !math.IsNaN(q) { + t.Fatalf("unexpected value for phi=NaN; got %v; want %v", q, nan) + } +} diff --git a/vendor/github.com/valyala/histogram/histogram_timing_test.go b/vendor/github.com/valyala/histogram/histogram_timing_test.go new file mode 100644 index 0000000..a52caae --- /dev/null +++ b/vendor/github.com/valyala/histogram/histogram_timing_test.go @@ -0,0 +1,25 @@ +package histogram + +import ( + "sync" + "testing" +) + +func BenchmarkFastUpdate(b *testing.B) { + b.ReportAllocs() + b.SetBytes(1) + b.RunParallel(func(pb *testing.PB) { + f := NewFast() + var v float64 + for pb.Next() { + f.Update(v) + v += 1.5 + } + SinkLock.Lock() + Sink += f.Quantile(0.5) + SinkLock.Unlock() + }) +} + +var Sink float64 +var SinkLock sync.Mutex diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/.travis.yml b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/.travis.yml new file mode 100644 index 0000000..336ccde --- /dev/null +++ b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/.travis.yml @@ -0,0 +1,16 @@ +language: go + +go: + - 1.7 + - 1.8 + +script: + # build test for supported platforms + - GOOS=linux go build + - GOOS=darwin go build + - GOOS=freebsd go build + - GOARCH=386 go build + + # run tests on a standard platform + - go test -v ./... + diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/LICENSE b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/LICENSE new file mode 100644 index 0000000..a2b05f6 --- /dev/null +++ b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Aliaksandr Valialkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/README.md b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/README.md new file mode 100644 index 0000000..3d384c6 --- /dev/null +++ b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/README.md @@ -0,0 +1,76 @@ +[![Build Status](https://travis-ci.org/valyala/fastrand.svg)](https://travis-ci.org/valyala/fastrand) +[![GoDoc](https://godoc.org/github.com/valyala/fastrand?status.svg)](http://godoc.org/github.com/valyala/fastrand) +[![Go Report](https://goreportcard.com/badge/github.com/valyala/fastrand)](https://goreportcard.com/report/github.com/valyala/fastrand) + + +# fastrand + +Fast pseudorandom number generator. + + +# Features + +- Optimized for speed. +- Performance scales on multiple CPUs. + +# How does it work? + +It abuses [sync.Pool](https://golang.org/pkg/sync/#Pool) for maintaining +"per-CPU" pseudorandom number generators. + +TODO: firgure out how to use real per-CPU pseudorandom number generators. + + +# Benchmark results + + +``` +$ GOMAXPROCS=1 go test -bench=. github.com/valyala/fastrand +goos: linux +goarch: amd64 +pkg: github.com/valyala/fastrand +BenchmarkUint32n 50000000 29.7 ns/op +BenchmarkRNGUint32n 200000000 6.50 ns/op +BenchmarkRNGUint32nWithLock 100000000 21.5 ns/op +BenchmarkMathRandInt31n 50000000 31.8 ns/op +BenchmarkMathRandRNGInt31n 100000000 17.9 ns/op +BenchmarkMathRandRNGInt31nWithLock 50000000 30.2 ns/op +PASS +ok github.com/valyala/fastrand 10.634s +``` + +``` +$ GOMAXPROCS=2 go test -bench=. github.com/valyala/fastrand +goos: linux +goarch: amd64 +pkg: github.com/valyala/fastrand +BenchmarkUint32n-2 100000000 17.6 ns/op +BenchmarkRNGUint32n-2 500000000 3.36 ns/op +BenchmarkRNGUint32nWithLock-2 50000000 32.0 ns/op +BenchmarkMathRandInt31n-2 20000000 51.2 ns/op +BenchmarkMathRandRNGInt31n-2 100000000 11.0 ns/op +BenchmarkMathRandRNGInt31nWithLock-2 20000000 91.0 ns/op +PASS +ok github.com/valyala/fastrand 9.543s +``` + +``` +$ GOMAXPROCS=4 go test -bench=. github.com/valyala/fastrand +goos: linux +goarch: amd64 +pkg: github.com/valyala/fastrand +BenchmarkUint32n-4 100000000 14.2 ns/op +BenchmarkRNGUint32n-4 500000000 3.30 ns/op +BenchmarkRNGUint32nWithLock-4 20000000 88.7 ns/op +BenchmarkMathRandInt31n-4 10000000 145 ns/op +BenchmarkMathRandRNGInt31n-4 200000000 8.35 ns/op +BenchmarkMathRandRNGInt31nWithLock-4 20000000 102 ns/op +PASS +ok github.com/valyala/fastrand 11.534s +``` + +As you can see, [fastrand.Uint32n](https://godoc.org/github.com/valyala/fastrand#Uint32n) +scales on multiple CPUs, while [rand.Int31n](https://golang.org/pkg/math/rand/#Int31n) +doesn't scale. Their performance is comparable on `GOMAXPROCS=1`, +but `fastrand.Uint32n` runs 3x faster than `rand.Int31n` on `GOMAXPROCS=2` +and 10x faster than `rand.Int31n` on `GOMAXPROCS=4`. diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/fastrand.go b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/fastrand.go new file mode 100644 index 0000000..3ea9177 --- /dev/null +++ b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/fastrand.go @@ -0,0 +1,74 @@ +// Package fastrand implements fast pesudorandom number generator +// that should scale well on multi-CPU systems. +// +// Use crypto/rand instead of this package for generating +// cryptographically secure random numbers. +package fastrand + +import ( + "sync" + "time" +) + +// Uint32 returns pseudorandom uint32. +// +// It is safe calling this function from concurrent goroutines. +func Uint32() uint32 { + v := rngPool.Get() + if v == nil { + v = &RNG{} + } + r := v.(*RNG) + x := r.Uint32() + rngPool.Put(r) + return x +} + +var rngPool sync.Pool + +// Uint32n returns pseudorandom uint32 in the range [0..maxN). +// +// It is safe calling this function from concurrent goroutines. +func Uint32n(maxN uint32) uint32 { + x := Uint32() + // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + return uint32((uint64(x) * uint64(maxN)) >> 32) +} + +// RNG is a pseudorandom number generator. +// +// It is unsafe to call RNG methods from concurrent goroutines. +type RNG struct { + x uint32 +} + +// Uint32 returns pseudorandom uint32. +// +// It is unsafe to call this method from concurrent goroutines. +func (r *RNG) Uint32() uint32 { + for r.x == 0 { + r.x = getRandomUint32() + } + + // See https://en.wikipedia.org/wiki/Xorshift + x := r.x + x ^= x << 13 + x ^= x >> 17 + x ^= x << 5 + r.x = x + return x +} + +// Uint32n returns pseudorandom uint32 in the range [0..maxN). +// +// It is unsafe to call this method from concurrent goroutines. +func (r *RNG) Uint32n(maxN uint32) uint32 { + x := r.Uint32() + // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + return uint32((uint64(x) * uint64(maxN)) >> 32) +} + +func getRandomUint32() uint32 { + x := time.Now().UnixNano() + return uint32((x >> 32) ^ x) +} diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/go.mod b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/go.mod new file mode 100644 index 0000000..958910b --- /dev/null +++ b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/go.mod @@ -0,0 +1 @@ +module github.com/valyala/fastrand diff --git a/vendor/github.com/valyala/histogram/vendor/modules.txt b/vendor/github.com/valyala/histogram/vendor/modules.txt new file mode 100644 index 0000000..9871e30 --- /dev/null +++ b/vendor/github.com/valyala/histogram/vendor/modules.txt @@ -0,0 +1,2 @@ +# github.com/valyala/fastrand v1.0.0 +github.com/valyala/fastrand diff --git a/walletapi/daemon_communication.go b/walletapi/daemon_communication.go index 3e75fcc..9dde1f2 100644 --- a/walletapi/daemon_communication.go +++ b/walletapi/daemon_communication.go @@ -157,7 +157,7 @@ func (w *Wallet_Memory) Sync_Wallet_Memory_With_Daemon() { rlog.Debugf("wallet topo height %d daemon online topo height %d\n", w.account.TopoHeight, w.Daemon_TopoHeight) previous := w.account.Balance_Result.Data var scid crypto.Hash - if _, _, err := w.GetEncryptedBalanceAtTopoHeight(scid, -1, w.GetAddress().String()); err == nil { + if _, _,_,_,_, err := w.GetEncryptedBalanceAtTopoHeight(scid, -1, w.GetAddress().String()); err == nil { if w.account.Balance_Result.Data != previous /*|| (len(w.account.EntriesNative[scid]) >= 1 && strings.ToLower(w.account.Balance_Result.Data) != strings.ToLower(w.account.EntriesNative[scid][len(w.account.EntriesNative[scid])-1].EWData)) */ { w.DecodeEncryptedBalance() // try to decode balance @@ -240,7 +240,7 @@ func (w *Wallet_Memory) DecodeEncryptedBalanceNow(el *crypto.ElGamal) uint64 { // TODO in order to stop privacy leaks we must guess this information somehow on client side itself // maybe the server can broadcast a bloomfilter or something else from the mempool keyimages // -func (w *Wallet_Memory) GetEncryptedBalanceAtTopoHeight(scid crypto.Hash, topoheight int64, accountaddr string) (bits int, e *crypto.ElGamal, err error) { +func (w *Wallet_Memory) GetEncryptedBalanceAtTopoHeight(scid crypto.Hash, topoheight int64, accountaddr string) (bits int, e *crypto.ElGamal, height,rtopoheight uint64, merkleroot crypto.Hash, err error) { defer func() { if r := recover(); r != nil { @@ -300,7 +300,7 @@ func (w *Wallet_Memory) GetEncryptedBalanceAtTopoHeight(scid crypto.Hash, topohe } } - // fmt.Printf("status '%s' err '%s' %+v %+v \n", result.Status , w.Error , result.Status == errormsg.ErrAccountUnregistered.Error() , accountaddr == w.account.GetAddress().String()) +// fmt.Printf("status '%s' err '%s' %+v %+v \n", result.Status , w.Error , result.Status == errormsg.ErrAccountUnregistered.Error() , accountaddr == w.account.GetAddress().String()) if scid.IsZero() && result.Status == errormsg.ErrAccountUnregistered.Error() { err = fmt.Errorf("%s", result.Status) @@ -311,7 +311,7 @@ func (w *Wallet_Memory) GetEncryptedBalanceAtTopoHeight(scid crypto.Hash, topohe w.Daemon_TopoHeight = result.DTopoheight w.Merkle_Balance_TreeHash = result.DMerkle_Balance_TreeHash - if scid.IsZero() && accountaddr == w.GetAddress().String() { + if topoheight == -1 && scid.IsZero() && accountaddr == w.GetAddress().String() { w.account.Balance_Result = result w.account.TopoHeight = result.Topoheight } @@ -330,10 +330,17 @@ func (w *Wallet_Memory) GetEncryptedBalanceAtTopoHeight(scid crypto.Hash, topohe w.Error = nil } - //fmt.Printf("decoding elgamal\n") - el := new(crypto.ElGamal).Deserialize(hexdecoded) - return result.Bits, el, nil + + hexdecoded, err = hex.DecodeString(result.Merkle_Balance_TreeHash) + if err != nil { + return + } + + var mhash crypto.Hash + copy(mhash[:],hexdecoded[:]) + + return result.Bits, el, uint64(result.Height), uint64(result.Topoheight), mhash, nil } func (w *Wallet_Memory) DecodeEncryptedBalance_Memory(el *crypto.ElGamal, hint uint64) (balance uint64) { @@ -346,7 +353,7 @@ func (w *Wallet_Memory) DecodeEncryptedBalance_Memory(el *crypto.ElGamal, hint u } func (w *Wallet_Memory) GetDecryptedBalanceAtTopoHeight(scid crypto.Hash, topoheight int64, accountaddr string) (balance uint64, err error) { - _, encrypted_balance, err := w.GetEncryptedBalanceAtTopoHeight(scid, topoheight, accountaddr) + _, encrypted_balance,_,_,_, err := w.GetEncryptedBalanceAtTopoHeight(scid, topoheight, accountaddr) if err != nil { return 0, err } @@ -466,13 +473,13 @@ func (w *Wallet_Memory) synchistory_internal(scid crypto.Hash, start_topo, end_t if start_topo == w.account.Balance_Result.Registration { start_balance_e = crypto.ConstructElGamal(w.account.Keys.Public.G1(), crypto.ElGamal_BASE_G) } else { - _, start_balance_e, err = w.GetEncryptedBalanceAtTopoHeight(scid, start_topo, w.GetAddress().String()) + _, start_balance_e,_,_,_, err = w.GetEncryptedBalanceAtTopoHeight(scid, start_topo, w.GetAddress().String()) if err != nil { return err } } - _, end_balance_e, err := w.GetEncryptedBalanceAtTopoHeight(scid, end_topo, w.GetAddress().String()) + _, end_balance_e,_,_,_, err := w.GetEncryptedBalanceAtTopoHeight(scid, end_topo, w.GetAddress().String()) if err != nil { return err } @@ -512,7 +519,7 @@ func (w *Wallet_Memory) synchistory_internal_binary_search(scid crypto.Hash, sta return w.synchistory_block(scid, end_topo) } - _, median_balance_e, err := w.GetEncryptedBalanceAtTopoHeight(scid, median, w.GetAddress().String()) + _, median_balance_e,_,_,_, err := w.GetEncryptedBalanceAtTopoHeight(scid, median, w.GetAddress().String()) if err != nil { return err } @@ -599,13 +606,13 @@ func (w *Wallet_Memory) synchistory_block(scid crypto.Hash, topo int64) (err err if topo <= 0 || w.account.Balance_Result.Registration == topo { previous_balance_e = crypto.ConstructElGamal(w.account.Keys.Public.G1(), crypto.ElGamal_BASE_G) } else { - _, previous_balance_e, err = w.GetEncryptedBalanceAtTopoHeight(scid, topo-1, w.GetAddress().String()) + _, previous_balance_e,_,_,_, err = w.GetEncryptedBalanceAtTopoHeight(scid, topo-1, w.GetAddress().String()) if err != nil { return err } } - _, current_balance_e, err = w.GetEncryptedBalanceAtTopoHeight(scid, topo, w.GetAddress().String()) + _, current_balance_e,_,_,_, err = w.GetEncryptedBalanceAtTopoHeight(scid, topo, w.GetAddress().String()) if err != nil { return err } @@ -627,6 +634,10 @@ func (w *Wallet_Memory) synchistory_block(scid crypto.Hash, topo int64) (err err return fmt.Errorf("getblock rpc failed") } + if bresult.Block_Header.SideBlock { // skip side blocks + return nil + } + block_bin, _ := hex.DecodeString(bresult.Blob) bl.Deserialize(block_bin) diff --git a/walletapi/transaction_build.go b/walletapi/transaction_build.go index 1a05f0f..dfa1f6b 100644 --- a/walletapi/transaction_build.go +++ b/walletapi/transaction_build.go @@ -2,16 +2,16 @@ package walletapi import "fmt" import "math/big" - -//import "encoding/binary" import mathrand "math/rand" -import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/globals" +import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/transaction" import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/cryptography/bn256" // generate proof etc +// we use a previous point in history and cryptographically prove that we have not used the funds till now func (w *Wallet_Memory) BuildTransaction(transfers []rpc.Transfer, emap map[string]map[string][]byte, rings [][]*bn256.G1, height uint64, scdata rpc.Arguments, roothash []byte, max_bits_array []int) *transaction.Transaction { var tx transaction.Transaction @@ -22,6 +22,11 @@ func (w *Wallet_Memory) BuildTransaction(transfers []rpc.Transfer, emap map[stri tx.Version = 1 tx.Height = height tx.TransactionType = transaction.NORMAL + + + if height % config.BLOCK_BATCH_SIZE != 0 { + panic(fmt.Sprintf("Height must be a multiple of %d (config.BLOCK_BATCH_SIZE)", config.BLOCK_BATCH_SIZE)) + } /* if burn_value >= 1 { tx.TransactionType = transaction.BURN_TX @@ -217,9 +222,12 @@ func (w *Wallet_Memory) BuildTransaction(transfers []rpc.Transfer, emap map[stri } - u := new(bn256.G1).ScalarMult(crypto.HashToPoint(crypto.HashtoNumber(append([]byte(crypto.PROTOCOL_CONSTANT), tx.Payloads[0].Statement.Roothash[:]...))), sender_secret) // this should be moved to generate proof + + + u := new(bn256.G1).ScalarMult(crypto.HeightToPoint(height), sender_secret) // this should be moved to generate proof + u1 := new(bn256.G1).ScalarMult(crypto.HeightToPoint(height + config.BLOCK_BATCH_SIZE), sender_secret) // this should be moved to generate proof for t := range transfers { - tx.Payloads[t].Proof = crypto.GenerateProof(&tx.Payloads[t].Statement, &witness_list[t], u, tx.GetHash(), tx.Payloads[t].BurnValue) + tx.Payloads[t].Proof = crypto.GenerateProof(&tx.Payloads[t].Statement, &witness_list[t], u,u1, height, tx.GetHash(), tx.Payloads[t].BurnValue) } // after the tx is serialized, it loses information which is then fed by blockchain @@ -227,8 +235,8 @@ func (w *Wallet_Memory) BuildTransaction(transfers []rpc.Transfer, emap map[stri //fmt.Printf("txhash before %s\n", tx.GetHash()) for t := range tx.Payloads { - if tx.Payloads[t].Proof.Verify(&tx.Payloads[t].Statement, tx.GetHash(), tx.Payloads[t].BurnValue) { - //fmt.Printf("TX verified with proof successfuly %s burn_value %d\n", tx.GetHash(), tx.Payloads[t].BurnValue) + if tx.Payloads[t].Proof.Verify(&tx.Payloads[t].Statement, tx.GetHash(), height, tx.Payloads[t].BurnValue) { + fmt.Printf("TX verified with proof successfuly %s burn_value %d\n", tx.GetHash(), tx.Payloads[t].BurnValue) //fmt.Printf("Statement %+v\n", tx.Payloads[t].Statement) //fmt.Printf("Proof %+v\n", tx.Payloads[t].Proof) diff --git a/walletapi/wallet_pool.go b/walletapi/wallet_pool.go index e8b3e73..0a9c23a 100644 --- a/walletapi/wallet_pool.go +++ b/walletapi/wallet_pool.go @@ -29,7 +29,7 @@ import "github.com/romana/rlog" //import "github.com/vmihailenco/msgpack" -//import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/cryptography/crypto" //import "github.com/deroproject/derohe/crypto/ringct" @@ -121,6 +121,7 @@ func (w *Wallet_Memory) PoolTransfer(transfers []rpc.Transfer, scdata rpc.Argume entry.Transfer_Everything = transfer_all entry.Trigger_Height = int64(w.Daemon_Height) + defer w.processPool(false) w.account.Lock() defer w.account.Unlock() w.account.Pool = append(w.account.Pool, entry) @@ -219,9 +220,9 @@ func (w *Wallet_Memory) processPool(checkonly bool) error { return fmt.Errorf("gettransa rpc failed err %s", err) } - if tx_result.Txs_as_hex[0] == "" { + if tx_result.Txs_as_hex[0] == "" { // we need to work this code better try.Status = "Lost (not in mempool/chain), Waiting for more blocks to retry" - if try.Height < (info.StableHeight + 1) { // we have attempted, lets wait some blocks, this needs to be optimized, for instant transfer + if try.Height + 3*config.BLOCK_BATCH_SIZE < (info.StableHeight + 1) { // we have attempted, lets wait some blocks, this needs to be optimized, for instant transfer // we need to send this tx again now }else{ continue // try other txs @@ -229,18 +230,27 @@ func (w *Wallet_Memory) processPool(checkonly bool) error { } else if tx_result.Txs[0].In_pool { try.Status = "TX in Mempool" continue - } else if tx_result.Txs[0].ValidBlock != "" { // if the result is valid in one of the blocks - try.Status = fmt.Sprintf("Mined in %s (%d confirmations)", tx_result.Txs[0].ValidBlock, info.TopoHeight-tx_result.Txs[0].Block_Height) - if try.Height < (info.StableHeight + 1) { // successful confirmation + } else if len(tx_result.Txs[0].MinedBlock) >= 1 { // if the result tx has been mined in one of the blocks + + + var bl_result rpc.GetBlockHeaderByHeight_Result + // Issue a call with a response. + if err := rpc_client.Call("DERO.GetBlockHeaderByHash", rpc.GetBlockHeaderByHash_Params{Hash: tx_result.Txs[0].MinedBlock[0]}, &bl_result); err != nil { + rlog.Errorf("GetBlockHeaderByTopoHeight Call failed: %v", err) + return err + } + + try.Status = fmt.Sprintf("Mined in %s (%d confirmations)", tx_result.Txs[0].MinedBlock[0], info.TopoHeight-bl_result.Block_Header.TopoHeight) + if try.Height + 2*config.BLOCK_BATCH_SIZE < (info.StableHeight + 1) { // successful confirmation w.account.PoolHistory = append(w.account.PoolHistory, w.account.Pool[i]) rlog.Infof("tx %s confirmed successfully at stableheight %d height %d trigger_height %d\n", try.TXID.String(), info.StableHeight, try.Height, w.account.Pool[i].Trigger_Height) w.account.Pool = append(w.account.Pool[:i], w.account.Pool[i+1:]...) i-- // so another element at same place gets used } continue - } else { - try.Status = fmt.Sprintf("Mined in sideblock (%d confirmations, waiting for more blocks)", info.Height-try.Height) - if try.Height < (info.StableHeight + 1) { // we have attempted, lets wait some blocks, this needs to be optimized, for instant transfer + } else { // tx may not be in pool and has not been mined, so we wait for couple of blocks + try.Status = fmt.Sprintf("unknown state (waiting for some more blocks)") + if try.Height + 2*config.BLOCK_BATCH_SIZE < (info.StableHeight + 1) { // we have attempted, lets wait some blocks, this needs to be optimized, for instant transfer // we need to send this tx again now }else{ continue // try other txs diff --git a/walletapi/wallet_transfer.go b/walletapi/wallet_transfer.go index 19a68b1..29e5183 100644 --- a/walletapi/wallet_transfer.go +++ b/walletapi/wallet_transfer.go @@ -23,7 +23,7 @@ import "fmt" //import cryptorand "crypto/rand" //import "encoding/binary" -import "encoding/hex" +//import "encoding/hex" //import "encoding/json" @@ -153,7 +153,7 @@ func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, transfer_all var rings [][]*bn256.G1 var max_bits_array []int - _, self_e, _ := w.GetEncryptedBalanceAtTopoHeight(transfers[0].SCID, -1, w.GetAddress().String()) + _, self_e,height,topoheight,treehash, err := w.GetEncryptedBalanceAtTopoHeight(transfers[0].SCID, rpc.RECENT_BATCH_BLOCK, w.GetAddress().String()) if err != nil { fmt.Printf("self unregistered err %s\n", err) return @@ -162,17 +162,9 @@ func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, transfer_all // WaitNewHeightBlock() // wait till a new block at new height is found // due to this we weill dispatch a new tx immediate after a block is found for better propagation - height := w.Daemon_Height - treehash := w.Merkle_Balance_TreeHash +// height := w.Daemon_Height +// treehash := w.Merkle_Balance_TreeHash - treehash_raw, err := hex.DecodeString(treehash) - if err != nil { - return - } - if len(treehash_raw) != 32 { - err = fmt.Errorf("roothash is not of 32 bytes, probably daemon corruption '%s'", treehash) - return - } for t := range transfers { @@ -187,7 +179,7 @@ func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, transfer_all bits_needed := make([]int, ringsize, ringsize) - bits_needed[0], self_e, err = w.GetEncryptedBalanceAtTopoHeight(transfers[t].SCID, -1, w.GetAddress().String()) + bits_needed[0], self_e,_,_,_, err = w.GetEncryptedBalanceAtTopoHeight(transfers[t].SCID, int64(topoheight), w.GetAddress().String()) if err != nil { fmt.Printf("self unregistered err %s\n", err) return @@ -201,7 +193,7 @@ func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, transfer_all return } var dest_e *crypto.ElGamal - bits_needed[1], dest_e, err = w.GetEncryptedBalanceAtTopoHeight(transfers[t].SCID, -1, addr.String()) + bits_needed[1], dest_e,_,_,_, err = w.GetEncryptedBalanceAtTopoHeight(transfers[t].SCID, int64(topoheight), addr.String()) if err != nil { fmt.Printf(" t %d unregistered1 '%s' %s\n", t, addr, err) return @@ -224,7 +216,7 @@ func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, transfer_all // fmt.Printf("%s receiver %s sender %s\n", k, receiver_without_payment_id.String(), w.GetAddress().String()) var ebal *crypto.ElGamal var addr *rpc.Address - bits_needed[len(ring_members_keys)], ebal, err = w.GetEncryptedBalanceAtTopoHeight(transfers[t].SCID, -1, k) + bits_needed[len(ring_members_keys)], ebal,_,_,_, err = w.GetEncryptedBalanceAtTopoHeight(transfers[t].SCID, int64(topoheight), k) if err != nil { fmt.Printf(" unregistered %s\n", k) return @@ -262,7 +254,7 @@ func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, transfer_all if !dry_run { rlog.Debugf("we should build a TX now") - tx = w.BuildTransaction(transfers, emap, rings, height, scdata, treehash_raw, max_bits_array) + tx = w.BuildTransaction(transfers, emap, rings, height, scdata, treehash[:], max_bits_array) } return