Merge branch 'master' into swarm-network-rewrite

# Conflicts:
#	swarm/api/api.go
#	swarm/api/http/error.go
#	swarm/api/http/server.go
#	swarm/network/depo.go
#	swarm/network/hive.go
#	swarm/network/kademlia/kademlia.go
#	swarm/network/protocol.go
#	swarm/storage/dbstore.go
#	swarm/storage/localstore.go
#	swarm/storage/memstore.go
#	swarm/swarm.go
This commit is contained in:
Balint Gabor 2018-02-26 13:25:47 +01:00
commit 167c995f9d
122 changed files with 8719 additions and 596 deletions

View file

@ -428,10 +428,23 @@ func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumb
} }
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
} }
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil
} }
func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
receipts := core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash))
if receipts == nil {
return nil, nil
}
logs := make([][]*types.Log, len(receipts))
for i, receipt := range receipts {
logs[i] = receipt.Logs
}
return logs, nil
}
func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription {
return event.NewSubscription(func(quit <-chan struct{}) error { return event.NewSubscription(func(quit <-chan struct{}) error {
<-quit <-quit

View file

@ -631,6 +631,7 @@ func deployDashboard(client *sshClient, network string, conf *config, config *da
"Tangerine": conf.Genesis.Config.EIP150Block, "Tangerine": conf.Genesis.Config.EIP150Block,
"Spurious": conf.Genesis.Config.EIP155Block, "Spurious": conf.Genesis.Config.EIP155Block,
"Byzantium": conf.Genesis.Config.ByzantiumBlock, "Byzantium": conf.Genesis.Config.ByzantiumBlock,
"Constantinople": conf.Genesis.Config.ConstantinopleBlock,
}) })
files[filepath.Join(workdir, "index.html")] = indexfile.Bytes() files[filepath.Join(workdir, "index.html")] = indexfile.Bytes()

View file

@ -43,6 +43,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/swarm" "github.com/ethereum/go-ethereum/swarm"
bzzapi "github.com/ethereum/go-ethereum/swarm/api" bzzapi "github.com/ethereum/go-ethereum/swarm/api"
swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
) )
@ -398,9 +399,14 @@ DEPRECATED: use 'swarm db clean'.
} }
app.Flags = append(app.Flags, rpcFlags...) app.Flags = append(app.Flags, rpcFlags...)
app.Flags = append(app.Flags, debug.Flags...) app.Flags = append(app.Flags, debug.Flags...)
app.Flags = append(app.Flags, swarmmetrics.Flags...)
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
return debug.Setup(ctx) if err := debug.Setup(ctx); err != nil {
return err
}
swarmmetrics.Setup(ctx)
return nil
} }
app.After = func(ctx *cli.Context) error { app.After = func(ctx *cli.Context) error {
debug.Exit() debug.Exit()

View file

@ -25,6 +25,6 @@ var (
Big3 = big.NewInt(3) Big3 = big.NewInt(3)
Big0 = big.NewInt(0) Big0 = big.NewInt(0)
Big32 = big.NewInt(32) Big32 = big.NewInt(32)
Big256 = big.NewInt(0xff) Big256 = big.NewInt(256)
Big257 = big.NewInt(257) Big257 = big.NewInt(257)
) )

View file

@ -35,9 +35,9 @@ import (
mmap "github.com/edsrzf/mmap-go" mmap "github.com/edsrzf/mmap-go"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/hashicorp/golang-lru/simplelru" "github.com/hashicorp/golang-lru/simplelru"
metrics "github.com/rcrowley/go-metrics"
) )
var ErrInvalidDumpMagic = errors.New("invalid dump magic") var ErrInvalidDumpMagic = errors.New("invalid dump magic")

View file

@ -46,7 +46,7 @@ import (
) )
var ( var (
blockInsertTimer = metrics.NewTimer("chain/inserts") blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
ErrNoGenesis = errors.New("Genesis not found in chain") ErrNoGenesis = errors.New("Genesis not found in chain")
) )
@ -648,22 +648,21 @@ func (bc *BlockChain) Stop() {
bc.wg.Wait() bc.wg.Wait()
// Ensure the state of a recent block is also stored to disk before exiting. // Ensure the state of a recent block is also stored to disk before exiting.
// It is fine if this state does not exist (fast start/stop cycle), but it is // We're writing three different states to catch different restart scenarios:
// advisable to leave an N block gap from the head so 1) a restart loads up // - HEAD: So we don't need to reprocess any blocks in the general case
// the last N blocks as sync assistance to remote nodes; 2) a restart during // - HEAD-1: So we don't do large reorgs if our HEAD becomes an uncle
// a (small) reorg doesn't require deep reprocesses; 3) chain "repair" from // - HEAD-127: So we have a hard limit on the number of blocks reexecuted
// missing states are constantly tested.
//
// This may be tuned a bit on mainnet if its too annoying to reprocess the last
// N blocks.
if !bc.cacheConfig.Disabled { if !bc.cacheConfig.Disabled {
triedb := bc.stateCache.TrieDB() triedb := bc.stateCache.TrieDB()
if number := bc.CurrentBlock().NumberU64(); number >= triesInMemory {
recent := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - triesInMemory + 1)
log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root()) for _, offset := range []uint64{0, 1, triesInMemory - 1} {
if err := triedb.Commit(recent.Root(), true); err != nil { if number := bc.CurrentBlock().NumberU64(); number > offset {
log.Error("Failed to commit recent state trie", "err", err) recent := bc.GetBlockByNumber(number - offset)
log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root())
if err := triedb.Commit(recent.Root(), true); err != nil {
log.Error("Failed to commit recent state trie", "err", err)
}
} }
} }
for !bc.triegc.Empty() { for !bc.triegc.Empty() {

View file

@ -34,26 +34,6 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
// newTestBlockChain creates a blockchain without validation.
func newTestBlockChain(fake bool) *BlockChain {
db, _ := ethdb.NewMemDatabase()
gspec := &Genesis{
Config: params.TestChainConfig,
Difficulty: big.NewInt(1),
}
gspec.MustCommit(db)
engine := ethash.NewFullFaker()
if !fake {
engine = ethash.NewTester()
}
blockchain, err := NewBlockChain(db, nil, gspec.Config, engine, vm.Config{})
if err != nil {
panic(err)
}
blockchain.SetValidator(bproc{})
return blockchain
}
// Test fork of length N starting from block i // Test fork of length N starting from block i
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) { func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
// Copy old chain up to #i into a new db // Copy old chain up to #i into a new db
@ -183,13 +163,18 @@ func insertChain(done chan bool, blockchain *BlockChain, chain types.Blocks, t *
} }
func TestLastBlock(t *testing.T) { func TestLastBlock(t *testing.T) {
bchain := newTestBlockChain(false) _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true)
defer bchain.Stop() if err != nil {
t.Fatalf("failed to create pristine chain: %v", err)
}
defer blockchain.Stop()
block := makeBlockChain(bchain.CurrentBlock(), 1, ethash.NewFaker(), bchain.db, 0)[0] blocks := makeBlockChain(blockchain.CurrentBlock(), 1, ethash.NewFullFaker(), blockchain.db, 0)
bchain.insert(block) if _, err := blockchain.InsertChain(blocks); err != nil {
if block.Hash() != GetHeadBlockHash(bchain.db) { t.Fatalf("Failed to insert block: %v", err)
t.Errorf("Write/Get HeadBlockHash failed") }
if blocks[len(blocks)-1].Hash() != GetHeadBlockHash(blockchain.db) {
t.Fatalf("Write/Get HeadBlockHash failed")
} }
} }
@ -337,55 +322,13 @@ func testBrokenChain(t *testing.T, full bool) {
} }
} }
type bproc struct{}
func (bproc) ValidateBody(*types.Block) error { return nil }
func (bproc) ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64) error {
return nil
}
func (bproc) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
return nil, nil, 0, nil
}
func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header {
blocks := makeBlockChainWithDiff(genesis, d, seed)
headers := make([]*types.Header, len(blocks))
for i, block := range blocks {
headers[i] = block.Header()
}
return headers
}
func makeBlockChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block {
var chain []*types.Block
for i, difficulty := range d {
header := &types.Header{
Coinbase: common.Address{seed},
Number: big.NewInt(int64(i + 1)),
Difficulty: big.NewInt(int64(difficulty)),
UncleHash: types.EmptyUncleHash,
TxHash: types.EmptyRootHash,
ReceiptHash: types.EmptyRootHash,
Time: big.NewInt(int64(i) + 1),
}
if i == 0 {
header.ParentHash = genesis.Hash()
} else {
header.ParentHash = chain[i-1].Hash()
}
block := types.NewBlockWithHeader(header)
chain = append(chain, block)
}
return chain
}
// Tests that reorganising a long difficult chain after a short easy one // Tests that reorganising a long difficult chain after a short easy one
// overwrites the canonical numbers and links in the database. // overwrites the canonical numbers and links in the database.
func TestReorgLongHeaders(t *testing.T) { testReorgLong(t, false) } func TestReorgLongHeaders(t *testing.T) { testReorgLong(t, false) }
func TestReorgLongBlocks(t *testing.T) { testReorgLong(t, true) } func TestReorgLongBlocks(t *testing.T) { testReorgLong(t, true) }
func testReorgLong(t *testing.T, full bool) { func testReorgLong(t *testing.T, full bool) {
testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10, full) testReorg(t, []int64{0, 0, -9}, []int64{0, 0, 0, -9}, 393280, full)
} }
// Tests that reorganising a short difficult chain after a long easy one // Tests that reorganising a short difficult chain after a long easy one
@ -394,45 +337,82 @@ func TestReorgShortHeaders(t *testing.T) { testReorgShort(t, false) }
func TestReorgShortBlocks(t *testing.T) { testReorgShort(t, true) } func TestReorgShortBlocks(t *testing.T) { testReorgShort(t, true) }
func testReorgShort(t *testing.T, full bool) { func testReorgShort(t *testing.T, full bool) {
testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11, full) // Create a long easy chain vs. a short heavy one. Due to difficulty adjustment
// we need a fairly long chain of blocks with different difficulties for a short
// one to become heavyer than a long one. The 96 is an empirical value.
easy := make([]int64, 96)
for i := 0; i < len(easy); i++ {
easy[i] = 60
}
diff := make([]int64, len(easy)-1)
for i := 0; i < len(diff); i++ {
diff[i] = -9
}
testReorg(t, easy, diff, 12615120, full)
} }
func testReorg(t *testing.T, first, second []int, td int64, full bool) { func testReorg(t *testing.T, first, second []int64, td int64, full bool) {
bc := newTestBlockChain(true) // Create a pristine chain and database
defer bc.Stop() db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
if err != nil {
t.Fatalf("failed to create pristine chain: %v", err)
}
defer blockchain.Stop()
// Insert an easy and a difficult chain afterwards // Insert an easy and a difficult chain afterwards
easyBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.CurrentBlock(), ethash.NewFaker(), db, len(first), func(i int, b *BlockGen) {
b.OffsetTime(first[i])
})
diffBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.CurrentBlock(), ethash.NewFaker(), db, len(second), func(i int, b *BlockGen) {
b.OffsetTime(second[i])
})
if full { if full {
bc.InsertChain(makeBlockChainWithDiff(bc.genesisBlock, first, 11)) if _, err := blockchain.InsertChain(easyBlocks); err != nil {
bc.InsertChain(makeBlockChainWithDiff(bc.genesisBlock, second, 22)) t.Fatalf("failed to insert easy chain: %v", err)
}
if _, err := blockchain.InsertChain(diffBlocks); err != nil {
t.Fatalf("failed to insert difficult chain: %v", err)
}
} else { } else {
bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, first, 11), 1) easyHeaders := make([]*types.Header, len(easyBlocks))
bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, second, 22), 1) for i, block := range easyBlocks {
easyHeaders[i] = block.Header()
}
diffHeaders := make([]*types.Header, len(diffBlocks))
for i, block := range diffBlocks {
diffHeaders[i] = block.Header()
}
if _, err := blockchain.InsertHeaderChain(easyHeaders, 1); err != nil {
t.Fatalf("failed to insert easy chain: %v", err)
}
if _, err := blockchain.InsertHeaderChain(diffHeaders, 1); err != nil {
t.Fatalf("failed to insert difficult chain: %v", err)
}
} }
// Check that the chain is valid number and link wise // Check that the chain is valid number and link wise
if full { if full {
prev := bc.CurrentBlock() prev := blockchain.CurrentBlock()
for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) { for block := blockchain.GetBlockByNumber(blockchain.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, blockchain.GetBlockByNumber(block.NumberU64()-1) {
if prev.ParentHash() != block.Hash() { if prev.ParentHash() != block.Hash() {
t.Errorf("parent block hash mismatch: have %x, want %x", prev.ParentHash(), block.Hash()) t.Errorf("parent block hash mismatch: have %x, want %x", prev.ParentHash(), block.Hash())
} }
} }
} else { } else {
prev := bc.CurrentHeader() prev := blockchain.CurrentHeader()
for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) { for header := blockchain.GetHeaderByNumber(blockchain.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, blockchain.GetHeaderByNumber(header.Number.Uint64()-1) {
if prev.ParentHash != header.Hash() { if prev.ParentHash != header.Hash() {
t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash()) t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash())
} }
} }
} }
// Make sure the chain total difficulty is the correct one // Make sure the chain total difficulty is the correct one
want := new(big.Int).Add(bc.genesisBlock.Difficulty(), big.NewInt(td)) want := new(big.Int).Add(blockchain.genesisBlock.Difficulty(), big.NewInt(td))
if full { if full {
if have := bc.GetTdByHash(bc.CurrentBlock().Hash()); have.Cmp(want) != 0 { if have := blockchain.GetTdByHash(blockchain.CurrentBlock().Hash()); have.Cmp(want) != 0 {
t.Errorf("total difficulty mismatch: have %v, want %v", have, want) t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
} }
} else { } else {
if have := bc.GetTdByHash(bc.CurrentHeader().Hash()); have.Cmp(want) != 0 { if have := blockchain.GetTdByHash(blockchain.CurrentHeader().Hash()); have.Cmp(want) != 0 {
t.Errorf("total difficulty mismatch: have %v, want %v", have, want) t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
} }
} }
@ -443,19 +423,28 @@ func TestBadHeaderHashes(t *testing.T) { testBadHashes(t, false) }
func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true) } func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true) }
func testBadHashes(t *testing.T, full bool) { func testBadHashes(t *testing.T, full bool) {
bc := newTestBlockChain(true) // Create a pristine chain and database
defer bc.Stop() db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
if err != nil {
t.Fatalf("failed to create pristine chain: %v", err)
}
defer blockchain.Stop()
// Create a chain, ban a hash and try to import // Create a chain, ban a hash and try to import
var err error
if full { if full {
blocks := makeBlockChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10) blocks := makeBlockChain(blockchain.CurrentBlock(), 3, ethash.NewFaker(), db, 10)
BadHashes[blocks[2].Header().Hash()] = true BadHashes[blocks[2].Header().Hash()] = true
_, err = bc.InsertChain(blocks) defer func() { delete(BadHashes, blocks[2].Header().Hash()) }()
_, err = blockchain.InsertChain(blocks)
} else { } else {
headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10) headers := makeHeaderChain(blockchain.CurrentHeader(), 3, ethash.NewFaker(), db, 10)
BadHashes[headers[2].Hash()] = true BadHashes[headers[2].Hash()] = true
_, err = bc.InsertHeaderChain(headers, 1) defer func() { delete(BadHashes, headers[2].Hash()) }()
_, err = blockchain.InsertHeaderChain(headers, 1)
} }
if err != ErrBlacklistedHash { if err != ErrBlacklistedHash {
t.Errorf("error mismatch: have: %v, want: %v", err, ErrBlacklistedHash) t.Errorf("error mismatch: have: %v, want: %v", err, ErrBlacklistedHash)
@ -468,40 +457,41 @@ func TestReorgBadHeaderHashes(t *testing.T) { testReorgBadHashes(t, false) }
func TestReorgBadBlockHashes(t *testing.T) { testReorgBadHashes(t, true) } func TestReorgBadBlockHashes(t *testing.T) { testReorgBadHashes(t, true) }
func testReorgBadHashes(t *testing.T, full bool) { func testReorgBadHashes(t *testing.T, full bool) {
bc := newTestBlockChain(true) // Create a pristine chain and database
defer bc.Stop() db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
if err != nil {
t.Fatalf("failed to create pristine chain: %v", err)
}
// Create a chain, import and ban afterwards // Create a chain, import and ban afterwards
headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10) headers := makeHeaderChain(blockchain.CurrentHeader(), 4, ethash.NewFaker(), db, 10)
blocks := makeBlockChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10) blocks := makeBlockChain(blockchain.CurrentBlock(), 4, ethash.NewFaker(), db, 10)
if full { if full {
if _, err := bc.InsertChain(blocks); err != nil { if _, err = blockchain.InsertChain(blocks); err != nil {
t.Fatalf("failed to import blocks: %v", err) t.Errorf("failed to import blocks: %v", err)
} }
if bc.CurrentBlock().Hash() != blocks[3].Hash() { if blockchain.CurrentBlock().Hash() != blocks[3].Hash() {
t.Errorf("last block hash mismatch: have: %x, want %x", bc.CurrentBlock().Hash(), blocks[3].Header().Hash()) t.Errorf("last block hash mismatch: have: %x, want %x", blockchain.CurrentBlock().Hash(), blocks[3].Header().Hash())
} }
BadHashes[blocks[3].Header().Hash()] = true BadHashes[blocks[3].Header().Hash()] = true
defer func() { delete(BadHashes, blocks[3].Header().Hash()) }() defer func() { delete(BadHashes, blocks[3].Header().Hash()) }()
} else { } else {
if _, err := bc.InsertHeaderChain(headers, 1); err != nil { if _, err = blockchain.InsertHeaderChain(headers, 1); err != nil {
t.Fatalf("failed to import headers: %v", err) t.Errorf("failed to import headers: %v", err)
} }
if bc.CurrentHeader().Hash() != headers[3].Hash() { if blockchain.CurrentHeader().Hash() != headers[3].Hash() {
t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash()) t.Errorf("last header hash mismatch: have: %x, want %x", blockchain.CurrentHeader().Hash(), headers[3].Hash())
} }
BadHashes[headers[3].Hash()] = true BadHashes[headers[3].Hash()] = true
defer func() { delete(BadHashes, headers[3].Hash()) }() defer func() { delete(BadHashes, headers[3].Hash()) }()
} }
blockchain.Stop()
// Create a new BlockChain and check that it rolled back the state. // Create a new BlockChain and check that it rolled back the state.
ncm, err := NewBlockChain(bc.db, nil, bc.chainConfig, ethash.NewFaker(), vm.Config{}) ncm, err := NewBlockChain(blockchain.db, nil, blockchain.chainConfig, ethash.NewFaker(), vm.Config{})
if err != nil { if err != nil {
t.Fatalf("failed to create new chain manager: %v", err) t.Fatalf("failed to create new chain manager: %v", err)
} }
defer ncm.Stop()
if full { if full {
if ncm.CurrentBlock().Hash() != blocks[2].Header().Hash() { if ncm.CurrentBlock().Hash() != blocks[2].Header().Hash() {
t.Errorf("last block hash mismatch: have: %x, want %x", ncm.CurrentBlock().Hash(), blocks[2].Header().Hash()) t.Errorf("last block hash mismatch: have: %x, want %x", ncm.CurrentBlock().Hash(), blocks[2].Header().Hash())
@ -514,6 +504,7 @@ func testReorgBadHashes(t *testing.T, full bool) {
t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash()) t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash())
} }
} }
ncm.Stop()
} }
// Tests chain insertions in the face of one entity containing an invalid nonce. // Tests chain insertions in the face of one entity containing an invalid nonce.
@ -989,10 +980,13 @@ done:
// Tests if the canonical block can be fetched from the database during chain insertion. // Tests if the canonical block can be fetched from the database during chain insertion.
func TestCanonicalBlockRetrieval(t *testing.T) { func TestCanonicalBlockRetrieval(t *testing.T) {
bc := newTestBlockChain(true) _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true)
defer bc.Stop() if err != nil {
t.Fatalf("failed to create pristine chain: %v", err)
}
defer blockchain.Stop()
chain, _ := GenerateChain(bc.chainConfig, bc.genesisBlock, ethash.NewFaker(), bc.db, 10, func(i int, gen *BlockGen) {}) chain, _ := GenerateChain(blockchain.chainConfig, blockchain.genesisBlock, ethash.NewFaker(), blockchain.db, 10, func(i int, gen *BlockGen) {})
var pend sync.WaitGroup var pend sync.WaitGroup
pend.Add(len(chain)) pend.Add(len(chain))
@ -1003,14 +997,14 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
// try to retrieve a block by its canonical hash and see if the block data can be retrieved. // try to retrieve a block by its canonical hash and see if the block data can be retrieved.
for { for {
ch := GetCanonicalHash(bc.db, block.NumberU64()) ch := GetCanonicalHash(blockchain.db, block.NumberU64())
if ch == (common.Hash{}) { if ch == (common.Hash{}) {
continue // busy wait for canonical hash to be written continue // busy wait for canonical hash to be written
} }
if ch != block.Hash() { if ch != block.Hash() {
t.Fatalf("unknown canonical hash, want %s, got %s", block.Hash().Hex(), ch.Hex()) t.Fatalf("unknown canonical hash, want %s, got %s", block.Hash().Hex(), ch.Hex())
} }
fb := GetBlock(bc.db, ch, block.NumberU64()) fb := GetBlock(blockchain.db, ch, block.NumberU64())
if fb == nil { if fb == nil {
t.Fatalf("unable to retrieve block %d for canonical hash: %s", block.NumberU64(), ch.Hex()) t.Fatalf("unable to retrieve block %d for canonical hash: %s", block.NumberU64(), ch.Hex())
} }
@ -1021,7 +1015,7 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
} }
}(chain[i]) }(chain[i])
if _, err := bc.InsertChain(types.Blocks{chain[i]}); err != nil { if _, err := blockchain.InsertChain(types.Blocks{chain[i]}); err != nil {
t.Fatalf("failed to insert block %d: %v", i, err) t.Fatalf("failed to insert block %d: %v", i, err)
} }
} }

View file

@ -70,8 +70,8 @@ var (
ErrChainConfigNotFound = errors.New("ChainConfig not found") // general config not found error ErrChainConfigNotFound = errors.New("ChainConfig not found") // general config not found error
preimageCounter = metrics.NewCounter("db/preimage/total") preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
preimageHitCounter = metrics.NewCounter("db/preimage/hits") preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
) )
// TxLookupEntry is a positional metadata to help looking up the data content of // TxLookupEntry is a positional metadata to help looking up the data content of

View file

@ -118,10 +118,12 @@ func TestSetupGenesis(t *testing.T) {
// Commit the 'old' genesis block with Homestead transition at #2. // Commit the 'old' genesis block with Homestead transition at #2.
// Advance to block #4, past the homestead transition block of customg. // Advance to block #4, past the homestead transition block of customg.
genesis := oldcustomg.MustCommit(db) genesis := oldcustomg.MustCommit(db)
bc, _ := NewBlockChain(db, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{}) bc, _ := NewBlockChain(db, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{})
defer bc.Stop() defer bc.Stop()
bc.SetValidator(bproc{})
bc.InsertChain(makeBlockChainWithDiff(genesis, []int{2, 3, 4, 5}, 0)) blocks, _ := GenerateChain(oldcustomg.Config, genesis, ethash.NewFaker(), db, 4, nil)
bc.InsertChain(blocks)
bc.CurrentBlock() bc.CurrentBlock()
// This should return a compatibility error. // This should return a compatibility error.
return SetupGenesisBlock(db, &customg) return SetupGenesisBlock(db, &customg)

View file

@ -87,20 +87,20 @@ var (
var ( var (
// Metrics for the pending pool // Metrics for the pending pool
pendingDiscardCounter = metrics.NewCounter("txpool/pending/discard") pendingDiscardCounter = metrics.NewRegisteredCounter("txpool/pending/discard", nil)
pendingReplaceCounter = metrics.NewCounter("txpool/pending/replace") pendingReplaceCounter = metrics.NewRegisteredCounter("txpool/pending/replace", nil)
pendingRateLimitCounter = metrics.NewCounter("txpool/pending/ratelimit") // Dropped due to rate limiting pendingRateLimitCounter = metrics.NewRegisteredCounter("txpool/pending/ratelimit", nil) // Dropped due to rate limiting
pendingNofundsCounter = metrics.NewCounter("txpool/pending/nofunds") // Dropped due to out-of-funds pendingNofundsCounter = metrics.NewRegisteredCounter("txpool/pending/nofunds", nil) // Dropped due to out-of-funds
// Metrics for the queued pool // Metrics for the queued pool
queuedDiscardCounter = metrics.NewCounter("txpool/queued/discard") queuedDiscardCounter = metrics.NewRegisteredCounter("txpool/queued/discard", nil)
queuedReplaceCounter = metrics.NewCounter("txpool/queued/replace") queuedReplaceCounter = metrics.NewRegisteredCounter("txpool/queued/replace", nil)
queuedRateLimitCounter = metrics.NewCounter("txpool/queued/ratelimit") // Dropped due to rate limiting queuedRateLimitCounter = metrics.NewRegisteredCounter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting
queuedNofundsCounter = metrics.NewCounter("txpool/queued/nofunds") // Dropped due to out-of-funds queuedNofundsCounter = metrics.NewRegisteredCounter("txpool/queued/nofunds", nil) // Dropped due to out-of-funds
// General tx metrics // General tx metrics
invalidTxCounter = metrics.NewCounter("txpool/invalid") invalidTxCounter = metrics.NewRegisteredCounter("txpool/invalid", nil)
underpricedTxCounter = metrics.NewCounter("txpool/underpriced") underpricedTxCounter = metrics.NewRegisteredCounter("txpool/underpriced", nil)
) )
// TxStatus is the current status of a transaction as seen by the pool. // TxStatus is the current status of a transaction as seen by the pool.

View file

@ -302,6 +302,66 @@ func opMulmod(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *S
return nil, nil return nil, nil
} }
// opSHL implements Shift Left
// The SHL instruction (shift left) pops 2 values from the stack, first arg1 and then arg2,
// and pushes on the stack arg2 shifted to the left by arg1 number of bits.
func opSHL(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
shift, value := math.U256(stack.pop()), math.U256(stack.peek())
defer evm.interpreter.intPool.put(shift) // First operand back into the pool
if shift.Cmp(common.Big256) >= 0 {
value.SetUint64(0)
return nil, nil
}
n := uint(shift.Uint64())
math.U256(value.Lsh(value, n))
return nil, nil
}
// opSHR implements Logical Shift Right
// The SHR instruction (logical shift right) pops 2 values from the stack, first arg1 and then arg2,
// and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill.
func opSHR(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
shift, value := math.U256(stack.pop()), math.U256(stack.peek())
defer evm.interpreter.intPool.put(shift) // First operand back into the pool
if shift.Cmp(common.Big256) >= 0 {
value.SetUint64(0)
return nil, nil
}
n := uint(shift.Uint64())
math.U256(value.Rsh(value, n))
return nil, nil
}
// opSAR implements Arithmetic Shift Right
// The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2,
// and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension.
func opSAR(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// Note, S256 returns (potentially) a new bigint, so we're popping, not peeking this one
shift, value := math.U256(stack.pop()), math.S256(stack.pop())
defer evm.interpreter.intPool.put(shift) // First operand back into the pool
if shift.Cmp(common.Big256) >= 0 {
if value.Sign() > 0 {
value.SetUint64(0)
} else {
value.SetInt64(-1)
}
stack.push(math.U256(value))
return nil, nil
}
n := uint(shift.Uint64())
value.Rsh(value, n)
stack.push(math.U256(value))
return nil, nil
}
func opSha3(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { func opSha3(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
offset, size := stack.pop(), stack.pop() offset, size := stack.pop(), stack.pop()
data := memory.Get(offset.Int64(), size.Int64()) data := memory.Get(offset.Int64(), size.Int64())

View file

@ -24,6 +24,48 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
type twoOperandTest struct {
x string
y string
expected string
}
func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)) {
var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
stack = newstack()
pc = uint64(0)
)
for i, test := range tests {
x := new(big.Int).SetBytes(common.Hex2Bytes(test.x))
shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y))
expected := new(big.Int).SetBytes(common.Hex2Bytes(test.expected))
stack.push(x)
stack.push(shift)
opFn(&pc, env, nil, nil, stack)
actual := stack.pop()
if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %d, expected %v, got %v", i, expected, actual)
}
// Check pool usage
// 1.pool is not allowed to contain anything on the stack
// 2.pool is not allowed to contain the same pointers twice
if env.interpreter.intPool.pool.len() > 0 {
poolvals := make(map[*big.Int]struct{})
poolvals[actual] = struct{}{}
for env.interpreter.intPool.pool.len() > 0 {
key := env.interpreter.intPool.get()
if _, exist := poolvals[key]; exist {
t.Errorf("Testcase %d, pool contains double-entry", i)
}
poolvals[key] = struct{}{}
}
}
}
}
func TestByteOp(t *testing.T) { func TestByteOp(t *testing.T) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
@ -57,6 +99,98 @@ func TestByteOp(t *testing.T) {
} }
} }
func TestSHL(t *testing.T) {
// Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shl-shift-left
tests := []twoOperandTest{
{"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
{"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000002"},
{"0000000000000000000000000000000000000000000000000000000000000001", "ff", "8000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000001", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000001", "0101", "0000000000000000000000000000000000000000000000000000000000000000"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "8000000000000000000000000000000000000000000000000000000000000000"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},
}
testTwoOperandOp(t, tests, opSHL)
}
func TestSHR(t *testing.T) {
// Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shr-logical-shift-right
tests := []twoOperandTest{
{"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
{"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "01", "4000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "ff", "0000000000000000000000000000000000000000000000000000000000000001"},
{"8000000000000000000000000000000000000000000000000000000000000000", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "0101", "0000000000000000000000000000000000000000000000000000000000000000"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "0000000000000000000000000000000000000000000000000000000000000001"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
}
testTwoOperandOp(t, tests, opSHR)
}
func TestSAR(t *testing.T) {
// Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#sar-arithmetic-shift-right
tests := []twoOperandTest{
{"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
{"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "01", "c000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "ff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"8000000000000000000000000000000000000000000000000000000000000000", "0100", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"8000000000000000000000000000000000000000000000000000000000000000", "0101", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"4000000000000000000000000000000000000000000000000000000000000000", "fe", "0000000000000000000000000000000000000000000000000000000000000001"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "f8", "000000000000000000000000000000000000000000000000000000000000007f"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "fe", "0000000000000000000000000000000000000000000000000000000000000001"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "0000000000000000000000000000000000000000000000000000000000000000"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
}
testTwoOperandOp(t, tests, opSAR)
}
func TestSGT(t *testing.T) {
tests := []twoOperandTest{
{"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"},
{"0000000000000000000000000000000000000000000000000000000000000001", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000001", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
}
testTwoOperandOp(t, tests, opSgt)
}
func TestSLT(t *testing.T) {
tests := []twoOperandTest{
{"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000001", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"},
{"8000000000000000000000000000000000000000000000000000000000000001", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"},
}
testTwoOperandOp(t, tests, opSlt)
}
func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) { func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
@ -259,3 +393,22 @@ func BenchmarkOpMulmod(b *testing.B) {
opBenchmark(b, opMulmod, x, y, z) opBenchmark(b, opMulmod, x, y, z)
} }
func BenchmarkOpSHL(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "ff"
opBenchmark(b, opSHL, x, y)
}
func BenchmarkOpSHR(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "ff"
opBenchmark(b, opSHR, x, y)
}
func BenchmarkOpSAR(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "ff"
opBenchmark(b, opSAR, x, y)
}

View file

@ -68,6 +68,8 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
// we'll set the default jump table. // we'll set the default jump table.
if !cfg.JumpTable[STOP].valid { if !cfg.JumpTable[STOP].valid {
switch { switch {
case evm.ChainConfig().IsConstantinople(evm.BlockNumber):
cfg.JumpTable = constantinopleInstructionSet
case evm.ChainConfig().IsByzantium(evm.BlockNumber): case evm.ChainConfig().IsByzantium(evm.BlockNumber):
cfg.JumpTable = byzantiumInstructionSet cfg.JumpTable = byzantiumInstructionSet
case evm.ChainConfig().IsHomestead(evm.BlockNumber): case evm.ChainConfig().IsHomestead(evm.BlockNumber):

View file

@ -51,11 +51,38 @@ type operation struct {
} }
var ( var (
frontierInstructionSet = NewFrontierInstructionSet() frontierInstructionSet = NewFrontierInstructionSet()
homesteadInstructionSet = NewHomesteadInstructionSet() homesteadInstructionSet = NewHomesteadInstructionSet()
byzantiumInstructionSet = NewByzantiumInstructionSet() byzantiumInstructionSet = NewByzantiumInstructionSet()
constantinopleInstructionSet = NewConstantinopleInstructionSet()
) )
// NewConstantinopleInstructionSet returns the frontier, homestead
// byzantium and contantinople instructions.
func NewConstantinopleInstructionSet() [256]operation {
// instructions that can be executed during the byzantium phase.
instructionSet := NewByzantiumInstructionSet()
instructionSet[SHL] = operation{
execute: opSHL,
gasCost: constGasFunc(GasFastestStep),
validateStack: makeStackFunc(2, 1),
valid: true,
}
instructionSet[SHR] = operation{
execute: opSHR,
gasCost: constGasFunc(GasFastestStep),
validateStack: makeStackFunc(2, 1),
valid: true,
}
instructionSet[SAR] = operation{
execute: opSAR,
gasCost: constGasFunc(GasFastestStep),
validateStack: makeStackFunc(2, 1),
valid: true,
}
return instructionSet
}
// NewByzantiumInstructionSet returns the frontier, homestead and // NewByzantiumInstructionSet returns the frontier, homestead and
// byzantium instructions. // byzantium instructions.
func NewByzantiumInstructionSet() [256]operation { func NewByzantiumInstructionSet() [256]operation {

View file

@ -63,6 +63,9 @@ const (
XOR XOR
NOT NOT
BYTE BYTE
SHL
SHR
SAR
SHA3 = 0x20 SHA3 = 0x20
) )
@ -234,6 +237,9 @@ var opCodeToString = map[OpCode]string{
OR: "OR", OR: "OR",
XOR: "XOR", XOR: "XOR",
BYTE: "BYTE", BYTE: "BYTE",
SHL: "SHL",
SHR: "SHR",
SAR: "SAR",
ADDMOD: "ADDMOD", ADDMOD: "ADDMOD",
MULMOD: "MULMOD", MULMOD: "MULMOD",
@ -400,6 +406,9 @@ var stringToOp = map[string]OpCode{
"OR": OR, "OR": OR,
"XOR": XOR, "XOR": XOR,
"BYTE": BYTE, "BYTE": BYTE,
"SHL": SHL,
"SHR": SHR,
"SAR": SAR,
"ADDMOD": ADDMOD, "ADDMOD": ADDMOD,
"MULMOD": MULMOD, "MULMOD": MULMOD,
"SHA3": SHA3, "SHA3": SHA3,

View file

@ -38,10 +38,10 @@ import (
"github.com/elastic/gosigar" "github.com/elastic/gosigar"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/rcrowley/go-metrics"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )

View file

@ -104,6 +104,18 @@ func (b *EthApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash)
return core.GetBlockReceipts(b.eth.chainDb, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash)), nil return core.GetBlockReceipts(b.eth.chainDb, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash)), nil
} }
func (b *EthApiBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
receipts := core.GetBlockReceipts(b.eth.chainDb, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash))
if receipts == nil {
return nil, nil
}
logs := make([][]*types.Log, len(receipts))
for i, receipt := range receipts {
logs[i] = receipt.Logs
}
return logs, nil
}
func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int { func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash) return b.eth.blockchain.GetTdByHash(blockHash)
} }

View file

@ -31,8 +31,8 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/rcrowley/go-metrics"
) )
var ( var (

View file

@ -23,21 +23,21 @@ import (
) )
var ( var (
headerInMeter = metrics.NewMeter("eth/downloader/headers/in") headerInMeter = metrics.NewRegisteredMeter("eth/downloader/headers/in", nil)
headerReqTimer = metrics.NewTimer("eth/downloader/headers/req") headerReqTimer = metrics.NewRegisteredTimer("eth/downloader/headers/req", nil)
headerDropMeter = metrics.NewMeter("eth/downloader/headers/drop") headerDropMeter = metrics.NewRegisteredMeter("eth/downloader/headers/drop", nil)
headerTimeoutMeter = metrics.NewMeter("eth/downloader/headers/timeout") headerTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/headers/timeout", nil)
bodyInMeter = metrics.NewMeter("eth/downloader/bodies/in") bodyInMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/in", nil)
bodyReqTimer = metrics.NewTimer("eth/downloader/bodies/req") bodyReqTimer = metrics.NewRegisteredTimer("eth/downloader/bodies/req", nil)
bodyDropMeter = metrics.NewMeter("eth/downloader/bodies/drop") bodyDropMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/drop", nil)
bodyTimeoutMeter = metrics.NewMeter("eth/downloader/bodies/timeout") bodyTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/timeout", nil)
receiptInMeter = metrics.NewMeter("eth/downloader/receipts/in") receiptInMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/in", nil)
receiptReqTimer = metrics.NewTimer("eth/downloader/receipts/req") receiptReqTimer = metrics.NewRegisteredTimer("eth/downloader/receipts/req", nil)
receiptDropMeter = metrics.NewMeter("eth/downloader/receipts/drop") receiptDropMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/drop", nil)
receiptTimeoutMeter = metrics.NewMeter("eth/downloader/receipts/timeout") receiptTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/timeout", nil)
stateInMeter = metrics.NewMeter("eth/downloader/states/in") stateInMeter = metrics.NewRegisteredMeter("eth/downloader/states/in", nil)
stateDropMeter = metrics.NewMeter("eth/downloader/states/drop") stateDropMeter = metrics.NewRegisteredMeter("eth/downloader/states/drop", nil)
) )

View file

@ -28,7 +28,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/rcrowley/go-metrics" "github.com/ethereum/go-ethereum/metrics"
"gopkg.in/karalabe/cookiejar.v2/collections/prque" "gopkg.in/karalabe/cookiejar.v2/collections/prque"
) )

View file

@ -23,21 +23,21 @@ import (
) )
var ( var (
propAnnounceInMeter = metrics.NewMeter("eth/fetcher/prop/announces/in") propAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/in", nil)
propAnnounceOutTimer = metrics.NewTimer("eth/fetcher/prop/announces/out") propAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/prop/announces/out", nil)
propAnnounceDropMeter = metrics.NewMeter("eth/fetcher/prop/announces/drop") propAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/drop", nil)
propAnnounceDOSMeter = metrics.NewMeter("eth/fetcher/prop/announces/dos") propAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/dos", nil)
propBroadcastInMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/in") propBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/in", nil)
propBroadcastOutTimer = metrics.NewTimer("eth/fetcher/prop/broadcasts/out") propBroadcastOutTimer = metrics.NewRegisteredTimer("eth/fetcher/prop/broadcasts/out", nil)
propBroadcastDropMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/drop") propBroadcastDropMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/drop", nil)
propBroadcastDOSMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/dos") propBroadcastDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/dos", nil)
headerFetchMeter = metrics.NewMeter("eth/fetcher/fetch/headers") headerFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/fetch/headers", nil)
bodyFetchMeter = metrics.NewMeter("eth/fetcher/fetch/bodies") bodyFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/fetch/bodies", nil)
headerFilterInMeter = metrics.NewMeter("eth/fetcher/filter/headers/in") headerFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/headers/in", nil)
headerFilterOutMeter = metrics.NewMeter("eth/fetcher/filter/headers/out") headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/headers/out", nil)
bodyFilterInMeter = metrics.NewMeter("eth/fetcher/filter/bodies/in") bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/bodies/in", nil)
bodyFilterOutMeter = metrics.NewMeter("eth/fetcher/filter/bodies/out") bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/bodies/out", nil)
) )

View file

@ -34,6 +34,7 @@ type Backend interface {
EventMux() *event.TypeMux EventMux() *event.TypeMux
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error)
SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
@ -201,16 +202,28 @@ func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, e
// match the filter criteria. This function is called when the bloom filter signals a potential match. // match the filter criteria. This function is called when the bloom filter signals a potential match.
func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs []*types.Log, err error) { func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs []*types.Log, err error) {
// Get the logs of the block // Get the logs of the block
receipts, err := f.backend.GetReceipts(ctx, header.Hash()) logsList, err := f.backend.GetLogs(ctx, header.Hash())
if err != nil { if err != nil {
return nil, err return nil, err
} }
var unfiltered []*types.Log var unfiltered []*types.Log
for _, receipt := range receipts { for _, logs := range logsList {
unfiltered = append(unfiltered, receipt.Logs...) unfiltered = append(unfiltered, logs...)
} }
logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics) logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
if len(logs) > 0 { if len(logs) > 0 {
// We have matching logs, check if we need to resolve full logs via the light client
if logs[0].TxHash == (common.Hash{}) {
receipts, err := f.backend.GetReceipts(ctx, header.Hash())
if err != nil {
return nil, err
}
unfiltered = unfiltered[:0]
for _, receipt := range receipts {
unfiltered = append(unfiltered, receipt.Logs...)
}
logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
}
return logs, nil return logs, nil
} }
return nil, nil return nil, nil

View file

@ -375,19 +375,35 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.
// Get the logs of the block // Get the logs of the block
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel() defer cancel()
receipts, err := es.backend.GetReceipts(ctx, header.Hash()) logsList, err := es.backend.GetLogs(ctx, header.Hash())
if err != nil { if err != nil {
return nil return nil
} }
var unfiltered []*types.Log var unfiltered []*types.Log
for _, receipt := range receipts { for _, logs := range logsList {
for _, log := range receipt.Logs { for _, log := range logs {
logcopy := *log logcopy := *log
logcopy.Removed = remove logcopy.Removed = remove
unfiltered = append(unfiltered, &logcopy) unfiltered = append(unfiltered, &logcopy)
} }
} }
logs := filterLogs(unfiltered, nil, nil, addresses, topics) logs := filterLogs(unfiltered, nil, nil, addresses, topics)
if len(logs) > 0 && logs[0].TxHash == (common.Hash{}) {
// We have matching but non-derived logs
receipts, err := es.backend.GetReceipts(ctx, header.Hash())
if err != nil {
return nil
}
unfiltered = unfiltered[:0]
for _, receipt := range receipts {
for _, log := range receipt.Logs {
logcopy := *log
logcopy.Removed = remove
unfiltered = append(unfiltered, &logcopy)
}
}
logs = filterLogs(unfiltered, nil, nil, addresses, topics)
}
return logs return logs
} }
return nil return nil

View file

@ -69,8 +69,19 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe
} }
func (b *testBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { func (b *testBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) {
num := core.GetBlockNumber(b.db, blockHash) number := core.GetBlockNumber(b.db, blockHash)
return core.GetBlockReceipts(b.db, blockHash, num), nil return core.GetBlockReceipts(b.db, blockHash, number), nil
}
func (b *testBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
number := core.GetBlockNumber(b.db, blockHash)
receipts := core.GetBlockReceipts(b.db, blockHash, number)
logs := make([][]*types.Log, len(receipts))
for i, receipt := range receipts {
logs[i] = receipt.Logs
}
return logs, nil
} }
func (b *testBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { func (b *testBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription {

View file

@ -22,38 +22,38 @@ import (
) )
var ( var (
propTxnInPacketsMeter = metrics.NewMeter("eth/prop/txns/in/packets") propTxnInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/packets", nil)
propTxnInTrafficMeter = metrics.NewMeter("eth/prop/txns/in/traffic") propTxnInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/traffic", nil)
propTxnOutPacketsMeter = metrics.NewMeter("eth/prop/txns/out/packets") propTxnOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/packets", nil)
propTxnOutTrafficMeter = metrics.NewMeter("eth/prop/txns/out/traffic") propTxnOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/traffic", nil)
propHashInPacketsMeter = metrics.NewMeter("eth/prop/hashes/in/packets") propHashInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/packets", nil)
propHashInTrafficMeter = metrics.NewMeter("eth/prop/hashes/in/traffic") propHashInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/traffic", nil)
propHashOutPacketsMeter = metrics.NewMeter("eth/prop/hashes/out/packets") propHashOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/packets", nil)
propHashOutTrafficMeter = metrics.NewMeter("eth/prop/hashes/out/traffic") propHashOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/traffic", nil)
propBlockInPacketsMeter = metrics.NewMeter("eth/prop/blocks/in/packets") propBlockInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/packets", nil)
propBlockInTrafficMeter = metrics.NewMeter("eth/prop/blocks/in/traffic") propBlockInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/traffic", nil)
propBlockOutPacketsMeter = metrics.NewMeter("eth/prop/blocks/out/packets") propBlockOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/packets", nil)
propBlockOutTrafficMeter = metrics.NewMeter("eth/prop/blocks/out/traffic") propBlockOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/traffic", nil)
reqHeaderInPacketsMeter = metrics.NewMeter("eth/req/headers/in/packets") reqHeaderInPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/in/packets", nil)
reqHeaderInTrafficMeter = metrics.NewMeter("eth/req/headers/in/traffic") reqHeaderInTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/in/traffic", nil)
reqHeaderOutPacketsMeter = metrics.NewMeter("eth/req/headers/out/packets") reqHeaderOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/out/packets", nil)
reqHeaderOutTrafficMeter = metrics.NewMeter("eth/req/headers/out/traffic") reqHeaderOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/out/traffic", nil)
reqBodyInPacketsMeter = metrics.NewMeter("eth/req/bodies/in/packets") reqBodyInPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/packets", nil)
reqBodyInTrafficMeter = metrics.NewMeter("eth/req/bodies/in/traffic") reqBodyInTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/traffic", nil)
reqBodyOutPacketsMeter = metrics.NewMeter("eth/req/bodies/out/packets") reqBodyOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/packets", nil)
reqBodyOutTrafficMeter = metrics.NewMeter("eth/req/bodies/out/traffic") reqBodyOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/traffic", nil)
reqStateInPacketsMeter = metrics.NewMeter("eth/req/states/in/packets") reqStateInPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/in/packets", nil)
reqStateInTrafficMeter = metrics.NewMeter("eth/req/states/in/traffic") reqStateInTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/in/traffic", nil)
reqStateOutPacketsMeter = metrics.NewMeter("eth/req/states/out/packets") reqStateOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/out/packets", nil)
reqStateOutTrafficMeter = metrics.NewMeter("eth/req/states/out/traffic") reqStateOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/out/traffic", nil)
reqReceiptInPacketsMeter = metrics.NewMeter("eth/req/receipts/in/packets") reqReceiptInPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/packets", nil)
reqReceiptInTrafficMeter = metrics.NewMeter("eth/req/receipts/in/traffic") reqReceiptInTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/traffic", nil)
reqReceiptOutPacketsMeter = metrics.NewMeter("eth/req/receipts/out/packets") reqReceiptOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/packets", nil)
reqReceiptOutTrafficMeter = metrics.NewMeter("eth/req/receipts/out/traffic") reqReceiptOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/traffic", nil)
miscInPacketsMeter = metrics.NewMeter("eth/misc/in/packets") miscInPacketsMeter = metrics.NewRegisteredMeter("eth/misc/in/packets", nil)
miscInTrafficMeter = metrics.NewMeter("eth/misc/in/traffic") miscInTrafficMeter = metrics.NewRegisteredMeter("eth/misc/in/traffic", nil)
miscOutPacketsMeter = metrics.NewMeter("eth/misc/out/packets") miscOutPacketsMeter = metrics.NewRegisteredMeter("eth/misc/out/packets", nil)
miscOutTrafficMeter = metrics.NewMeter("eth/misc/out/traffic") miscOutTrafficMeter = metrics.NewRegisteredMeter("eth/misc/out/traffic", nil)
) )
// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of // meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of

View file

@ -29,8 +29,6 @@ import (
"github.com/syndtr/goleveldb/leveldb/filter" "github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/opt"
gometrics "github.com/rcrowley/go-metrics"
) )
var OpenFileLimit = 64 var OpenFileLimit = 64
@ -39,15 +37,15 @@ type LDBDatabase struct {
fn string // filename for reporting fn string // filename for reporting
db *leveldb.DB // LevelDB instance db *leveldb.DB // LevelDB instance
getTimer gometrics.Timer // Timer for measuring the database get request counts and latencies getTimer metrics.Timer // Timer for measuring the database get request counts and latencies
putTimer gometrics.Timer // Timer for measuring the database put request counts and latencies putTimer metrics.Timer // Timer for measuring the database put request counts and latencies
delTimer gometrics.Timer // Timer for measuring the database delete request counts and latencies delTimer metrics.Timer // Timer for measuring the database delete request counts and latencies
missMeter gometrics.Meter // Meter for measuring the missed database get requests missMeter metrics.Meter // Meter for measuring the missed database get requests
readMeter gometrics.Meter // Meter for measuring the database get request data usage readMeter metrics.Meter // Meter for measuring the database get request data usage
writeMeter gometrics.Meter // Meter for measuring the database put request data usage writeMeter metrics.Meter // Meter for measuring the database put request data usage
compTimeMeter gometrics.Meter // Meter for measuring the total time spent in database compaction compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction
compReadMeter gometrics.Meter // Meter for measuring the data read during compaction compReadMeter metrics.Meter // Meter for measuring the data read during compaction
compWriteMeter gometrics.Meter // Meter for measuring the data written during compaction compWriteMeter metrics.Meter // Meter for measuring the data written during compaction
quitLock sync.Mutex // Mutex protecting the quit channel access quitLock sync.Mutex // Mutex protecting the quit channel access
quitChan chan chan error // Quit channel to stop the metrics collection before closing the database quitChan chan chan error // Quit channel to stop the metrics collection before closing the database
@ -180,15 +178,15 @@ func (db *LDBDatabase) Meter(prefix string) {
return return
} }
// Initialize all the metrics collector at the requested prefix // Initialize all the metrics collector at the requested prefix
db.getTimer = metrics.NewTimer(prefix + "user/gets") db.getTimer = metrics.NewRegisteredTimer(prefix+"user/gets", nil)
db.putTimer = metrics.NewTimer(prefix + "user/puts") db.putTimer = metrics.NewRegisteredTimer(prefix+"user/puts", nil)
db.delTimer = metrics.NewTimer(prefix + "user/dels") db.delTimer = metrics.NewRegisteredTimer(prefix+"user/dels", nil)
db.missMeter = metrics.NewMeter(prefix + "user/misses") db.missMeter = metrics.NewRegisteredMeter(prefix+"user/misses", nil)
db.readMeter = metrics.NewMeter(prefix + "user/reads") db.readMeter = metrics.NewRegisteredMeter(prefix+"user/reads", nil)
db.writeMeter = metrics.NewMeter(prefix + "user/writes") db.writeMeter = metrics.NewRegisteredMeter(prefix+"user/writes", nil)
db.compTimeMeter = metrics.NewMeter(prefix + "compact/time") db.compTimeMeter = metrics.NewRegisteredMeter(prefix+"compact/time", nil)
db.compReadMeter = metrics.NewMeter(prefix + "compact/input") db.compReadMeter = metrics.NewRegisteredMeter(prefix+"compact/input", nil)
db.compWriteMeter = metrics.NewMeter(prefix + "compact/output") db.compWriteMeter = metrics.NewRegisteredMeter(prefix+"compact/output", nil)
// Create a quit channel for the periodic collector and run it // Create a quit channel for the periodic collector and run it
db.quitLock.Lock() db.quitLock.Lock()

View file

@ -26,6 +26,8 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log/term" "github.com/ethereum/go-ethereum/log/term"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/metrics/exp"
colorable "github.com/mattn/go-colorable" colorable "github.com/mattn/go-colorable"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
) )
@ -127,6 +129,10 @@ func Setup(ctx *cli.Context) error {
// pprof server // pprof server
if ctx.GlobalBool(pprofFlag.Name) { if ctx.GlobalBool(pprofFlag.Name) {
// Hook go-metrics into expvar on any /debug/metrics request, load all vars
// from the registry into expvar, and execute regular expvar handler.
exp.Exp(metrics.DefaultRegistry)
address := fmt.Sprintf("%s:%d", ctx.GlobalString(pprofAddrFlag.Name), ctx.GlobalInt(pprofPortFlag.Name)) address := fmt.Sprintf("%s:%d", ctx.GlobalString(pprofAddrFlag.Name), ctx.GlobalInt(pprofPortFlag.Name))
go func() { go func() {
log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address))

View file

@ -1032,15 +1032,19 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context,
} }
// GetTransactionReceipt returns the transaction receipt for the given transaction hash. // GetTransactionReceipt returns the transaction receipt for the given transaction hash.
func (s *PublicTransactionPoolAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash) tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash)
if tx == nil { if tx == nil {
return nil, errors.New("unknown transaction") return nil, errors.New("unknown transaction")
} }
receipt, _, _, _ := core.GetReceipt(s.b.ChainDb(), hash) // Old receipts don't have the lookup data available receipts, err := s.b.GetReceipts(ctx, blockHash)
if receipt == nil { if err != nil {
return nil, err
}
if len(receipts) <= int(index) {
return nil, errors.New("unknown receipt") return nil, errors.New("unknown receipt")
} }
receipt := receipts[index]
var signer types.Signer = types.FrontierSigner{} var signer types.Signer = types.FrontierSigner{}
if tx.Protected() { if tx.Protected() {

View file

@ -87,6 +87,10 @@ func (b *LesApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash)
return light.GetBlockReceipts(ctx, b.eth.odr, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash)) return light.GetBlockReceipts(ctx, b.eth.odr, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash))
} }
func (b *LesApiBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
return light.GetBlockLogs(ctx, b.eth.odr, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash))
}
func (b *LesApiBackend) GetTd(blockHash common.Hash) *big.Int { func (b *LesApiBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash) return b.eth.blockchain.GetTdByHash(blockHash)
} }

View file

@ -58,10 +58,10 @@ var (
reqReceiptInTrafficMeter = metrics.NewMeter("eth/req/receipts/in/traffic") reqReceiptInTrafficMeter = metrics.NewMeter("eth/req/receipts/in/traffic")
reqReceiptOutPacketsMeter = metrics.NewMeter("eth/req/receipts/out/packets") reqReceiptOutPacketsMeter = metrics.NewMeter("eth/req/receipts/out/packets")
reqReceiptOutTrafficMeter = metrics.NewMeter("eth/req/receipts/out/traffic")*/ reqReceiptOutTrafficMeter = metrics.NewMeter("eth/req/receipts/out/traffic")*/
miscInPacketsMeter = metrics.NewMeter("les/misc/in/packets") miscInPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets", nil)
miscInTrafficMeter = metrics.NewMeter("les/misc/in/traffic") miscInTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic", nil)
miscOutPacketsMeter = metrics.NewMeter("les/misc/out/packets") miscOutPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets", nil)
miscOutTrafficMeter = metrics.NewMeter("les/misc/out/traffic") miscOutTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic", nil)
) )
// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of // meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of

View file

@ -126,15 +126,48 @@ func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint
// GetBlockReceipts retrieves the receipts generated by the transactions included // GetBlockReceipts retrieves the receipts generated by the transactions included
// in a block given by its hash. // in a block given by its hash.
func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) { func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) {
// Retrieve the potentially incomplete receipts from disk or network
receipts := core.GetBlockReceipts(odr.Database(), hash, number) receipts := core.GetBlockReceipts(odr.Database(), hash, number)
if receipts != nil { if receipts == nil {
return receipts, nil r := &ReceiptsRequest{Hash: hash, Number: number}
if err := odr.Retrieve(ctx, r); err != nil {
return nil, err
}
receipts = r.Receipts
} }
r := &ReceiptsRequest{Hash: hash, Number: number} // If the receipts are incomplete, fill the derived fields
if err := odr.Retrieve(ctx, r); err != nil { if len(receipts) > 0 && receipts[0].TxHash == (common.Hash{}) {
return nil, err block, err := GetBlock(ctx, odr, hash, number)
if err != nil {
return nil, err
}
genesis := core.GetCanonicalHash(odr.Database(), 0)
config, _ := core.GetChainConfig(odr.Database(), genesis)
core.SetReceiptsData(config, block, receipts)
core.WriteBlockReceipts(odr.Database(), hash, number, receipts)
} }
return r.Receipts, nil return receipts, nil
}
// GetBlockLogs retrieves the logs generated by the transactions included in a
// block given by its hash.
func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) {
// Retrieve the potentially incomplete receipts from disk or network
receipts := core.GetBlockReceipts(odr.Database(), hash, number)
if receipts == nil {
r := &ReceiptsRequest{Hash: hash, Number: number}
if err := odr.Retrieve(ctx, r); err != nil {
return nil, err
}
receipts = r.Receipts
}
// Return the logs without deriving any computed fields on the receipts
logs := make([][]*types.Log, len(receipts))
for i, receipt := range receipts {
logs[i] = receipt.Logs
}
return logs, nil
} }
// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to the given bit index and section indexes // GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to the given bit index and section indexes

1
metrics/FORK.md Normal file
View file

@ -0,0 +1 @@
This repo has been forked from https://github.com/rcrowley/go-metrics at commit e181e09

View file

@ -42,12 +42,22 @@ t.Update(47)
Register() is not threadsafe. For threadsafe metric registration use Register() is not threadsafe. For threadsafe metric registration use
GetOrRegister: GetOrRegister:
``` ```go
t := metrics.GetOrRegisterTimer("account.create.latency", nil) t := metrics.GetOrRegisterTimer("account.create.latency", nil)
t.Time(func() {}) t.Time(func() {})
t.Update(47) t.Update(47)
``` ```
**NOTE:** Be sure to unregister short-lived meters and timers otherwise they will
leak memory:
```go
// Will call Stop() on the Meter to allow for garbage collection
metrics.Unregister("quux")
// Or similarly for a Timer that embeds a Meter
metrics.Unregister("bang")
```
Periodically log every metric in human-readable form to standard error: Periodically log every metric in human-readable form to standard error:
```go ```go
@ -81,12 +91,13 @@ issues [#121](https://github.com/rcrowley/go-metrics/issues/121) and
```go ```go
import "github.com/vrischmann/go-metrics-influxdb" import "github.com/vrischmann/go-metrics-influxdb"
go influxdb.Influxdb(metrics.DefaultRegistry, 10e9, &influxdb.Config{ go influxdb.InfluxDB(metrics.DefaultRegistry,
Host: "127.0.0.1:8086", 10e9,
Database: "metrics", "127.0.0.1:8086",
Username: "test", "database-name",
Password: "test", "username",
}) "password"
)
``` ```
Periodically upload every metric to Librato using the [Librato client](https://github.com/mihasya/go-metrics-librato): Periodically upload every metric to Librato using the [Librato client](https://github.com/mihasya/go-metrics-librato):
@ -146,8 +157,10 @@ Publishing Metrics
Clients are available for the following destinations: Clients are available for the following destinations:
* Librato - [https://github.com/mihasya/go-metrics-librato](https://github.com/mihasya/go-metrics-librato) * Librato - https://github.com/mihasya/go-metrics-librato
* Graphite - [https://github.com/cyberdelia/go-metrics-graphite](https://github.com/cyberdelia/go-metrics-graphite) * Graphite - https://github.com/cyberdelia/go-metrics-graphite
* InfluxDB - [https://github.com/vrischmann/go-metrics-influxdb](https://github.com/vrischmann/go-metrics-influxdb) * InfluxDB - https://github.com/vrischmann/go-metrics-influxdb
* Ganglia - [https://github.com/appscode/metlia](https://github.com/appscode/metlia) * Ganglia - https://github.com/appscode/metlia
* Prometheus - [https://github.com/deathowl/go-metrics-prometheus](https://github.com/deathowl/go-metrics-prometheus) * Prometheus - https://github.com/deathowl/go-metrics-prometheus
* DataDog - https://github.com/syntaqx/go-metrics-datadog
* SignalFX - https://github.com/pascallouisperez/go-metrics-signalfx

View file

@ -22,7 +22,7 @@ func GetOrRegisterCounter(name string, r Registry) Counter {
// NewCounter constructs a new StandardCounter. // NewCounter constructs a new StandardCounter.
func NewCounter() Counter { func NewCounter() Counter {
if UseNilMetrics { if !Enabled {
return NilCounter{} return NilCounter{}
} }
return &StandardCounter{0} return &StandardCounter{0}

77
metrics/counter_test.go Normal file
View file

@ -0,0 +1,77 @@
package metrics
import "testing"
func BenchmarkCounter(b *testing.B) {
c := NewCounter()
b.ResetTimer()
for i := 0; i < b.N; i++ {
c.Inc(1)
}
}
func TestCounterClear(t *testing.T) {
c := NewCounter()
c.Inc(1)
c.Clear()
if count := c.Count(); 0 != count {
t.Errorf("c.Count(): 0 != %v\n", count)
}
}
func TestCounterDec1(t *testing.T) {
c := NewCounter()
c.Dec(1)
if count := c.Count(); -1 != count {
t.Errorf("c.Count(): -1 != %v\n", count)
}
}
func TestCounterDec2(t *testing.T) {
c := NewCounter()
c.Dec(2)
if count := c.Count(); -2 != count {
t.Errorf("c.Count(): -2 != %v\n", count)
}
}
func TestCounterInc1(t *testing.T) {
c := NewCounter()
c.Inc(1)
if count := c.Count(); 1 != count {
t.Errorf("c.Count(): 1 != %v\n", count)
}
}
func TestCounterInc2(t *testing.T) {
c := NewCounter()
c.Inc(2)
if count := c.Count(); 2 != count {
t.Errorf("c.Count(): 2 != %v\n", count)
}
}
func TestCounterSnapshot(t *testing.T) {
c := NewCounter()
c.Inc(1)
snapshot := c.Snapshot()
c.Inc(1)
if count := snapshot.Count(); 1 != count {
t.Errorf("c.Count(): 1 != %v\n", count)
}
}
func TestCounterZero(t *testing.T) {
c := NewCounter()
if count := c.Count(); 0 != count {
t.Errorf("c.Count(): 0 != %v\n", count)
}
}
func TestGetOrRegisterCounter(t *testing.T) {
r := NewRegistry()
NewRegisteredCounter("foo", r).Inc(47)
if c := GetOrRegisterCounter("foo", r); 47 != c.Count() {
t.Fatal(c)
}
}

View file

@ -22,7 +22,7 @@ var (
// Capture new values for the Go garbage collector statistics exported in // Capture new values for the Go garbage collector statistics exported in
// debug.GCStats. This is designed to be called as a goroutine. // debug.GCStats. This is designed to be called as a goroutine.
func CaptureDebugGCStats(r Registry, d time.Duration) { func CaptureDebugGCStats(r Registry, d time.Duration) {
for _ = range time.Tick(d) { for range time.Tick(d) {
CaptureDebugGCStatsOnce(r) CaptureDebugGCStatsOnce(r)
} }
} }
@ -41,8 +41,8 @@ func CaptureDebugGCStatsOnce(r Registry) {
debug.ReadGCStats(&gcStats) debug.ReadGCStats(&gcStats)
debugMetrics.ReadGCStats.UpdateSince(t) debugMetrics.ReadGCStats.UpdateSince(t)
debugMetrics.GCStats.LastGC.Update(int64(gcStats.LastGC.UnixNano())) debugMetrics.GCStats.LastGC.Update(gcStats.LastGC.UnixNano())
debugMetrics.GCStats.NumGC.Update(int64(gcStats.NumGC)) debugMetrics.GCStats.NumGC.Update(gcStats.NumGC)
if lastGC != gcStats.LastGC && 0 < len(gcStats.Pause) { if lastGC != gcStats.LastGC && 0 < len(gcStats.Pause) {
debugMetrics.GCStats.Pause.Update(int64(gcStats.Pause[0])) debugMetrics.GCStats.Pause.Update(int64(gcStats.Pause[0]))
} }

48
metrics/debug_test.go Normal file
View file

@ -0,0 +1,48 @@
package metrics
import (
"runtime"
"runtime/debug"
"testing"
"time"
)
func BenchmarkDebugGCStats(b *testing.B) {
r := NewRegistry()
RegisterDebugGCStats(r)
b.ResetTimer()
for i := 0; i < b.N; i++ {
CaptureDebugGCStatsOnce(r)
}
}
func TestDebugGCStatsBlocking(t *testing.T) {
if g := runtime.GOMAXPROCS(0); g < 2 {
t.Skipf("skipping TestDebugGCMemStatsBlocking with GOMAXPROCS=%d\n", g)
return
}
ch := make(chan int)
go testDebugGCStatsBlocking(ch)
var gcStats debug.GCStats
t0 := time.Now()
debug.ReadGCStats(&gcStats)
t1 := time.Now()
t.Log("i++ during debug.ReadGCStats:", <-ch)
go testDebugGCStatsBlocking(ch)
d := t1.Sub(t0)
t.Log(d)
time.Sleep(d)
t.Log("i++ during time.Sleep:", <-ch)
}
func testDebugGCStatsBlocking(ch chan int) {
i := 0
for {
select {
case ch <- i:
return
default:
i++
}
}
}

View file

@ -17,7 +17,7 @@ type EWMA interface {
// NewEWMA constructs a new EWMA with the given alpha. // NewEWMA constructs a new EWMA with the given alpha.
func NewEWMA(alpha float64) EWMA { func NewEWMA(alpha float64) EWMA {
if UseNilMetrics { if !Enabled {
return NilEWMA{} return NilEWMA{}
} }
return &StandardEWMA{alpha: alpha} return &StandardEWMA{alpha: alpha}

225
metrics/ewma_test.go Normal file
View file

@ -0,0 +1,225 @@
package metrics
import "testing"
func BenchmarkEWMA(b *testing.B) {
a := NewEWMA1()
b.ResetTimer()
for i := 0; i < b.N; i++ {
a.Update(1)
a.Tick()
}
}
func TestEWMA1(t *testing.T) {
a := NewEWMA1()
a.Update(3)
a.Tick()
if rate := a.Rate(); 0.6 != rate {
t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.22072766470286553 != rate {
t.Errorf("1 minute a.Rate(): 0.22072766470286553 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.08120116994196772 != rate {
t.Errorf("2 minute a.Rate(): 0.08120116994196772 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.029872241020718428 != rate {
t.Errorf("3 minute a.Rate(): 0.029872241020718428 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.01098938333324054 != rate {
t.Errorf("4 minute a.Rate(): 0.01098938333324054 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.004042768199451294 != rate {
t.Errorf("5 minute a.Rate(): 0.004042768199451294 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.0014872513059998212 != rate {
t.Errorf("6 minute a.Rate(): 0.0014872513059998212 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.0005471291793327122 != rate {
t.Errorf("7 minute a.Rate(): 0.0005471291793327122 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.00020127757674150815 != rate {
t.Errorf("8 minute a.Rate(): 0.00020127757674150815 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 7.404588245200814e-05 != rate {
t.Errorf("9 minute a.Rate(): 7.404588245200814e-05 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 2.7239957857491083e-05 != rate {
t.Errorf("10 minute a.Rate(): 2.7239957857491083e-05 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 1.0021020474147462e-05 != rate {
t.Errorf("11 minute a.Rate(): 1.0021020474147462e-05 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 3.6865274119969525e-06 != rate {
t.Errorf("12 minute a.Rate(): 3.6865274119969525e-06 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 1.3561976441886433e-06 != rate {
t.Errorf("13 minute a.Rate(): 1.3561976441886433e-06 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 4.989172314621449e-07 != rate {
t.Errorf("14 minute a.Rate(): 4.989172314621449e-07 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 1.8354139230109722e-07 != rate {
t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate)
}
}
func TestEWMA5(t *testing.T) {
a := NewEWMA5()
a.Update(3)
a.Tick()
if rate := a.Rate(); 0.6 != rate {
t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.49123845184678905 != rate {
t.Errorf("1 minute a.Rate(): 0.49123845184678905 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.4021920276213837 != rate {
t.Errorf("2 minute a.Rate(): 0.4021920276213837 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.32928698165641596 != rate {
t.Errorf("3 minute a.Rate(): 0.32928698165641596 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.269597378470333 != rate {
t.Errorf("4 minute a.Rate(): 0.269597378470333 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.2207276647028654 != rate {
t.Errorf("5 minute a.Rate(): 0.2207276647028654 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.18071652714732128 != rate {
t.Errorf("6 minute a.Rate(): 0.18071652714732128 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.14795817836496392 != rate {
t.Errorf("7 minute a.Rate(): 0.14795817836496392 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.12113791079679326 != rate {
t.Errorf("8 minute a.Rate(): 0.12113791079679326 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.09917933293295193 != rate {
t.Errorf("9 minute a.Rate(): 0.09917933293295193 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.08120116994196763 != rate {
t.Errorf("10 minute a.Rate(): 0.08120116994196763 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.06648189501740036 != rate {
t.Errorf("11 minute a.Rate(): 0.06648189501740036 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.05443077197364752 != rate {
t.Errorf("12 minute a.Rate(): 0.05443077197364752 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.04456414692860035 != rate {
t.Errorf("13 minute a.Rate(): 0.04456414692860035 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.03648603757513079 != rate {
t.Errorf("14 minute a.Rate(): 0.03648603757513079 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.0298722410207183831020718428 != rate {
t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate)
}
}
func TestEWMA15(t *testing.T) {
a := NewEWMA15()
a.Update(3)
a.Tick()
if rate := a.Rate(); 0.6 != rate {
t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.5613041910189706 != rate {
t.Errorf("1 minute a.Rate(): 0.5613041910189706 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.5251039914257684 != rate {
t.Errorf("2 minute a.Rate(): 0.5251039914257684 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.4912384518467888184678905 != rate {
t.Errorf("3 minute a.Rate(): 0.4912384518467888184678905 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.459557003018789 != rate {
t.Errorf("4 minute a.Rate(): 0.459557003018789 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.4299187863442732 != rate {
t.Errorf("5 minute a.Rate(): 0.4299187863442732 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.4021920276213831 != rate {
t.Errorf("6 minute a.Rate(): 0.4021920276213831 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.37625345116383313 != rate {
t.Errorf("7 minute a.Rate(): 0.37625345116383313 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.3519877317060185 != rate {
t.Errorf("8 minute a.Rate(): 0.3519877317060185 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.3292869816564153165641596 != rate {
t.Errorf("9 minute a.Rate(): 0.3292869816564153165641596 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.3080502714195546 != rate {
t.Errorf("10 minute a.Rate(): 0.3080502714195546 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.2881831806538789 != rate {
t.Errorf("11 minute a.Rate(): 0.2881831806538789 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.26959737847033216 != rate {
t.Errorf("12 minute a.Rate(): 0.26959737847033216 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.2522102307052083 != rate {
t.Errorf("13 minute a.Rate(): 0.2522102307052083 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.23594443252115815 != rate {
t.Errorf("14 minute a.Rate(): 0.23594443252115815 != %v\n", rate)
}
elapseMinute(a)
if rate := a.Rate(); 0.2207276647028646247028654470286553 != rate {
t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate)
}
}
func elapseMinute(a EWMA) {
for i := 0; i < 12; i++ {
a.Tick()
}
}

View file

@ -8,7 +8,7 @@ import (
"net/http" "net/http"
"sync" "sync"
"github.com/rcrowley/go-metrics" "github.com/ethereum/go-ethereum/metrics"
) )
type exp struct { type exp struct {
@ -97,22 +97,22 @@ func (exp *exp) publishHistogram(name string, metric metrics.Histogram) {
exp.getInt(name + ".count").Set(h.Count()) exp.getInt(name + ".count").Set(h.Count())
exp.getFloat(name + ".min").Set(float64(h.Min())) exp.getFloat(name + ".min").Set(float64(h.Min()))
exp.getFloat(name + ".max").Set(float64(h.Max())) exp.getFloat(name + ".max").Set(float64(h.Max()))
exp.getFloat(name + ".mean").Set(float64(h.Mean())) exp.getFloat(name + ".mean").Set(h.Mean())
exp.getFloat(name + ".std-dev").Set(float64(h.StdDev())) exp.getFloat(name + ".std-dev").Set(h.StdDev())
exp.getFloat(name + ".50-percentile").Set(float64(ps[0])) exp.getFloat(name + ".50-percentile").Set(ps[0])
exp.getFloat(name + ".75-percentile").Set(float64(ps[1])) exp.getFloat(name + ".75-percentile").Set(ps[1])
exp.getFloat(name + ".95-percentile").Set(float64(ps[2])) exp.getFloat(name + ".95-percentile").Set(ps[2])
exp.getFloat(name + ".99-percentile").Set(float64(ps[3])) exp.getFloat(name + ".99-percentile").Set(ps[3])
exp.getFloat(name + ".999-percentile").Set(float64(ps[4])) exp.getFloat(name + ".999-percentile").Set(ps[4])
} }
func (exp *exp) publishMeter(name string, metric metrics.Meter) { func (exp *exp) publishMeter(name string, metric metrics.Meter) {
m := metric.Snapshot() m := metric.Snapshot()
exp.getInt(name + ".count").Set(m.Count()) exp.getInt(name + ".count").Set(m.Count())
exp.getFloat(name + ".one-minute").Set(float64(m.Rate1())) exp.getFloat(name + ".one-minute").Set(m.Rate1())
exp.getFloat(name + ".five-minute").Set(float64(m.Rate5())) exp.getFloat(name + ".five-minute").Set(m.Rate5())
exp.getFloat(name + ".fifteen-minute").Set(float64((m.Rate15()))) exp.getFloat(name + ".fifteen-minute").Set((m.Rate15()))
exp.getFloat(name + ".mean").Set(float64(m.RateMean())) exp.getFloat(name + ".mean").Set(m.RateMean())
} }
func (exp *exp) publishTimer(name string, metric metrics.Timer) { func (exp *exp) publishTimer(name string, metric metrics.Timer) {
@ -121,17 +121,17 @@ func (exp *exp) publishTimer(name string, metric metrics.Timer) {
exp.getInt(name + ".count").Set(t.Count()) exp.getInt(name + ".count").Set(t.Count())
exp.getFloat(name + ".min").Set(float64(t.Min())) exp.getFloat(name + ".min").Set(float64(t.Min()))
exp.getFloat(name + ".max").Set(float64(t.Max())) exp.getFloat(name + ".max").Set(float64(t.Max()))
exp.getFloat(name + ".mean").Set(float64(t.Mean())) exp.getFloat(name + ".mean").Set(t.Mean())
exp.getFloat(name + ".std-dev").Set(float64(t.StdDev())) exp.getFloat(name + ".std-dev").Set(t.StdDev())
exp.getFloat(name + ".50-percentile").Set(float64(ps[0])) exp.getFloat(name + ".50-percentile").Set(ps[0])
exp.getFloat(name + ".75-percentile").Set(float64(ps[1])) exp.getFloat(name + ".75-percentile").Set(ps[1])
exp.getFloat(name + ".95-percentile").Set(float64(ps[2])) exp.getFloat(name + ".95-percentile").Set(ps[2])
exp.getFloat(name + ".99-percentile").Set(float64(ps[3])) exp.getFloat(name + ".99-percentile").Set(ps[3])
exp.getFloat(name + ".999-percentile").Set(float64(ps[4])) exp.getFloat(name + ".999-percentile").Set(ps[4])
exp.getFloat(name + ".one-minute").Set(float64(t.Rate1())) exp.getFloat(name + ".one-minute").Set(t.Rate1())
exp.getFloat(name + ".five-minute").Set(float64(t.Rate5())) exp.getFloat(name + ".five-minute").Set(t.Rate5())
exp.getFloat(name + ".fifteen-minute").Set(float64((t.Rate15()))) exp.getFloat(name + ".fifteen-minute").Set(t.Rate15())
exp.getFloat(name + ".mean-rate").Set(float64(t.RateMean())) exp.getFloat(name + ".mean-rate").Set(t.RateMean())
} }
func (exp *exp) syncToExpvar() { func (exp *exp) syncToExpvar() {

View file

@ -20,7 +20,7 @@ func GetOrRegisterGauge(name string, r Registry) Gauge {
// NewGauge constructs a new StandardGauge. // NewGauge constructs a new StandardGauge.
func NewGauge() Gauge { func NewGauge() Gauge {
if UseNilMetrics { if !Enabled {
return NilGauge{} return NilGauge{}
} }
return &StandardGauge{0} return &StandardGauge{0}
@ -38,7 +38,7 @@ func NewRegisteredGauge(name string, r Registry) Gauge {
// NewFunctionalGauge constructs a new FunctionalGauge. // NewFunctionalGauge constructs a new FunctionalGauge.
func NewFunctionalGauge(f func() int64) Gauge { func NewFunctionalGauge(f func() int64) Gauge {
if UseNilMetrics { if !Enabled {
return NilGauge{} return NilGauge{}
} }
return &FunctionalGauge{value: f} return &FunctionalGauge{value: f}

View file

@ -20,7 +20,7 @@ func GetOrRegisterGaugeFloat64(name string, r Registry) GaugeFloat64 {
// NewGaugeFloat64 constructs a new StandardGaugeFloat64. // NewGaugeFloat64 constructs a new StandardGaugeFloat64.
func NewGaugeFloat64() GaugeFloat64 { func NewGaugeFloat64() GaugeFloat64 {
if UseNilMetrics { if !Enabled {
return NilGaugeFloat64{} return NilGaugeFloat64{}
} }
return &StandardGaugeFloat64{ return &StandardGaugeFloat64{
@ -40,7 +40,7 @@ func NewRegisteredGaugeFloat64(name string, r Registry) GaugeFloat64 {
// NewFunctionalGauge constructs a new FunctionalGauge. // NewFunctionalGauge constructs a new FunctionalGauge.
func NewFunctionalGaugeFloat64(f func() float64) GaugeFloat64 { func NewFunctionalGaugeFloat64(f func() float64) GaugeFloat64 {
if UseNilMetrics { if !Enabled {
return NilGaugeFloat64{} return NilGaugeFloat64{}
} }
return &FunctionalGaugeFloat64{value: f} return &FunctionalGaugeFloat64{value: f}

View file

@ -0,0 +1,59 @@
package metrics
import "testing"
func BenchmarkGuageFloat64(b *testing.B) {
g := NewGaugeFloat64()
b.ResetTimer()
for i := 0; i < b.N; i++ {
g.Update(float64(i))
}
}
func TestGaugeFloat64(t *testing.T) {
g := NewGaugeFloat64()
g.Update(float64(47.0))
if v := g.Value(); float64(47.0) != v {
t.Errorf("g.Value(): 47.0 != %v\n", v)
}
}
func TestGaugeFloat64Snapshot(t *testing.T) {
g := NewGaugeFloat64()
g.Update(float64(47.0))
snapshot := g.Snapshot()
g.Update(float64(0))
if v := snapshot.Value(); float64(47.0) != v {
t.Errorf("g.Value(): 47.0 != %v\n", v)
}
}
func TestGetOrRegisterGaugeFloat64(t *testing.T) {
r := NewRegistry()
NewRegisteredGaugeFloat64("foo", r).Update(float64(47.0))
t.Logf("registry: %v", r)
if g := GetOrRegisterGaugeFloat64("foo", r); float64(47.0) != g.Value() {
t.Fatal(g)
}
}
func TestFunctionalGaugeFloat64(t *testing.T) {
var counter float64
fg := NewFunctionalGaugeFloat64(func() float64 {
counter++
return counter
})
fg.Value()
fg.Value()
if counter != 2 {
t.Error("counter != 2")
}
}
func TestGetOrRegisterFunctionalGaugeFloat64(t *testing.T) {
r := NewRegistry()
NewRegisteredFunctionalGaugeFloat64("foo", r, func() float64 { return 47 })
if g := GetOrRegisterGaugeFloat64("foo", r); 47 != g.Value() {
t.Fatal(g)
}
}

68
metrics/gauge_test.go Normal file
View file

@ -0,0 +1,68 @@
package metrics
import (
"fmt"
"testing"
)
func BenchmarkGuage(b *testing.B) {
g := NewGauge()
b.ResetTimer()
for i := 0; i < b.N; i++ {
g.Update(int64(i))
}
}
func TestGauge(t *testing.T) {
g := NewGauge()
g.Update(int64(47))
if v := g.Value(); 47 != v {
t.Errorf("g.Value(): 47 != %v\n", v)
}
}
func TestGaugeSnapshot(t *testing.T) {
g := NewGauge()
g.Update(int64(47))
snapshot := g.Snapshot()
g.Update(int64(0))
if v := snapshot.Value(); 47 != v {
t.Errorf("g.Value(): 47 != %v\n", v)
}
}
func TestGetOrRegisterGauge(t *testing.T) {
r := NewRegistry()
NewRegisteredGauge("foo", r).Update(47)
if g := GetOrRegisterGauge("foo", r); 47 != g.Value() {
t.Fatal(g)
}
}
func TestFunctionalGauge(t *testing.T) {
var counter int64
fg := NewFunctionalGauge(func() int64 {
counter++
return counter
})
fg.Value()
fg.Value()
if counter != 2 {
t.Error("counter != 2")
}
}
func TestGetOrRegisterFunctionalGauge(t *testing.T) {
r := NewRegistry()
NewRegisteredFunctionalGauge("foo", r, func() int64 { return 47 })
if g := GetOrRegisterGauge("foo", r); 47 != g.Value() {
t.Fatal(g)
}
}
func ExampleGetOrRegisterGauge() {
m := "server.bytes_sent"
g := GetOrRegisterGauge(m, nil)
g.Update(47)
fmt.Println(g.Value()) // Output: 47
}

View file

@ -39,7 +39,7 @@ func Graphite(r Registry, d time.Duration, prefix string, addr *net.TCPAddr) {
// but it takes a GraphiteConfig instead. // but it takes a GraphiteConfig instead.
func GraphiteWithConfig(c GraphiteConfig) { func GraphiteWithConfig(c GraphiteConfig) {
log.Printf("WARNING: This go-metrics client has been DEPRECATED! It has been moved to https://github.com/cyberdelia/go-metrics-graphite and will be removed from rcrowley/go-metrics on August 12th 2015") log.Printf("WARNING: This go-metrics client has been DEPRECATED! It has been moved to https://github.com/cyberdelia/go-metrics-graphite and will be removed from rcrowley/go-metrics on August 12th 2015")
for _ = range time.Tick(c.FlushInterval) { for range time.Tick(c.FlushInterval) {
if err := graphite(&c); nil != err { if err := graphite(&c); nil != err {
log.Println(err) log.Println(err)
} }

22
metrics/graphite_test.go Normal file
View file

@ -0,0 +1,22 @@
package metrics
import (
"net"
"time"
)
func ExampleGraphite() {
addr, _ := net.ResolveTCPAddr("net", ":2003")
go Graphite(DefaultRegistry, 1*time.Second, "some.prefix", addr)
}
func ExampleGraphiteWithConfig() {
addr, _ := net.ResolveTCPAddr("net", ":2003")
go GraphiteWithConfig(GraphiteConfig{
Addr: addr,
Registry: DefaultRegistry,
FlushInterval: 1 * time.Second,
DurationUnit: time.Millisecond,
Percentiles: []float64{0.5, 0.75, 0.99, 0.999},
})
}

View file

@ -11,7 +11,7 @@ type Healthcheck interface {
// NewHealthcheck constructs a new Healthcheck which will use the given // NewHealthcheck constructs a new Healthcheck which will use the given
// function to update its status. // function to update its status.
func NewHealthcheck(f func(Healthcheck)) Healthcheck { func NewHealthcheck(f func(Healthcheck)) Healthcheck {
if UseNilMetrics { if !Enabled {
return NilHealthcheck{} return NilHealthcheck{}
} }
return &StandardHealthcheck{nil, f} return &StandardHealthcheck{nil, f}

View file

@ -28,7 +28,7 @@ func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram {
// NewHistogram constructs a new StandardHistogram from a Sample. // NewHistogram constructs a new StandardHistogram from a Sample.
func NewHistogram(s Sample) Histogram { func NewHistogram(s Sample) Histogram {
if UseNilMetrics { if !Enabled {
return NilHistogram{} return NilHistogram{}
} }
return &StandardHistogram{sample: s} return &StandardHistogram{sample: s}

95
metrics/histogram_test.go Normal file
View file

@ -0,0 +1,95 @@
package metrics
import "testing"
func BenchmarkHistogram(b *testing.B) {
h := NewHistogram(NewUniformSample(100))
b.ResetTimer()
for i := 0; i < b.N; i++ {
h.Update(int64(i))
}
}
func TestGetOrRegisterHistogram(t *testing.T) {
r := NewRegistry()
s := NewUniformSample(100)
NewRegisteredHistogram("foo", r, s).Update(47)
if h := GetOrRegisterHistogram("foo", r, s); 1 != h.Count() {
t.Fatal(h)
}
}
func TestHistogram10000(t *testing.T) {
h := NewHistogram(NewUniformSample(100000))
for i := 1; i <= 10000; i++ {
h.Update(int64(i))
}
testHistogram10000(t, h)
}
func TestHistogramEmpty(t *testing.T) {
h := NewHistogram(NewUniformSample(100))
if count := h.Count(); 0 != count {
t.Errorf("h.Count(): 0 != %v\n", count)
}
if min := h.Min(); 0 != min {
t.Errorf("h.Min(): 0 != %v\n", min)
}
if max := h.Max(); 0 != max {
t.Errorf("h.Max(): 0 != %v\n", max)
}
if mean := h.Mean(); 0.0 != mean {
t.Errorf("h.Mean(): 0.0 != %v\n", mean)
}
if stdDev := h.StdDev(); 0.0 != stdDev {
t.Errorf("h.StdDev(): 0.0 != %v\n", stdDev)
}
ps := h.Percentiles([]float64{0.5, 0.75, 0.99})
if 0.0 != ps[0] {
t.Errorf("median: 0.0 != %v\n", ps[0])
}
if 0.0 != ps[1] {
t.Errorf("75th percentile: 0.0 != %v\n", ps[1])
}
if 0.0 != ps[2] {
t.Errorf("99th percentile: 0.0 != %v\n", ps[2])
}
}
func TestHistogramSnapshot(t *testing.T) {
h := NewHistogram(NewUniformSample(100000))
for i := 1; i <= 10000; i++ {
h.Update(int64(i))
}
snapshot := h.Snapshot()
h.Update(0)
testHistogram10000(t, snapshot)
}
func testHistogram10000(t *testing.T, h Histogram) {
if count := h.Count(); 10000 != count {
t.Errorf("h.Count(): 10000 != %v\n", count)
}
if min := h.Min(); 1 != min {
t.Errorf("h.Min(): 1 != %v\n", min)
}
if max := h.Max(); 10000 != max {
t.Errorf("h.Max(): 10000 != %v\n", max)
}
if mean := h.Mean(); 5000.5 != mean {
t.Errorf("h.Mean(): 5000.5 != %v\n", mean)
}
if stdDev := h.StdDev(); 2886.751331514372 != stdDev {
t.Errorf("h.StdDev(): 2886.751331514372 != %v\n", stdDev)
}
ps := h.Percentiles([]float64{0.5, 0.75, 0.99})
if 5000.5 != ps[0] {
t.Errorf("median: 5000.5 != %v\n", ps[0])
}
if 7500.75 != ps[1] {
t.Errorf("75th percentile: 7500.75 != %v\n", ps[1])
}
if 9900.99 != ps[2] {
t.Errorf("99th percentile: 9900.99 != %v\n", ps[2])
}
}

19
metrics/influxdb/LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2015 Vincent Rischmann
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.

View file

@ -0,0 +1,30 @@
go-metrics-influxdb
===================
This is a reporter for the [go-metrics](https://github.com/rcrowley/go-metrics) library which will post the metrics to [InfluxDB](https://influxdb.com/).
Note
----
This is only compatible with InfluxDB 0.9+.
Usage
-----
```go
import "github.com/vrischmann/go-metrics-influxdb"
go influxdb.InfluxDB(
metrics.DefaultRegistry, // metrics registry
time.Second * 10, // interval
"http://localhost:8086", // the InfluxDB url
"mydb", // your InfluxDB database
"myuser", // your InfluxDB user
"mypassword", // your InfluxDB password
)
```
License
-------
go-metrics-influxdb is licensed under the MIT license. See the LICENSE file for details.

View file

@ -0,0 +1,227 @@
package influxdb
import (
"fmt"
"log"
uurl "net/url"
"time"
"github.com/ethereum/go-ethereum/metrics"
"github.com/influxdata/influxdb/client"
)
type reporter struct {
reg metrics.Registry
interval time.Duration
url uurl.URL
database string
username string
password string
namespace string
tags map[string]string
client *client.Client
cache map[string]int64
}
// InfluxDB starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval.
func InfluxDB(r metrics.Registry, d time.Duration, url, database, username, password, namespace string) {
InfluxDBWithTags(r, d, url, database, username, password, namespace, nil)
}
// InfluxDBWithTags starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval with the specified tags
func InfluxDBWithTags(r metrics.Registry, d time.Duration, url, database, username, password, namespace string, tags map[string]string) {
u, err := uurl.Parse(url)
if err != nil {
log.Printf("unable to parse InfluxDB url %s. err=%v", url, err)
return
}
rep := &reporter{
reg: r,
interval: d,
url: *u,
database: database,
username: username,
password: password,
namespace: namespace,
tags: tags,
cache: make(map[string]int64),
}
if err := rep.makeClient(); err != nil {
log.Printf("unable to make InfluxDB client. err=%v", err)
return
}
rep.run()
}
func (r *reporter) makeClient() (err error) {
r.client, err = client.NewClient(client.Config{
URL: r.url,
Username: r.username,
Password: r.password,
})
return
}
func (r *reporter) run() {
intervalTicker := time.Tick(r.interval)
pingTicker := time.Tick(time.Second * 5)
for {
select {
case <-intervalTicker:
if err := r.send(); err != nil {
log.Printf("unable to send to InfluxDB. err=%v", err)
}
case <-pingTicker:
_, _, err := r.client.Ping()
if err != nil {
log.Printf("got error while sending a ping to InfluxDB, trying to recreate client. err=%v", err)
if err = r.makeClient(); err != nil {
log.Printf("unable to make InfluxDB client. err=%v", err)
}
}
}
}
}
func (r *reporter) send() error {
var pts []client.Point
r.reg.Each(func(name string, i interface{}) {
now := time.Now()
namespace := r.namespace
switch metric := i.(type) {
case metrics.Counter:
v := metric.Count()
l := r.cache[name]
pts = append(pts, client.Point{
Measurement: fmt.Sprintf("%s%s.count", namespace, name),
Tags: r.tags,
Fields: map[string]interface{}{
"value": v - l,
},
Time: now,
})
r.cache[name] = v
case metrics.Gauge:
ms := metric.Snapshot()
pts = append(pts, client.Point{
Measurement: fmt.Sprintf("%s%s.gauge", namespace, name),
Tags: r.tags,
Fields: map[string]interface{}{
"value": ms.Value(),
},
Time: now,
})
case metrics.GaugeFloat64:
ms := metric.Snapshot()
pts = append(pts, client.Point{
Measurement: fmt.Sprintf("%s%s.gauge", namespace, name),
Tags: r.tags,
Fields: map[string]interface{}{
"value": ms.Value(),
},
Time: now,
})
case metrics.Histogram:
ms := metric.Snapshot()
ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999})
pts = append(pts, client.Point{
Measurement: fmt.Sprintf("%s%s.histogram", namespace, name),
Tags: r.tags,
Fields: map[string]interface{}{
"count": ms.Count(),
"max": ms.Max(),
"mean": ms.Mean(),
"min": ms.Min(),
"stddev": ms.StdDev(),
"variance": ms.Variance(),
"p50": ps[0],
"p75": ps[1],
"p95": ps[2],
"p99": ps[3],
"p999": ps[4],
"p9999": ps[5],
},
Time: now,
})
case metrics.Meter:
ms := metric.Snapshot()
pts = append(pts, client.Point{
Measurement: fmt.Sprintf("%s%s.meter", namespace, name),
Tags: r.tags,
Fields: map[string]interface{}{
"count": ms.Count(),
"m1": ms.Rate1(),
"m5": ms.Rate5(),
"m15": ms.Rate15(),
"mean": ms.RateMean(),
},
Time: now,
})
case metrics.Timer:
ms := metric.Snapshot()
ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999})
pts = append(pts, client.Point{
Measurement: fmt.Sprintf("%s%s.timer", namespace, name),
Tags: r.tags,
Fields: map[string]interface{}{
"count": ms.Count(),
"max": ms.Max(),
"mean": ms.Mean(),
"min": ms.Min(),
"stddev": ms.StdDev(),
"variance": ms.Variance(),
"p50": ps[0],
"p75": ps[1],
"p95": ps[2],
"p99": ps[3],
"p999": ps[4],
"p9999": ps[5],
"m1": ms.Rate1(),
"m5": ms.Rate5(),
"m15": ms.Rate15(),
"meanrate": ms.RateMean(),
},
Time: now,
})
case metrics.ResettingTimer:
t := metric.Snapshot()
if len(t.Values()) > 0 {
ps := t.Percentiles([]float64{50, 95, 99})
val := t.Values()
pts = append(pts, client.Point{
Measurement: fmt.Sprintf("%s%s.span", namespace, name),
Tags: r.tags,
Fields: map[string]interface{}{
"count": len(val),
"max": val[len(val)-1],
"mean": t.Mean(),
"min": val[0],
"p50": ps[0],
"p95": ps[1],
"p99": ps[2],
},
Time: now,
})
}
}
})
bps := client.BatchPoints{
Points: pts,
Database: r.database,
}
_, err := r.client.Write(bps)
return err
}

5
metrics/init_test.go Normal file
View file

@ -0,0 +1,5 @@
package metrics
func init() {
Enabled = true
}

31
metrics/json.go Normal file
View file

@ -0,0 +1,31 @@
package metrics
import (
"encoding/json"
"io"
"time"
)
// MarshalJSON returns a byte slice containing a JSON representation of all
// the metrics in the Registry.
func (r *StandardRegistry) MarshalJSON() ([]byte, error) {
return json.Marshal(r.GetAll())
}
// WriteJSON writes metrics from the given registry periodically to the
// specified io.Writer as JSON.
func WriteJSON(r Registry, d time.Duration, w io.Writer) {
for range time.Tick(d) {
WriteJSONOnce(r, w)
}
}
// WriteJSONOnce writes metrics from the given registry to the specified
// io.Writer as JSON.
func WriteJSONOnce(r Registry, w io.Writer) {
json.NewEncoder(w).Encode(r)
}
func (p *PrefixedRegistry) MarshalJSON() ([]byte, error) {
return json.Marshal(p.GetAll())
}

28
metrics/json_test.go Normal file
View file

@ -0,0 +1,28 @@
package metrics
import (
"bytes"
"encoding/json"
"testing"
)
func TestRegistryMarshallJSON(t *testing.T) {
b := &bytes.Buffer{}
enc := json.NewEncoder(b)
r := NewRegistry()
r.Register("counter", NewCounter())
enc.Encode(r)
if s := b.String(); "{\"counter\":{\"count\":0}}\n" != s {
t.Fatalf(s)
}
}
func TestRegistryWriteJSONOnce(t *testing.T) {
r := NewRegistry()
r.Register("counter", NewCounter())
b := &bytes.Buffer{}
WriteJSONOnce(r, b)
if s := b.String(); s != "{\"counter\":{\"count\":0}}\n" {
t.Fail()
}
}

102
metrics/librato/client.go Normal file
View file

@ -0,0 +1,102 @@
package librato
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
const Operations = "operations"
const OperationsShort = "ops"
type LibratoClient struct {
Email, Token string
}
// property strings
const (
// display attributes
Color = "color"
DisplayMax = "display_max"
DisplayMin = "display_min"
DisplayUnitsLong = "display_units_long"
DisplayUnitsShort = "display_units_short"
DisplayStacked = "display_stacked"
DisplayTransform = "display_transform"
// special gauge display attributes
SummarizeFunction = "summarize_function"
Aggregate = "aggregate"
// metric keys
Name = "name"
Period = "period"
Description = "description"
DisplayName = "display_name"
Attributes = "attributes"
// measurement keys
MeasureTime = "measure_time"
Source = "source"
Value = "value"
// special gauge keys
Count = "count"
Sum = "sum"
Max = "max"
Min = "min"
SumSquares = "sum_squares"
// batch keys
Counters = "counters"
Gauges = "gauges"
MetricsPostUrl = "https://metrics-api.librato.com/v1/metrics"
)
type Measurement map[string]interface{}
type Metric map[string]interface{}
type Batch struct {
Gauges []Measurement `json:"gauges,omitempty"`
Counters []Measurement `json:"counters,omitempty"`
MeasureTime int64 `json:"measure_time"`
Source string `json:"source"`
}
func (self *LibratoClient) PostMetrics(batch Batch) (err error) {
var (
js []byte
req *http.Request
resp *http.Response
)
if len(batch.Counters) == 0 && len(batch.Gauges) == 0 {
return nil
}
if js, err = json.Marshal(batch); err != nil {
return
}
if req, err = http.NewRequest("POST", MetricsPostUrl, bytes.NewBuffer(js)); err != nil {
return
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(self.Email, self.Token)
if resp, err = http.DefaultClient.Do(req); err != nil {
return
}
if resp.StatusCode != http.StatusOK {
var body []byte
if body, err = ioutil.ReadAll(resp.Body); err != nil {
body = []byte(fmt.Sprintf("(could not fetch response body for error: %s)", err))
}
err = fmt.Errorf("Unable to post to Librato: %d %s %s", resp.StatusCode, resp.Status, string(body))
}
return
}

235
metrics/librato/librato.go Normal file
View file

@ -0,0 +1,235 @@
package librato
import (
"fmt"
"log"
"math"
"regexp"
"time"
"github.com/ethereum/go-ethereum/metrics"
)
// a regexp for extracting the unit from time.Duration.String
var unitRegexp = regexp.MustCompile(`[^\\d]+$`)
// a helper that turns a time.Duration into librato display attributes for timer metrics
func translateTimerAttributes(d time.Duration) (attrs map[string]interface{}) {
attrs = make(map[string]interface{})
attrs[DisplayTransform] = fmt.Sprintf("x/%d", int64(d))
attrs[DisplayUnitsShort] = string(unitRegexp.Find([]byte(d.String())))
return
}
type Reporter struct {
Email, Token string
Namespace string
Source string
Interval time.Duration
Registry metrics.Registry
Percentiles []float64 // percentiles to report on histogram metrics
TimerAttributes map[string]interface{} // units in which timers will be displayed
intervalSec int64
}
func NewReporter(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) *Reporter {
return &Reporter{e, t, "", s, d, r, p, translateTimerAttributes(u), int64(d / time.Second)}
}
func Librato(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) {
NewReporter(r, d, e, t, s, p, u).Run()
}
func (self *Reporter) Run() {
log.Printf("WARNING: This client has been DEPRECATED! It has been moved to https://github.com/mihasya/go-metrics-librato and will be removed from rcrowley/go-metrics on August 5th 2015")
ticker := time.Tick(self.Interval)
metricsApi := &LibratoClient{self.Email, self.Token}
for now := range ticker {
var metrics Batch
var err error
if metrics, err = self.BuildRequest(now, self.Registry); err != nil {
log.Printf("ERROR constructing librato request body %s", err)
continue
}
if err := metricsApi.PostMetrics(metrics); err != nil {
log.Printf("ERROR sending metrics to librato %s", err)
continue
}
}
}
// calculate sum of squares from data provided by metrics.Histogram
// see http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
func sumSquares(s metrics.Sample) float64 {
count := float64(s.Count())
sumSquared := math.Pow(count*s.Mean(), 2)
sumSquares := math.Pow(count*s.StdDev(), 2) + sumSquared/count
if math.IsNaN(sumSquares) {
return 0.0
}
return sumSquares
}
func sumSquaresTimer(t metrics.Timer) float64 {
count := float64(t.Count())
sumSquared := math.Pow(count*t.Mean(), 2)
sumSquares := math.Pow(count*t.StdDev(), 2) + sumSquared/count
if math.IsNaN(sumSquares) {
return 0.0
}
return sumSquares
}
func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Batch, err error) {
snapshot = Batch{
// coerce timestamps to a stepping fn so that they line up in Librato graphs
MeasureTime: (now.Unix() / self.intervalSec) * self.intervalSec,
Source: self.Source,
}
snapshot.Gauges = make([]Measurement, 0)
snapshot.Counters = make([]Measurement, 0)
histogramGaugeCount := 1 + len(self.Percentiles)
r.Each(func(name string, metric interface{}) {
if self.Namespace != "" {
name = fmt.Sprintf("%s.%s", self.Namespace, name)
}
measurement := Measurement{}
measurement[Period] = self.Interval.Seconds()
switch m := metric.(type) {
case metrics.Counter:
if m.Count() > 0 {
measurement[Name] = fmt.Sprintf("%s.%s", name, "count")
measurement[Value] = float64(m.Count())
measurement[Attributes] = map[string]interface{}{
DisplayUnitsLong: Operations,
DisplayUnitsShort: OperationsShort,
DisplayMin: "0",
}
snapshot.Counters = append(snapshot.Counters, measurement)
}
case metrics.Gauge:
measurement[Name] = name
measurement[Value] = float64(m.Value())
snapshot.Gauges = append(snapshot.Gauges, measurement)
case metrics.GaugeFloat64:
measurement[Name] = name
measurement[Value] = m.Value()
snapshot.Gauges = append(snapshot.Gauges, measurement)
case metrics.Histogram:
if m.Count() > 0 {
gauges := make([]Measurement, histogramGaugeCount)
s := m.Sample()
measurement[Name] = fmt.Sprintf("%s.%s", name, "hist")
measurement[Count] = uint64(s.Count())
measurement[Max] = float64(s.Max())
measurement[Min] = float64(s.Min())
measurement[Sum] = float64(s.Sum())
measurement[SumSquares] = sumSquares(s)
gauges[0] = measurement
for i, p := range self.Percentiles {
gauges[i+1] = Measurement{
Name: fmt.Sprintf("%s.%.2f", measurement[Name], p),
Value: s.Percentile(p),
Period: measurement[Period],
}
}
snapshot.Gauges = append(snapshot.Gauges, gauges...)
}
case metrics.Meter:
measurement[Name] = name
measurement[Value] = float64(m.Count())
snapshot.Counters = append(snapshot.Counters, measurement)
snapshot.Gauges = append(snapshot.Gauges,
Measurement{
Name: fmt.Sprintf("%s.%s", name, "1min"),
Value: m.Rate1(),
Period: int64(self.Interval.Seconds()),
Attributes: map[string]interface{}{
DisplayUnitsLong: Operations,
DisplayUnitsShort: OperationsShort,
DisplayMin: "0",
},
},
Measurement{
Name: fmt.Sprintf("%s.%s", name, "5min"),
Value: m.Rate5(),
Period: int64(self.Interval.Seconds()),
Attributes: map[string]interface{}{
DisplayUnitsLong: Operations,
DisplayUnitsShort: OperationsShort,
DisplayMin: "0",
},
},
Measurement{
Name: fmt.Sprintf("%s.%s", name, "15min"),
Value: m.Rate15(),
Period: int64(self.Interval.Seconds()),
Attributes: map[string]interface{}{
DisplayUnitsLong: Operations,
DisplayUnitsShort: OperationsShort,
DisplayMin: "0",
},
},
)
case metrics.Timer:
measurement[Name] = name
measurement[Value] = float64(m.Count())
snapshot.Counters = append(snapshot.Counters, measurement)
if m.Count() > 0 {
libratoName := fmt.Sprintf("%s.%s", name, "timer.mean")
gauges := make([]Measurement, histogramGaugeCount)
gauges[0] = Measurement{
Name: libratoName,
Count: uint64(m.Count()),
Sum: m.Mean() * float64(m.Count()),
Max: float64(m.Max()),
Min: float64(m.Min()),
SumSquares: sumSquaresTimer(m),
Period: int64(self.Interval.Seconds()),
Attributes: self.TimerAttributes,
}
for i, p := range self.Percentiles {
gauges[i+1] = Measurement{
Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100),
Value: m.Percentile(p),
Period: int64(self.Interval.Seconds()),
Attributes: self.TimerAttributes,
}
}
snapshot.Gauges = append(snapshot.Gauges, gauges...)
snapshot.Gauges = append(snapshot.Gauges,
Measurement{
Name: fmt.Sprintf("%s.%s", name, "rate.1min"),
Value: m.Rate1(),
Period: int64(self.Interval.Seconds()),
Attributes: map[string]interface{}{
DisplayUnitsLong: Operations,
DisplayUnitsShort: OperationsShort,
DisplayMin: "0",
},
},
Measurement{
Name: fmt.Sprintf("%s.%s", name, "rate.5min"),
Value: m.Rate5(),
Period: int64(self.Interval.Seconds()),
Attributes: map[string]interface{}{
DisplayUnitsLong: Operations,
DisplayUnitsShort: OperationsShort,
DisplayMin: "0",
},
},
Measurement{
Name: fmt.Sprintf("%s.%s", name, "rate.15min"),
Value: m.Rate15(),
Period: int64(self.Interval.Seconds()),
Attributes: map[string]interface{}{
DisplayUnitsLong: Operations,
DisplayUnitsShort: OperationsShort,
DisplayMin: "0",
},
},
)
}
}
})
return
}

View file

@ -18,7 +18,7 @@ func LogScaled(r Registry, freq time.Duration, scale time.Duration, l Logger) {
du := float64(scale) du := float64(scale)
duSuffix := scale.String()[1:] duSuffix := scale.String()[1:]
for _ = range time.Tick(freq) { for range time.Tick(freq) {
r.Each(func(name string, i interface{}) { r.Each(func(name string, i interface{}) {
switch metric := i.(type) { switch metric := i.(type) {
case Counter: case Counter:

View file

@ -15,10 +15,13 @@ type Meter interface {
Rate15() float64 Rate15() float64
RateMean() float64 RateMean() float64
Snapshot() Meter Snapshot() Meter
Stop()
} }
// GetOrRegisterMeter returns an existing Meter or constructs and registers a // GetOrRegisterMeter returns an existing Meter or constructs and registers a
// new StandardMeter. // new StandardMeter.
// Be sure to unregister the meter from the registry once it is of no use to
// allow for garbage collection.
func GetOrRegisterMeter(name string, r Registry) Meter { func GetOrRegisterMeter(name string, r Registry) Meter {
if nil == r { if nil == r {
r = DefaultRegistry r = DefaultRegistry
@ -27,14 +30,15 @@ func GetOrRegisterMeter(name string, r Registry) Meter {
} }
// NewMeter constructs a new StandardMeter and launches a goroutine. // NewMeter constructs a new StandardMeter and launches a goroutine.
// Be sure to call Stop() once the meter is of no use to allow for garbage collection.
func NewMeter() Meter { func NewMeter() Meter {
if UseNilMetrics { if !Enabled {
return NilMeter{} return NilMeter{}
} }
m := newStandardMeter() m := newStandardMeter()
arbiter.Lock() arbiter.Lock()
defer arbiter.Unlock() defer arbiter.Unlock()
arbiter.meters = append(arbiter.meters, m) arbiter.meters[m] = struct{}{}
if !arbiter.started { if !arbiter.started {
arbiter.started = true arbiter.started = true
go arbiter.tick() go arbiter.tick()
@ -44,6 +48,8 @@ func NewMeter() Meter {
// NewMeter constructs and registers a new StandardMeter and launches a // NewMeter constructs and registers a new StandardMeter and launches a
// goroutine. // goroutine.
// Be sure to unregister the meter from the registry once it is of no use to
// allow for garbage collection.
func NewRegisteredMeter(name string, r Registry) Meter { func NewRegisteredMeter(name string, r Registry) Meter {
c := NewMeter() c := NewMeter()
if nil == r { if nil == r {
@ -86,6 +92,9 @@ func (m *MeterSnapshot) RateMean() float64 { return m.rateMean }
// Snapshot returns the snapshot. // Snapshot returns the snapshot.
func (m *MeterSnapshot) Snapshot() Meter { return m } func (m *MeterSnapshot) Snapshot() Meter { return m }
// Stop is a no-op.
func (m *MeterSnapshot) Stop() {}
// NilMeter is a no-op Meter. // NilMeter is a no-op Meter.
type NilMeter struct{} type NilMeter struct{}
@ -110,12 +119,16 @@ func (NilMeter) RateMean() float64 { return 0.0 }
// Snapshot is a no-op. // Snapshot is a no-op.
func (NilMeter) Snapshot() Meter { return NilMeter{} } func (NilMeter) Snapshot() Meter { return NilMeter{} }
// Stop is a no-op.
func (NilMeter) Stop() {}
// StandardMeter is the standard implementation of a Meter. // StandardMeter is the standard implementation of a Meter.
type StandardMeter struct { type StandardMeter struct {
lock sync.RWMutex lock sync.RWMutex
snapshot *MeterSnapshot snapshot *MeterSnapshot
a1, a5, a15 EWMA a1, a5, a15 EWMA
startTime time.Time startTime time.Time
stopped bool
} }
func newStandardMeter() *StandardMeter { func newStandardMeter() *StandardMeter {
@ -128,6 +141,19 @@ func newStandardMeter() *StandardMeter {
} }
} }
// Stop stops the meter, Mark() will be a no-op if you use it after being stopped.
func (m *StandardMeter) Stop() {
m.lock.Lock()
stopped := m.stopped
m.stopped = true
m.lock.Unlock()
if !stopped {
arbiter.Lock()
delete(arbiter.meters, m)
arbiter.Unlock()
}
}
// Count returns the number of events recorded. // Count returns the number of events recorded.
func (m *StandardMeter) Count() int64 { func (m *StandardMeter) Count() int64 {
m.lock.RLock() m.lock.RLock()
@ -136,10 +162,13 @@ func (m *StandardMeter) Count() int64 {
return count return count
} }
// Mark records the occurance of n events. // Mark records the occurrence of n events.
func (m *StandardMeter) Mark(n int64) { func (m *StandardMeter) Mark(n int64) {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
if m.stopped {
return
}
m.snapshot.count += n m.snapshot.count += n
m.a1.Update(n) m.a1.Update(n)
m.a5.Update(n) m.a5.Update(n)
@ -205,29 +234,28 @@ func (m *StandardMeter) tick() {
m.updateSnapshot() m.updateSnapshot()
} }
// meterArbiter ticks meters every 5s from a single goroutine.
// meters are references in a set for future stopping.
type meterArbiter struct { type meterArbiter struct {
sync.RWMutex sync.RWMutex
started bool started bool
meters []*StandardMeter meters map[*StandardMeter]struct{}
ticker *time.Ticker ticker *time.Ticker
} }
var arbiter = meterArbiter{ticker: time.NewTicker(5e9)} var arbiter = meterArbiter{ticker: time.NewTicker(5e9), meters: make(map[*StandardMeter]struct{})}
// Ticks meters on the scheduled interval // Ticks meters on the scheduled interval
func (ma *meterArbiter) tick() { func (ma *meterArbiter) tick() {
for { for range ma.ticker.C {
select { ma.tickMeters()
case <-ma.ticker.C:
ma.tickMeters()
}
} }
} }
func (ma *meterArbiter) tickMeters() { func (ma *meterArbiter) tickMeters() {
ma.RLock() ma.RLock()
defer ma.RUnlock() defer ma.RUnlock()
for _, meter := range ma.meters { for meter := range ma.meters {
meter.tick() meter.tick()
} }
} }

73
metrics/meter_test.go Normal file
View file

@ -0,0 +1,73 @@
package metrics
import (
"testing"
"time"
)
func BenchmarkMeter(b *testing.B) {
m := NewMeter()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Mark(1)
}
}
func TestGetOrRegisterMeter(t *testing.T) {
r := NewRegistry()
NewRegisteredMeter("foo", r).Mark(47)
if m := GetOrRegisterMeter("foo", r); 47 != m.Count() {
t.Fatal(m)
}
}
func TestMeterDecay(t *testing.T) {
ma := meterArbiter{
ticker: time.NewTicker(time.Millisecond),
meters: make(map[*StandardMeter]struct{}),
}
m := newStandardMeter()
ma.meters[m] = struct{}{}
go ma.tick()
m.Mark(1)
rateMean := m.RateMean()
time.Sleep(100 * time.Millisecond)
if m.RateMean() >= rateMean {
t.Error("m.RateMean() didn't decrease")
}
}
func TestMeterNonzero(t *testing.T) {
m := NewMeter()
m.Mark(3)
if count := m.Count(); 3 != count {
t.Errorf("m.Count(): 3 != %v\n", count)
}
}
func TestMeterStop(t *testing.T) {
l := len(arbiter.meters)
m := NewMeter()
if len(arbiter.meters) != l+1 {
t.Errorf("arbiter.meters: %d != %d\n", l+1, len(arbiter.meters))
}
m.Stop()
if len(arbiter.meters) != l {
t.Errorf("arbiter.meters: %d != %d\n", l, len(arbiter.meters))
}
}
func TestMeterSnapshot(t *testing.T) {
m := NewMeter()
m.Mark(1)
if snapshot := m.Snapshot(); m.RateMean() != snapshot.RateMean() {
t.Fatal(snapshot)
}
}
func TestMeterZero(t *testing.T) {
m := NewMeter()
if count := m.Count(); 0 != count {
t.Errorf("m.Count(): 0 != %v\n", count)
}
}

View file

@ -1,20 +1,8 @@
// Copyright 2015 The go-ethereum Authors // Go port of Coda Hale's Metrics library
// This file is part of the go-ethereum library.
// //
// The go-ethereum library is free software: you can redistribute it and/or modify // <https://github.com/rcrowley/go-metrics>
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// //
// The go-ethereum library is distributed in the hope that it will be useful, // Coda Hale's original work: <https://github.com/codahale/metrics>
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package metrics provides general system and process level metrics collection.
package metrics package metrics
import ( import (
@ -24,17 +12,19 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/rcrowley/go-metrics"
"github.com/rcrowley/go-metrics/exp"
) )
// Enabled is checked by the constructor functions for all of the
// standard metrics. If it is true, the metric returned is a stub.
//
// This global kill-switch helps quantify the observer effect and makes
// for less cluttered pprof profiles.
var Enabled bool = false
// MetricsEnabledFlag is the CLI flag name to use to enable metrics collections. // MetricsEnabledFlag is the CLI flag name to use to enable metrics collections.
const MetricsEnabledFlag = "metrics" const MetricsEnabledFlag = "metrics"
const DashboardEnabledFlag = "dashboard" const DashboardEnabledFlag = "dashboard"
// Enabled is the flag specifying if metrics are enable or not.
var Enabled = false
// Init enables or disables the metrics system. Since we need this to run before // Init enables or disables the metrics system. Since we need this to run before
// any other code gets to create meters and timers, we'll actually do an ugly hack // any other code gets to create meters and timers, we'll actually do an ugly hack
// and peek into the command line args for the metrics flag. // and peek into the command line args for the metrics flag.
@ -45,34 +35,6 @@ func init() {
Enabled = true Enabled = true
} }
} }
exp.Exp(metrics.DefaultRegistry)
}
// NewCounter create a new metrics Counter, either a real one of a NOP stub depending
// on the metrics flag.
func NewCounter(name string) metrics.Counter {
if !Enabled {
return new(metrics.NilCounter)
}
return metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry)
}
// NewMeter create a new metrics Meter, either a real one of a NOP stub depending
// on the metrics flag.
func NewMeter(name string) metrics.Meter {
if !Enabled {
return new(metrics.NilMeter)
}
return metrics.GetOrRegisterMeter(name, metrics.DefaultRegistry)
}
// NewTimer create a new metrics Timer, either a real one of a NOP stub depending
// on the metrics flag.
func NewTimer(name string) metrics.Timer {
if !Enabled {
return new(metrics.NilTimer)
}
return metrics.GetOrRegisterTimer(name, metrics.DefaultRegistry)
} }
// CollectProcessMetrics periodically collects various metrics about the running // CollectProcessMetrics periodically collects various metrics about the running
@ -90,17 +52,17 @@ func CollectProcessMetrics(refresh time.Duration) {
diskstats[i] = new(DiskStats) diskstats[i] = new(DiskStats)
} }
// Define the various metrics to collect // Define the various metrics to collect
memAllocs := metrics.GetOrRegisterMeter("system/memory/allocs", metrics.DefaultRegistry) memAllocs := GetOrRegisterMeter("system/memory/allocs", DefaultRegistry)
memFrees := metrics.GetOrRegisterMeter("system/memory/frees", metrics.DefaultRegistry) memFrees := GetOrRegisterMeter("system/memory/frees", DefaultRegistry)
memInuse := metrics.GetOrRegisterMeter("system/memory/inuse", metrics.DefaultRegistry) memInuse := GetOrRegisterMeter("system/memory/inuse", DefaultRegistry)
memPauses := metrics.GetOrRegisterMeter("system/memory/pauses", metrics.DefaultRegistry) memPauses := GetOrRegisterMeter("system/memory/pauses", DefaultRegistry)
var diskReads, diskReadBytes, diskWrites, diskWriteBytes metrics.Meter var diskReads, diskReadBytes, diskWrites, diskWriteBytes Meter
if err := ReadDiskStats(diskstats[0]); err == nil { if err := ReadDiskStats(diskstats[0]); err == nil {
diskReads = metrics.GetOrRegisterMeter("system/disk/readcount", metrics.DefaultRegistry) diskReads = GetOrRegisterMeter("system/disk/readcount", DefaultRegistry)
diskReadBytes = metrics.GetOrRegisterMeter("system/disk/readdata", metrics.DefaultRegistry) diskReadBytes = GetOrRegisterMeter("system/disk/readdata", DefaultRegistry)
diskWrites = metrics.GetOrRegisterMeter("system/disk/writecount", metrics.DefaultRegistry) diskWrites = GetOrRegisterMeter("system/disk/writecount", DefaultRegistry)
diskWriteBytes = metrics.GetOrRegisterMeter("system/disk/writedata", metrics.DefaultRegistry) diskWriteBytes = GetOrRegisterMeter("system/disk/writedata", DefaultRegistry)
} else { } else {
log.Debug("Failed to read disk metrics", "err", err) log.Debug("Failed to read disk metrics", "err", err)
} }

124
metrics/metrics_test.go Normal file
View file

@ -0,0 +1,124 @@
package metrics
import (
"fmt"
"io/ioutil"
"log"
"sync"
"testing"
)
const FANOUT = 128
// Stop the compiler from complaining during debugging.
var (
_ = ioutil.Discard
_ = log.LstdFlags
)
func BenchmarkMetrics(b *testing.B) {
r := NewRegistry()
c := NewRegisteredCounter("counter", r)
g := NewRegisteredGauge("gauge", r)
gf := NewRegisteredGaugeFloat64("gaugefloat64", r)
h := NewRegisteredHistogram("histogram", r, NewUniformSample(100))
m := NewRegisteredMeter("meter", r)
t := NewRegisteredTimer("timer", r)
RegisterDebugGCStats(r)
RegisterRuntimeMemStats(r)
b.ResetTimer()
ch := make(chan bool)
wgD := &sync.WaitGroup{}
/*
wgD.Add(1)
go func() {
defer wgD.Done()
//log.Println("go CaptureDebugGCStats")
for {
select {
case <-ch:
//log.Println("done CaptureDebugGCStats")
return
default:
CaptureDebugGCStatsOnce(r)
}
}
}()
//*/
wgR := &sync.WaitGroup{}
//*
wgR.Add(1)
go func() {
defer wgR.Done()
//log.Println("go CaptureRuntimeMemStats")
for {
select {
case <-ch:
//log.Println("done CaptureRuntimeMemStats")
return
default:
CaptureRuntimeMemStatsOnce(r)
}
}
}()
//*/
wgW := &sync.WaitGroup{}
/*
wgW.Add(1)
go func() {
defer wgW.Done()
//log.Println("go Write")
for {
select {
case <-ch:
//log.Println("done Write")
return
default:
WriteOnce(r, ioutil.Discard)
}
}
}()
//*/
wg := &sync.WaitGroup{}
wg.Add(FANOUT)
for i := 0; i < FANOUT; i++ {
go func(i int) {
defer wg.Done()
//log.Println("go", i)
for i := 0; i < b.N; i++ {
c.Inc(1)
g.Update(int64(i))
gf.Update(float64(i))
h.Update(int64(i))
m.Mark(1)
t.Update(1)
}
//log.Println("done", i)
}(i)
}
wg.Wait()
close(ch)
wgD.Wait()
wgR.Wait()
wgW.Wait()
}
func Example() {
c := NewCounter()
Register("money", c)
c.Inc(17)
// Threadsafe registration
t := GetOrRegisterTimer("db.get.latency", nil)
t.Time(func() {})
t.Update(1)
fmt.Println(c.Count())
fmt.Println(t.Min())
// Output: 17
// 1
}

View file

@ -38,7 +38,7 @@ func OpenTSDB(r Registry, d time.Duration, prefix string, addr *net.TCPAddr) {
// OpenTSDBWithConfig is a blocking exporter function just like OpenTSDB, // OpenTSDBWithConfig is a blocking exporter function just like OpenTSDB,
// but it takes a OpenTSDBConfig instead. // but it takes a OpenTSDBConfig instead.
func OpenTSDBWithConfig(c OpenTSDBConfig) { func OpenTSDBWithConfig(c OpenTSDBConfig) {
for _ = range time.Tick(c.FlushInterval) { for range time.Tick(c.FlushInterval) {
if err := openTSDB(&c); nil != err { if err := openTSDB(&c); nil != err {
log.Println(err) log.Println(err)
} }

21
metrics/opentsdb_test.go Normal file
View file

@ -0,0 +1,21 @@
package metrics
import (
"net"
"time"
)
func ExampleOpenTSDB() {
addr, _ := net.ResolveTCPAddr("net", ":2003")
go OpenTSDB(DefaultRegistry, 1*time.Second, "some.prefix", addr)
}
func ExampleOpenTSDBWithConfig() {
addr, _ := net.ResolveTCPAddr("net", ":2003")
go OpenTSDBWithConfig(OpenTSDBConfig{
Addr: addr,
Registry: DefaultRegistry,
FlushInterval: 1 * time.Second,
DurationUnit: time.Millisecond,
})
}

View file

@ -29,6 +29,9 @@ type Registry interface {
// Get the metric by the given name or nil if none is registered. // Get the metric by the given name or nil if none is registered.
Get(string) interface{} Get(string) interface{}
// GetAll metrics in the Registry.
GetAll() map[string]map[string]interface{}
// Gets an existing metric or registers the given one. // Gets an existing metric or registers the given one.
// The interface can be the metric to register if not found in registry, // The interface can be the metric to register if not found in registry,
// or a function returning the metric for lazy instantiation. // or a function returning the metric for lazy instantiation.
@ -109,10 +112,72 @@ func (r *StandardRegistry) RunHealthchecks() {
} }
} }
// GetAll metrics in the Registry
func (r *StandardRegistry) GetAll() map[string]map[string]interface{} {
data := make(map[string]map[string]interface{})
r.Each(func(name string, i interface{}) {
values := make(map[string]interface{})
switch metric := i.(type) {
case Counter:
values["count"] = metric.Count()
case Gauge:
values["value"] = metric.Value()
case GaugeFloat64:
values["value"] = metric.Value()
case Healthcheck:
values["error"] = nil
metric.Check()
if err := metric.Error(); nil != err {
values["error"] = metric.Error().Error()
}
case Histogram:
h := metric.Snapshot()
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
values["count"] = h.Count()
values["min"] = h.Min()
values["max"] = h.Max()
values["mean"] = h.Mean()
values["stddev"] = h.StdDev()
values["median"] = ps[0]
values["75%"] = ps[1]
values["95%"] = ps[2]
values["99%"] = ps[3]
values["99.9%"] = ps[4]
case Meter:
m := metric.Snapshot()
values["count"] = m.Count()
values["1m.rate"] = m.Rate1()
values["5m.rate"] = m.Rate5()
values["15m.rate"] = m.Rate15()
values["mean.rate"] = m.RateMean()
case Timer:
t := metric.Snapshot()
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
values["count"] = t.Count()
values["min"] = t.Min()
values["max"] = t.Max()
values["mean"] = t.Mean()
values["stddev"] = t.StdDev()
values["median"] = ps[0]
values["75%"] = ps[1]
values["95%"] = ps[2]
values["99%"] = ps[3]
values["99.9%"] = ps[4]
values["1m.rate"] = t.Rate1()
values["5m.rate"] = t.Rate5()
values["15m.rate"] = t.Rate15()
values["mean.rate"] = t.RateMean()
}
data[name] = values
})
return data
}
// Unregister the metric with the given name. // Unregister the metric with the given name.
func (r *StandardRegistry) Unregister(name string) { func (r *StandardRegistry) Unregister(name string) {
r.mutex.Lock() r.mutex.Lock()
defer r.mutex.Unlock() defer r.mutex.Unlock()
r.stop(name)
delete(r.metrics, name) delete(r.metrics, name)
} }
@ -120,7 +185,8 @@ func (r *StandardRegistry) Unregister(name string) {
func (r *StandardRegistry) UnregisterAll() { func (r *StandardRegistry) UnregisterAll() {
r.mutex.Lock() r.mutex.Lock()
defer r.mutex.Unlock() defer r.mutex.Unlock()
for name, _ := range r.metrics { for name := range r.metrics {
r.stop(name)
delete(r.metrics, name) delete(r.metrics, name)
} }
} }
@ -130,7 +196,7 @@ func (r *StandardRegistry) register(name string, i interface{}) error {
return DuplicateMetric(name) return DuplicateMetric(name)
} }
switch i.(type) { switch i.(type) {
case Counter, Gauge, GaugeFloat64, Healthcheck, Histogram, Meter, Timer: case Counter, Gauge, GaugeFloat64, Healthcheck, Histogram, Meter, Timer, ResettingTimer:
r.metrics[name] = i r.metrics[name] = i
} }
return nil return nil
@ -146,6 +212,19 @@ func (r *StandardRegistry) registered() map[string]interface{} {
return metrics return metrics
} }
func (r *StandardRegistry) stop(name string) {
if i, ok := r.metrics[name]; ok {
if s, ok := i.(Stoppable); ok {
s.Stop()
}
}
}
// Stoppable defines the metrics which has to be stopped.
type Stoppable interface {
Stop()
}
type PrefixedRegistry struct { type PrefixedRegistry struct {
underlying Registry underlying Registry
prefix string prefix string
@ -216,6 +295,11 @@ func (r *PrefixedRegistry) RunHealthchecks() {
r.underlying.RunHealthchecks() r.underlying.RunHealthchecks()
} }
// GetAll metrics in the Registry
func (r *PrefixedRegistry) GetAll() map[string]map[string]interface{} {
return r.underlying.GetAll()
}
// Unregister the metric with the given name. The name will be prefixed. // Unregister the metric with the given name. The name will be prefixed.
func (r *PrefixedRegistry) Unregister(name string) { func (r *PrefixedRegistry) Unregister(name string) {
realName := r.prefix + name realName := r.prefix + name

305
metrics/registry_test.go Normal file
View file

@ -0,0 +1,305 @@
package metrics
import (
"testing"
)
func BenchmarkRegistry(b *testing.B) {
r := NewRegistry()
r.Register("foo", NewCounter())
b.ResetTimer()
for i := 0; i < b.N; i++ {
r.Each(func(string, interface{}) {})
}
}
func TestRegistry(t *testing.T) {
r := NewRegistry()
r.Register("foo", NewCounter())
i := 0
r.Each(func(name string, iface interface{}) {
i++
if "foo" != name {
t.Fatal(name)
}
if _, ok := iface.(Counter); !ok {
t.Fatal(iface)
}
})
if 1 != i {
t.Fatal(i)
}
r.Unregister("foo")
i = 0
r.Each(func(string, interface{}) { i++ })
if 0 != i {
t.Fatal(i)
}
}
func TestRegistryDuplicate(t *testing.T) {
r := NewRegistry()
if err := r.Register("foo", NewCounter()); nil != err {
t.Fatal(err)
}
if err := r.Register("foo", NewGauge()); nil == err {
t.Fatal(err)
}
i := 0
r.Each(func(name string, iface interface{}) {
i++
if _, ok := iface.(Counter); !ok {
t.Fatal(iface)
}
})
if 1 != i {
t.Fatal(i)
}
}
func TestRegistryGet(t *testing.T) {
r := NewRegistry()
r.Register("foo", NewCounter())
if count := r.Get("foo").(Counter).Count(); 0 != count {
t.Fatal(count)
}
r.Get("foo").(Counter).Inc(1)
if count := r.Get("foo").(Counter).Count(); 1 != count {
t.Fatal(count)
}
}
func TestRegistryGetOrRegister(t *testing.T) {
r := NewRegistry()
// First metric wins with GetOrRegister
_ = r.GetOrRegister("foo", NewCounter())
m := r.GetOrRegister("foo", NewGauge())
if _, ok := m.(Counter); !ok {
t.Fatal(m)
}
i := 0
r.Each(func(name string, iface interface{}) {
i++
if name != "foo" {
t.Fatal(name)
}
if _, ok := iface.(Counter); !ok {
t.Fatal(iface)
}
})
if i != 1 {
t.Fatal(i)
}
}
func TestRegistryGetOrRegisterWithLazyInstantiation(t *testing.T) {
r := NewRegistry()
// First metric wins with GetOrRegister
_ = r.GetOrRegister("foo", NewCounter)
m := r.GetOrRegister("foo", NewGauge)
if _, ok := m.(Counter); !ok {
t.Fatal(m)
}
i := 0
r.Each(func(name string, iface interface{}) {
i++
if name != "foo" {
t.Fatal(name)
}
if _, ok := iface.(Counter); !ok {
t.Fatal(iface)
}
})
if i != 1 {
t.Fatal(i)
}
}
func TestRegistryUnregister(t *testing.T) {
l := len(arbiter.meters)
r := NewRegistry()
r.Register("foo", NewCounter())
r.Register("bar", NewMeter())
r.Register("baz", NewTimer())
if len(arbiter.meters) != l+2 {
t.Errorf("arbiter.meters: %d != %d\n", l+2, len(arbiter.meters))
}
r.Unregister("foo")
r.Unregister("bar")
r.Unregister("baz")
if len(arbiter.meters) != l {
t.Errorf("arbiter.meters: %d != %d\n", l+2, len(arbiter.meters))
}
}
func TestPrefixedChildRegistryGetOrRegister(t *testing.T) {
r := NewRegistry()
pr := NewPrefixedChildRegistry(r, "prefix.")
_ = pr.GetOrRegister("foo", NewCounter())
i := 0
r.Each(func(name string, m interface{}) {
i++
if name != "prefix.foo" {
t.Fatal(name)
}
})
if i != 1 {
t.Fatal(i)
}
}
func TestPrefixedRegistryGetOrRegister(t *testing.T) {
r := NewPrefixedRegistry("prefix.")
_ = r.GetOrRegister("foo", NewCounter())
i := 0
r.Each(func(name string, m interface{}) {
i++
if name != "prefix.foo" {
t.Fatal(name)
}
})
if i != 1 {
t.Fatal(i)
}
}
func TestPrefixedRegistryRegister(t *testing.T) {
r := NewPrefixedRegistry("prefix.")
err := r.Register("foo", NewCounter())
c := NewCounter()
Register("bar", c)
if err != nil {
t.Fatal(err.Error())
}
i := 0
r.Each(func(name string, m interface{}) {
i++
if name != "prefix.foo" {
t.Fatal(name)
}
})
if i != 1 {
t.Fatal(i)
}
}
func TestPrefixedRegistryUnregister(t *testing.T) {
r := NewPrefixedRegistry("prefix.")
_ = r.Register("foo", NewCounter())
i := 0
r.Each(func(name string, m interface{}) {
i++
if name != "prefix.foo" {
t.Fatal(name)
}
})
if i != 1 {
t.Fatal(i)
}
r.Unregister("foo")
i = 0
r.Each(func(name string, m interface{}) {
i++
})
if i != 0 {
t.Fatal(i)
}
}
func TestPrefixedRegistryGet(t *testing.T) {
pr := NewPrefixedRegistry("prefix.")
name := "foo"
pr.Register(name, NewCounter())
fooCounter := pr.Get(name)
if fooCounter == nil {
t.Fatal(name)
}
}
func TestPrefixedChildRegistryGet(t *testing.T) {
r := NewRegistry()
pr := NewPrefixedChildRegistry(r, "prefix.")
name := "foo"
pr.Register(name, NewCounter())
fooCounter := pr.Get(name)
if fooCounter == nil {
t.Fatal(name)
}
}
func TestChildPrefixedRegistryRegister(t *testing.T) {
r := NewPrefixedChildRegistry(DefaultRegistry, "prefix.")
err := r.Register("foo", NewCounter())
c := NewCounter()
Register("bar", c)
if err != nil {
t.Fatal(err.Error())
}
i := 0
r.Each(func(name string, m interface{}) {
i++
if name != "prefix.foo" {
t.Fatal(name)
}
})
if i != 1 {
t.Fatal(i)
}
}
func TestChildPrefixedRegistryOfChildRegister(t *testing.T) {
r := NewPrefixedChildRegistry(NewRegistry(), "prefix.")
r2 := NewPrefixedChildRegistry(r, "prefix2.")
err := r.Register("foo2", NewCounter())
if err != nil {
t.Fatal(err.Error())
}
err = r2.Register("baz", NewCounter())
c := NewCounter()
Register("bars", c)
i := 0
r2.Each(func(name string, m interface{}) {
i++
if name != "prefix.prefix2.baz" {
//t.Fatal(name)
}
})
if i != 1 {
t.Fatal(i)
}
}
func TestWalkRegistries(t *testing.T) {
r := NewPrefixedChildRegistry(NewRegistry(), "prefix.")
r2 := NewPrefixedChildRegistry(r, "prefix2.")
err := r.Register("foo2", NewCounter())
if err != nil {
t.Fatal(err.Error())
}
err = r2.Register("baz", NewCounter())
c := NewCounter()
Register("bars", c)
_, prefix := findPrefix(r2, "")
if "prefix.prefix2." != prefix {
t.Fatal(prefix)
}
}

237
metrics/resetting_timer.go Normal file
View file

@ -0,0 +1,237 @@
package metrics
import (
"math"
"sort"
"sync"
"time"
)
// Initial slice capacity for the values stored in a ResettingTimer
const InitialResettingTimerSliceCap = 10
// ResettingTimer is used for storing aggregated values for timers, which are reset on every flush interval.
type ResettingTimer interface {
Values() []int64
Snapshot() ResettingTimer
Percentiles([]float64) []int64
Mean() float64
Time(func())
Update(time.Duration)
UpdateSince(time.Time)
}
// GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a
// new StandardResettingTimer.
func GetOrRegisterResettingTimer(name string, r Registry) ResettingTimer {
if nil == r {
r = DefaultRegistry
}
return r.GetOrRegister(name, NewResettingTimer).(ResettingTimer)
}
// NewRegisteredResettingTimer constructs and registers a new StandardResettingTimer.
func NewRegisteredResettingTimer(name string, r Registry) ResettingTimer {
c := NewResettingTimer()
if nil == r {
r = DefaultRegistry
}
r.Register(name, c)
return c
}
// NewResettingTimer constructs a new StandardResettingTimer
func NewResettingTimer() ResettingTimer {
if !Enabled {
return NilResettingTimer{}
}
return &StandardResettingTimer{
values: make([]int64, 0, InitialResettingTimerSliceCap),
}
}
// NilResettingTimer is a no-op ResettingTimer.
type NilResettingTimer struct {
}
// Values is a no-op.
func (NilResettingTimer) Values() []int64 { return nil }
// Snapshot is a no-op.
func (NilResettingTimer) Snapshot() ResettingTimer { return NilResettingTimer{} }
// Time is a no-op.
func (NilResettingTimer) Time(func()) {}
// Update is a no-op.
func (NilResettingTimer) Update(time.Duration) {}
// Percentiles panics.
func (NilResettingTimer) Percentiles([]float64) []int64 {
panic("Percentiles called on a NilResettingTimer")
}
// Mean panics.
func (NilResettingTimer) Mean() float64 {
panic("Mean called on a NilResettingTimer")
}
// UpdateSince is a no-op.
func (NilResettingTimer) UpdateSince(time.Time) {}
// StandardResettingTimer is the standard implementation of a ResettingTimer.
// and Meter.
type StandardResettingTimer struct {
values []int64
mutex sync.Mutex
}
// Values returns a slice with all measurements.
func (t *StandardResettingTimer) Values() []int64 {
return t.values
}
// Snapshot resets the timer and returns a read-only copy of its contents.
func (t *StandardResettingTimer) Snapshot() ResettingTimer {
t.mutex.Lock()
defer t.mutex.Unlock()
currentValues := t.values
t.values = make([]int64, 0, InitialResettingTimerSliceCap)
return &ResettingTimerSnapshot{
values: currentValues,
}
}
// Percentiles panics.
func (t *StandardResettingTimer) Percentiles([]float64) []int64 {
panic("Percentiles called on a StandardResettingTimer")
}
// Mean panics.
func (t *StandardResettingTimer) Mean() float64 {
panic("Mean called on a StandardResettingTimer")
}
// Record the duration of the execution of the given function.
func (t *StandardResettingTimer) Time(f func()) {
ts := time.Now()
f()
t.Update(time.Since(ts))
}
// Record the duration of an event.
func (t *StandardResettingTimer) Update(d time.Duration) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.values = append(t.values, int64(d))
}
// Record the duration of an event that started at a time and ends now.
func (t *StandardResettingTimer) UpdateSince(ts time.Time) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.values = append(t.values, int64(time.Since(ts)))
}
// ResettingTimerSnapshot is a point-in-time copy of another ResettingTimer.
type ResettingTimerSnapshot struct {
values []int64
mean float64
thresholdBoundaries []int64
calculated bool
}
// Snapshot returns the snapshot.
func (t *ResettingTimerSnapshot) Snapshot() ResettingTimer { return t }
// Time panics.
func (*ResettingTimerSnapshot) Time(func()) {
panic("Time called on a ResettingTimerSnapshot")
}
// Update panics.
func (*ResettingTimerSnapshot) Update(time.Duration) {
panic("Update called on a ResettingTimerSnapshot")
}
// UpdateSince panics.
func (*ResettingTimerSnapshot) UpdateSince(time.Time) {
panic("UpdateSince called on a ResettingTimerSnapshot")
}
// Values returns all values from snapshot.
func (t *ResettingTimerSnapshot) Values() []int64 {
return t.values
}
// Percentiles returns the boundaries for the input percentiles.
func (t *ResettingTimerSnapshot) Percentiles(percentiles []float64) []int64 {
t.calc(percentiles)
return t.thresholdBoundaries
}
// Mean returns the mean of the snapshotted values
func (t *ResettingTimerSnapshot) Mean() float64 {
if !t.calculated {
t.calc([]float64{})
}
return t.mean
}
func (t *ResettingTimerSnapshot) calc(percentiles []float64) {
sort.Sort(Int64Slice(t.values))
count := len(t.values)
if count > 0 {
min := t.values[0]
max := t.values[count-1]
cumulativeValues := make([]int64, count)
cumulativeValues[0] = min
for i := 1; i < count; i++ {
cumulativeValues[i] = t.values[i] + cumulativeValues[i-1]
}
t.thresholdBoundaries = make([]int64, len(percentiles))
thresholdBoundary := max
for i, pct := range percentiles {
if count > 1 {
var abs float64
if pct >= 0 {
abs = pct
} else {
abs = 100 + pct
}
// poor man's math.Round(x):
// math.Floor(x + 0.5)
indexOfPerc := int(math.Floor(((abs / 100.0) * float64(count)) + 0.5))
if pct >= 0 {
indexOfPerc -= 1 // index offset=0
}
thresholdBoundary = t.values[indexOfPerc]
}
t.thresholdBoundaries[i] = thresholdBoundary
}
sum := cumulativeValues[count-1]
t.mean = float64(sum) / float64(count)
} else {
t.thresholdBoundaries = make([]int64, len(percentiles))
t.mean = 0
}
t.calculated = true
}
// Int64Slice attaches the methods of sort.Interface to []int64, sorting in increasing order.
type Int64Slice []int64
func (s Int64Slice) Len() int { return len(s) }
func (s Int64Slice) Less(i, j int) bool { return s[i] < s[j] }
func (s Int64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

View file

@ -0,0 +1,106 @@
package metrics
import (
"testing"
"time"
)
func TestResettingTimer(t *testing.T) {
tests := []struct {
values []int64
start int
end int
wantP50 int64
wantP95 int64
wantP99 int64
wantMean float64
wantMin int64
wantMax int64
}{
{
values: []int64{},
start: 1,
end: 11,
wantP50: 5, wantP95: 10, wantP99: 10,
wantMin: 1, wantMax: 10, wantMean: 5.5,
},
{
values: []int64{},
start: 1,
end: 101,
wantP50: 50, wantP95: 95, wantP99: 99,
wantMin: 1, wantMax: 100, wantMean: 50.5,
},
{
values: []int64{1},
start: 0,
end: 0,
wantP50: 1, wantP95: 1, wantP99: 1,
wantMin: 1, wantMax: 1, wantMean: 1,
},
{
values: []int64{0},
start: 0,
end: 0,
wantP50: 0, wantP95: 0, wantP99: 0,
wantMin: 0, wantMax: 0, wantMean: 0,
},
{
values: []int64{},
start: 0,
end: 0,
wantP50: 0, wantP95: 0, wantP99: 0,
wantMin: 0, wantMax: 0, wantMean: 0,
},
{
values: []int64{1, 10},
start: 0,
end: 0,
wantP50: 1, wantP95: 10, wantP99: 10,
wantMin: 1, wantMax: 10, wantMean: 5.5,
},
}
for ind, tt := range tests {
timer := NewResettingTimer()
for i := tt.start; i < tt.end; i++ {
tt.values = append(tt.values, int64(i))
}
for _, v := range tt.values {
timer.Update(time.Duration(v))
}
snap := timer.Snapshot()
ps := snap.Percentiles([]float64{50, 95, 99})
val := snap.Values()
if len(val) > 0 {
if tt.wantMin != val[0] {
t.Fatalf("%d: min: got %d, want %d", ind, val[0], tt.wantMin)
}
if tt.wantMax != val[len(val)-1] {
t.Fatalf("%d: max: got %d, want %d", ind, val[len(val)-1], tt.wantMax)
}
}
if tt.wantMean != snap.Mean() {
t.Fatalf("%d: mean: got %.2f, want %.2f", ind, snap.Mean(), tt.wantMean)
}
if tt.wantP50 != ps[0] {
t.Fatalf("%d: p50: got %d, want %d", ind, ps[0], tt.wantP50)
}
if tt.wantP95 != ps[1] {
t.Fatalf("%d: p95: got %d, want %d", ind, ps[1], tt.wantP95)
}
if tt.wantP99 != ps[2] {
t.Fatalf("%d: p99: got %d, want %d", ind, ps[2], tt.wantP99)
}
}
}

View file

@ -55,7 +55,7 @@ var (
// Capture new values for the Go runtime statistics exported in // Capture new values for the Go runtime statistics exported in
// runtime.MemStats. This is designed to be called as a goroutine. // runtime.MemStats. This is designed to be called as a goroutine.
func CaptureRuntimeMemStats(r Registry, d time.Duration) { func CaptureRuntimeMemStats(r Registry, d time.Duration) {
for _ = range time.Tick(d) { for range time.Tick(d) {
CaptureRuntimeMemStatsOnce(r) CaptureRuntimeMemStatsOnce(r)
} }
} }

88
metrics/runtime_test.go Normal file
View file

@ -0,0 +1,88 @@
package metrics
import (
"runtime"
"testing"
"time"
)
func BenchmarkRuntimeMemStats(b *testing.B) {
r := NewRegistry()
RegisterRuntimeMemStats(r)
b.ResetTimer()
for i := 0; i < b.N; i++ {
CaptureRuntimeMemStatsOnce(r)
}
}
func TestRuntimeMemStats(t *testing.T) {
r := NewRegistry()
RegisterRuntimeMemStats(r)
CaptureRuntimeMemStatsOnce(r)
zero := runtimeMetrics.MemStats.PauseNs.Count() // Get a "zero" since GC may have run before these tests.
runtime.GC()
CaptureRuntimeMemStatsOnce(r)
if count := runtimeMetrics.MemStats.PauseNs.Count(); 1 != count-zero {
t.Fatal(count - zero)
}
runtime.GC()
runtime.GC()
CaptureRuntimeMemStatsOnce(r)
if count := runtimeMetrics.MemStats.PauseNs.Count(); 3 != count-zero {
t.Fatal(count - zero)
}
for i := 0; i < 256; i++ {
runtime.GC()
}
CaptureRuntimeMemStatsOnce(r)
if count := runtimeMetrics.MemStats.PauseNs.Count(); 259 != count-zero {
t.Fatal(count - zero)
}
for i := 0; i < 257; i++ {
runtime.GC()
}
CaptureRuntimeMemStatsOnce(r)
if count := runtimeMetrics.MemStats.PauseNs.Count(); 515 != count-zero { // We lost one because there were too many GCs between captures.
t.Fatal(count - zero)
}
}
func TestRuntimeMemStatsNumThread(t *testing.T) {
r := NewRegistry()
RegisterRuntimeMemStats(r)
CaptureRuntimeMemStatsOnce(r)
if value := runtimeMetrics.NumThread.Value(); value < 1 {
t.Fatalf("got NumThread: %d, wanted at least 1", value)
}
}
func TestRuntimeMemStatsBlocking(t *testing.T) {
if g := runtime.GOMAXPROCS(0); g < 2 {
t.Skipf("skipping TestRuntimeMemStatsBlocking with GOMAXPROCS=%d\n", g)
}
ch := make(chan int)
go testRuntimeMemStatsBlocking(ch)
var memStats runtime.MemStats
t0 := time.Now()
runtime.ReadMemStats(&memStats)
t1 := time.Now()
t.Log("i++ during runtime.ReadMemStats:", <-ch)
go testRuntimeMemStatsBlocking(ch)
d := t1.Sub(t0)
t.Log(d)
time.Sleep(d)
t.Log("i++ during time.Sleep:", <-ch)
}
func testRuntimeMemStatsBlocking(ch chan int) {
i := 0
for {
select {
case ch <- i:
return
default:
i++
}
}
}

View file

@ -46,7 +46,7 @@ type ExpDecaySample struct {
// NewExpDecaySample constructs a new exponentially-decaying sample with the // NewExpDecaySample constructs a new exponentially-decaying sample with the
// given reservoir size and alpha. // given reservoir size and alpha.
func NewExpDecaySample(reservoirSize int, alpha float64) Sample { func NewExpDecaySample(reservoirSize int, alpha float64) Sample {
if UseNilMetrics { if !Enabled {
return NilSample{} return NilSample{}
} }
s := &ExpDecaySample{ s := &ExpDecaySample{
@ -407,7 +407,7 @@ type UniformSample struct {
// NewUniformSample constructs a new uniform sample with the given reservoir // NewUniformSample constructs a new uniform sample with the given reservoir
// size. // size.
func NewUniformSample(reservoirSize int) Sample { func NewUniformSample(reservoirSize int) Sample {
if UseNilMetrics { if !Enabled {
return NilSample{} return NilSample{}
} }
return &UniformSample{ return &UniformSample{

363
metrics/sample_test.go Normal file
View file

@ -0,0 +1,363 @@
package metrics
import (
"math/rand"
"runtime"
"testing"
"time"
)
// Benchmark{Compute,Copy}{1000,1000000} demonstrate that, even for relatively
// expensive computations like Variance, the cost of copying the Sample, as
// approximated by a make and copy, is much greater than the cost of the
// computation for small samples and only slightly less for large samples.
func BenchmarkCompute1000(b *testing.B) {
s := make([]int64, 1000)
for i := 0; i < len(s); i++ {
s[i] = int64(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SampleVariance(s)
}
}
func BenchmarkCompute1000000(b *testing.B) {
s := make([]int64, 1000000)
for i := 0; i < len(s); i++ {
s[i] = int64(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SampleVariance(s)
}
}
func BenchmarkCopy1000(b *testing.B) {
s := make([]int64, 1000)
for i := 0; i < len(s); i++ {
s[i] = int64(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
sCopy := make([]int64, len(s))
copy(sCopy, s)
}
}
func BenchmarkCopy1000000(b *testing.B) {
s := make([]int64, 1000000)
for i := 0; i < len(s); i++ {
s[i] = int64(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
sCopy := make([]int64, len(s))
copy(sCopy, s)
}
}
func BenchmarkExpDecaySample257(b *testing.B) {
benchmarkSample(b, NewExpDecaySample(257, 0.015))
}
func BenchmarkExpDecaySample514(b *testing.B) {
benchmarkSample(b, NewExpDecaySample(514, 0.015))
}
func BenchmarkExpDecaySample1028(b *testing.B) {
benchmarkSample(b, NewExpDecaySample(1028, 0.015))
}
func BenchmarkUniformSample257(b *testing.B) {
benchmarkSample(b, NewUniformSample(257))
}
func BenchmarkUniformSample514(b *testing.B) {
benchmarkSample(b, NewUniformSample(514))
}
func BenchmarkUniformSample1028(b *testing.B) {
benchmarkSample(b, NewUniformSample(1028))
}
func TestExpDecaySample10(t *testing.T) {
rand.Seed(1)
s := NewExpDecaySample(100, 0.99)
for i := 0; i < 10; i++ {
s.Update(int64(i))
}
if size := s.Count(); 10 != size {
t.Errorf("s.Count(): 10 != %v\n", size)
}
if size := s.Size(); 10 != size {
t.Errorf("s.Size(): 10 != %v\n", size)
}
if l := len(s.Values()); 10 != l {
t.Errorf("len(s.Values()): 10 != %v\n", l)
}
for _, v := range s.Values() {
if v > 10 || v < 0 {
t.Errorf("out of range [0, 10): %v\n", v)
}
}
}
func TestExpDecaySample100(t *testing.T) {
rand.Seed(1)
s := NewExpDecaySample(1000, 0.01)
for i := 0; i < 100; i++ {
s.Update(int64(i))
}
if size := s.Count(); 100 != size {
t.Errorf("s.Count(): 100 != %v\n", size)
}
if size := s.Size(); 100 != size {
t.Errorf("s.Size(): 100 != %v\n", size)
}
if l := len(s.Values()); 100 != l {
t.Errorf("len(s.Values()): 100 != %v\n", l)
}
for _, v := range s.Values() {
if v > 100 || v < 0 {
t.Errorf("out of range [0, 100): %v\n", v)
}
}
}
func TestExpDecaySample1000(t *testing.T) {
rand.Seed(1)
s := NewExpDecaySample(100, 0.99)
for i := 0; i < 1000; i++ {
s.Update(int64(i))
}
if size := s.Count(); 1000 != size {
t.Errorf("s.Count(): 1000 != %v\n", size)
}
if size := s.Size(); 100 != size {
t.Errorf("s.Size(): 100 != %v\n", size)
}
if l := len(s.Values()); 100 != l {
t.Errorf("len(s.Values()): 100 != %v\n", l)
}
for _, v := range s.Values() {
if v > 1000 || v < 0 {
t.Errorf("out of range [0, 1000): %v\n", v)
}
}
}
// This test makes sure that the sample's priority is not amplified by using
// nanosecond duration since start rather than second duration since start.
// The priority becomes +Inf quickly after starting if this is done,
// effectively freezing the set of samples until a rescale step happens.
func TestExpDecaySampleNanosecondRegression(t *testing.T) {
rand.Seed(1)
s := NewExpDecaySample(100, 0.99)
for i := 0; i < 100; i++ {
s.Update(10)
}
time.Sleep(1 * time.Millisecond)
for i := 0; i < 100; i++ {
s.Update(20)
}
v := s.Values()
avg := float64(0)
for i := 0; i < len(v); i++ {
avg += float64(v[i])
}
avg /= float64(len(v))
if avg > 16 || avg < 14 {
t.Errorf("out of range [14, 16]: %v\n", avg)
}
}
func TestExpDecaySampleRescale(t *testing.T) {
s := NewExpDecaySample(2, 0.001).(*ExpDecaySample)
s.update(time.Now(), 1)
s.update(time.Now().Add(time.Hour+time.Microsecond), 1)
for _, v := range s.values.Values() {
if v.k == 0.0 {
t.Fatal("v.k == 0.0")
}
}
}
func TestExpDecaySampleSnapshot(t *testing.T) {
now := time.Now()
rand.Seed(1)
s := NewExpDecaySample(100, 0.99)
for i := 1; i <= 10000; i++ {
s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i))
}
snapshot := s.Snapshot()
s.Update(1)
testExpDecaySampleStatistics(t, snapshot)
}
func TestExpDecaySampleStatistics(t *testing.T) {
now := time.Now()
rand.Seed(1)
s := NewExpDecaySample(100, 0.99)
for i := 1; i <= 10000; i++ {
s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i))
}
testExpDecaySampleStatistics(t, s)
}
func TestUniformSample(t *testing.T) {
rand.Seed(1)
s := NewUniformSample(100)
for i := 0; i < 1000; i++ {
s.Update(int64(i))
}
if size := s.Count(); 1000 != size {
t.Errorf("s.Count(): 1000 != %v\n", size)
}
if size := s.Size(); 100 != size {
t.Errorf("s.Size(): 100 != %v\n", size)
}
if l := len(s.Values()); 100 != l {
t.Errorf("len(s.Values()): 100 != %v\n", l)
}
for _, v := range s.Values() {
if v > 1000 || v < 0 {
t.Errorf("out of range [0, 100): %v\n", v)
}
}
}
func TestUniformSampleIncludesTail(t *testing.T) {
rand.Seed(1)
s := NewUniformSample(100)
max := 100
for i := 0; i < max; i++ {
s.Update(int64(i))
}
v := s.Values()
sum := 0
exp := (max - 1) * max / 2
for i := 0; i < len(v); i++ {
sum += int(v[i])
}
if exp != sum {
t.Errorf("sum: %v != %v\n", exp, sum)
}
}
func TestUniformSampleSnapshot(t *testing.T) {
s := NewUniformSample(100)
for i := 1; i <= 10000; i++ {
s.Update(int64(i))
}
snapshot := s.Snapshot()
s.Update(1)
testUniformSampleStatistics(t, snapshot)
}
func TestUniformSampleStatistics(t *testing.T) {
rand.Seed(1)
s := NewUniformSample(100)
for i := 1; i <= 10000; i++ {
s.Update(int64(i))
}
testUniformSampleStatistics(t, s)
}
func benchmarkSample(b *testing.B, s Sample) {
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
pauseTotalNs := memStats.PauseTotalNs
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.Update(1)
}
b.StopTimer()
runtime.GC()
runtime.ReadMemStats(&memStats)
b.Logf("GC cost: %d ns/op", int(memStats.PauseTotalNs-pauseTotalNs)/b.N)
}
func testExpDecaySampleStatistics(t *testing.T, s Sample) {
if count := s.Count(); 10000 != count {
t.Errorf("s.Count(): 10000 != %v\n", count)
}
if min := s.Min(); 107 != min {
t.Errorf("s.Min(): 107 != %v\n", min)
}
if max := s.Max(); 10000 != max {
t.Errorf("s.Max(): 10000 != %v\n", max)
}
if mean := s.Mean(); 4965.98 != mean {
t.Errorf("s.Mean(): 4965.98 != %v\n", mean)
}
if stdDev := s.StdDev(); 2959.825156930727 != stdDev {
t.Errorf("s.StdDev(): 2959.825156930727 != %v\n", stdDev)
}
ps := s.Percentiles([]float64{0.5, 0.75, 0.99})
if 4615 != ps[0] {
t.Errorf("median: 4615 != %v\n", ps[0])
}
if 7672 != ps[1] {
t.Errorf("75th percentile: 7672 != %v\n", ps[1])
}
if 9998.99 != ps[2] {
t.Errorf("99th percentile: 9998.99 != %v\n", ps[2])
}
}
func testUniformSampleStatistics(t *testing.T, s Sample) {
if count := s.Count(); 10000 != count {
t.Errorf("s.Count(): 10000 != %v\n", count)
}
if min := s.Min(); 37 != min {
t.Errorf("s.Min(): 37 != %v\n", min)
}
if max := s.Max(); 9989 != max {
t.Errorf("s.Max(): 9989 != %v\n", max)
}
if mean := s.Mean(); 4748.14 != mean {
t.Errorf("s.Mean(): 4748.14 != %v\n", mean)
}
if stdDev := s.StdDev(); 2826.684117548333 != stdDev {
t.Errorf("s.StdDev(): 2826.684117548333 != %v\n", stdDev)
}
ps := s.Percentiles([]float64{0.5, 0.75, 0.99})
if 4599 != ps[0] {
t.Errorf("median: 4599 != %v\n", ps[0])
}
if 7380.5 != ps[1] {
t.Errorf("75th percentile: 7380.5 != %v\n", ps[1])
}
if 9986.429999999998 != ps[2] {
t.Errorf("99th percentile: 9986.429999999998 != %v\n", ps[2])
}
}
// TestUniformSampleConcurrentUpdateCount would expose data race problems with
// concurrent Update and Count calls on Sample when test is called with -race
// argument
func TestUniformSampleConcurrentUpdateCount(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
s := NewUniformSample(100)
for i := 0; i < 100; i++ {
s.Update(int64(i))
}
quit := make(chan struct{})
go func() {
t := time.NewTicker(10 * time.Millisecond)
for {
select {
case <-t.C:
s.Update(rand.Int63())
case <-quit:
t.Stop()
return
}
}
}()
for i := 0; i < 1000; i++ {
s.Count()
time.Sleep(5 * time.Millisecond)
}
quit <- struct{}{}
}

View file

@ -11,7 +11,7 @@ import (
// Output each metric in the given registry to syslog periodically using // Output each metric in the given registry to syslog periodically using
// the given syslogger. // the given syslogger.
func Syslog(r Registry, d time.Duration, w *syslog.Writer) { func Syslog(r Registry, d time.Duration, w *syslog.Writer) {
for _ = range time.Tick(d) { for range time.Tick(d) {
r.Each(func(name string, i interface{}) { r.Each(func(name string, i interface{}) {
switch metric := i.(type) { switch metric := i.(type) {
case Counter: case Counter:

View file

@ -19,6 +19,7 @@ type Timer interface {
RateMean() float64 RateMean() float64
Snapshot() Timer Snapshot() Timer
StdDev() float64 StdDev() float64
Stop()
Sum() int64 Sum() int64
Time(func()) Time(func())
Update(time.Duration) Update(time.Duration)
@ -28,6 +29,8 @@ type Timer interface {
// GetOrRegisterTimer returns an existing Timer or constructs and registers a // GetOrRegisterTimer returns an existing Timer or constructs and registers a
// new StandardTimer. // new StandardTimer.
// Be sure to unregister the meter from the registry once it is of no use to
// allow for garbage collection.
func GetOrRegisterTimer(name string, r Registry) Timer { func GetOrRegisterTimer(name string, r Registry) Timer {
if nil == r { if nil == r {
r = DefaultRegistry r = DefaultRegistry
@ -36,8 +39,9 @@ func GetOrRegisterTimer(name string, r Registry) Timer {
} }
// NewCustomTimer constructs a new StandardTimer from a Histogram and a Meter. // NewCustomTimer constructs a new StandardTimer from a Histogram and a Meter.
// Be sure to call Stop() once the timer is of no use to allow for garbage collection.
func NewCustomTimer(h Histogram, m Meter) Timer { func NewCustomTimer(h Histogram, m Meter) Timer {
if UseNilMetrics { if !Enabled {
return NilTimer{} return NilTimer{}
} }
return &StandardTimer{ return &StandardTimer{
@ -47,6 +51,8 @@ func NewCustomTimer(h Histogram, m Meter) Timer {
} }
// NewRegisteredTimer constructs and registers a new StandardTimer. // NewRegisteredTimer constructs and registers a new StandardTimer.
// Be sure to unregister the meter from the registry once it is of no use to
// allow for garbage collection.
func NewRegisteredTimer(name string, r Registry) Timer { func NewRegisteredTimer(name string, r Registry) Timer {
c := NewTimer() c := NewTimer()
if nil == r { if nil == r {
@ -58,8 +64,9 @@ func NewRegisteredTimer(name string, r Registry) Timer {
// NewTimer constructs a new StandardTimer using an exponentially-decaying // NewTimer constructs a new StandardTimer using an exponentially-decaying
// sample with the same reservoir size and alpha as UNIX load averages. // sample with the same reservoir size and alpha as UNIX load averages.
// Be sure to call Stop() once the timer is of no use to allow for garbage collection.
func NewTimer() Timer { func NewTimer() Timer {
if UseNilMetrics { if !Enabled {
return NilTimer{} return NilTimer{}
} }
return &StandardTimer{ return &StandardTimer{
@ -112,6 +119,9 @@ func (NilTimer) Snapshot() Timer { return NilTimer{} }
// StdDev is a no-op. // StdDev is a no-op.
func (NilTimer) StdDev() float64 { return 0.0 } func (NilTimer) StdDev() float64 { return 0.0 }
// Stop is a no-op.
func (NilTimer) Stop() {}
// Sum is a no-op. // Sum is a no-op.
func (NilTimer) Sum() int64 { return 0 } func (NilTimer) Sum() int64 { return 0 }
@ -201,6 +211,11 @@ func (t *StandardTimer) StdDev() float64 {
return t.histogram.StdDev() return t.histogram.StdDev()
} }
// Stop stops the meter.
func (t *StandardTimer) Stop() {
t.meter.Stop()
}
// Sum returns the sum in the sample. // Sum returns the sum in the sample.
func (t *StandardTimer) Sum() int64 { func (t *StandardTimer) Sum() int64 {
return t.histogram.Sum() return t.histogram.Sum()
@ -288,6 +303,9 @@ func (t *TimerSnapshot) Snapshot() Timer { return t }
// was taken. // was taken.
func (t *TimerSnapshot) StdDev() float64 { return t.histogram.StdDev() } func (t *TimerSnapshot) StdDev() float64 { return t.histogram.StdDev() }
// Stop is a no-op.
func (t *TimerSnapshot) Stop() {}
// Sum returns the sum at the time the snapshot was taken. // Sum returns the sum at the time the snapshot was taken.
func (t *TimerSnapshot) Sum() int64 { return t.histogram.Sum() } func (t *TimerSnapshot) Sum() int64 { return t.histogram.Sum() }

101
metrics/timer_test.go Normal file
View file

@ -0,0 +1,101 @@
package metrics
import (
"fmt"
"math"
"testing"
"time"
)
func BenchmarkTimer(b *testing.B) {
tm := NewTimer()
b.ResetTimer()
for i := 0; i < b.N; i++ {
tm.Update(1)
}
}
func TestGetOrRegisterTimer(t *testing.T) {
r := NewRegistry()
NewRegisteredTimer("foo", r).Update(47)
if tm := GetOrRegisterTimer("foo", r); 1 != tm.Count() {
t.Fatal(tm)
}
}
func TestTimerExtremes(t *testing.T) {
tm := NewTimer()
tm.Update(math.MaxInt64)
tm.Update(0)
if stdDev := tm.StdDev(); 4.611686018427388e+18 != stdDev {
t.Errorf("tm.StdDev(): 4.611686018427388e+18 != %v\n", stdDev)
}
}
func TestTimerStop(t *testing.T) {
l := len(arbiter.meters)
tm := NewTimer()
if len(arbiter.meters) != l+1 {
t.Errorf("arbiter.meters: %d != %d\n", l+1, len(arbiter.meters))
}
tm.Stop()
if len(arbiter.meters) != l {
t.Errorf("arbiter.meters: %d != %d\n", l, len(arbiter.meters))
}
}
func TestTimerFunc(t *testing.T) {
tm := NewTimer()
tm.Time(func() { time.Sleep(50e6) })
if max := tm.Max(); 45e6 > max || max > 55e6 {
t.Errorf("tm.Max(): 45e6 > %v || %v > 55e6\n", max, max)
}
}
func TestTimerZero(t *testing.T) {
tm := NewTimer()
if count := tm.Count(); 0 != count {
t.Errorf("tm.Count(): 0 != %v\n", count)
}
if min := tm.Min(); 0 != min {
t.Errorf("tm.Min(): 0 != %v\n", min)
}
if max := tm.Max(); 0 != max {
t.Errorf("tm.Max(): 0 != %v\n", max)
}
if mean := tm.Mean(); 0.0 != mean {
t.Errorf("tm.Mean(): 0.0 != %v\n", mean)
}
if stdDev := tm.StdDev(); 0.0 != stdDev {
t.Errorf("tm.StdDev(): 0.0 != %v\n", stdDev)
}
ps := tm.Percentiles([]float64{0.5, 0.75, 0.99})
if 0.0 != ps[0] {
t.Errorf("median: 0.0 != %v\n", ps[0])
}
if 0.0 != ps[1] {
t.Errorf("75th percentile: 0.0 != %v\n", ps[1])
}
if 0.0 != ps[2] {
t.Errorf("99th percentile: 0.0 != %v\n", ps[2])
}
if rate1 := tm.Rate1(); 0.0 != rate1 {
t.Errorf("tm.Rate1(): 0.0 != %v\n", rate1)
}
if rate5 := tm.Rate5(); 0.0 != rate5 {
t.Errorf("tm.Rate5(): 0.0 != %v\n", rate5)
}
if rate15 := tm.Rate15(); 0.0 != rate15 {
t.Errorf("tm.Rate15(): 0.0 != %v\n", rate15)
}
if rateMean := tm.RateMean(); 0.0 != rateMean {
t.Errorf("tm.RateMean(): 0.0 != %v\n", rateMean)
}
}
func ExampleGetOrRegisterTimer() {
m := "account.create.latency"
t := GetOrRegisterTimer(m, nil)
t.Update(47)
fmt.Println(t.Max()) // Output: 47
}

View file

@ -7,4 +7,4 @@ GOFMT_LINES=`gofmt -l . | wc -l | xargs`
test $GOFMT_LINES -eq 0 || echo "gofmt needs to be run, ${GOFMT_LINES} files have issues" test $GOFMT_LINES -eq 0 || echo "gofmt needs to be run, ${GOFMT_LINES} files have issues"
# run the tests for the root package # run the tests for the root package
go test . go test -race .

View file

@ -10,7 +10,7 @@ import (
// Write sorts writes each metric in the given registry periodically to the // Write sorts writes each metric in the given registry periodically to the
// given io.Writer. // given io.Writer.
func Write(r Registry, d time.Duration, w io.Writer) { func Write(r Registry, d time.Duration, w io.Writer) {
for _ = range time.Tick(d) { for range time.Tick(d) {
WriteOnce(r, w) WriteOnce(r, w)
} }
} }

22
metrics/writer_test.go Normal file
View file

@ -0,0 +1,22 @@
package metrics
import (
"sort"
"testing"
)
func TestMetricsSorting(t *testing.T) {
var namedMetrics = namedMetricSlice{
{name: "zzz"},
{name: "bbb"},
{name: "fff"},
{name: "ggg"},
}
sort.Sort(namedMetrics)
for i, name := range []string{"bbb", "fff", "ggg", "zzz"} {
if namedMetrics[i].name != name {
t.Fail()
}
}
}

View file

@ -24,10 +24,10 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/rcrowley/go-metrics"
) )
// PrivateAdminAPI is the collection of administrative API methods exposed only // PrivateAdminAPI is the collection of administrative API methods exposed only

View file

@ -25,10 +25,10 @@ import (
) )
var ( var (
ingressConnectMeter = metrics.NewMeter("p2p/InboundConnects") ingressConnectMeter = metrics.NewRegisteredMeter("p2p/InboundConnects", nil)
ingressTrafficMeter = metrics.NewMeter("p2p/InboundTraffic") ingressTrafficMeter = metrics.NewRegisteredMeter("p2p/InboundTraffic", nil)
egressConnectMeter = metrics.NewMeter("p2p/OutboundConnects") egressConnectMeter = metrics.NewRegisteredMeter("p2p/OutboundConnects", nil)
egressTrafficMeter = metrics.NewMeter("p2p/OutboundTraffic") egressTrafficMeter = metrics.NewRegisteredMeter("p2p/OutboundTraffic", nil)
) )
// meteredConn is a wrapper around a network TCP connection that meters both the // meteredConn is a wrapper around a network TCP connection that meters both the

View file

@ -31,46 +31,46 @@ var (
var ( var (
// MainnetChainConfig is the chain parameters to run a node on the main network. // MainnetChainConfig is the chain parameters to run a node on the main network.
MainnetChainConfig = &ChainConfig{ MainnetChainConfig = &ChainConfig{
ChainId: big.NewInt(1), ChainId: big.NewInt(1),
HomesteadBlock: big.NewInt(1150000), HomesteadBlock: big.NewInt(1150000),
DAOForkBlock: big.NewInt(1920000), DAOForkBlock: big.NewInt(1920000),
DAOForkSupport: true, DAOForkSupport: true,
EIP150Block: big.NewInt(2463000), EIP150Block: big.NewInt(2463000),
EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
EIP155Block: big.NewInt(2675000), EIP155Block: big.NewInt(2675000),
EIP158Block: big.NewInt(2675000), EIP158Block: big.NewInt(2675000),
ByzantiumBlock: big.NewInt(4370000), ByzantiumBlock: big.NewInt(4370000),
ConstantinopleBlock: nil,
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
} }
// TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network. // TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network.
TestnetChainConfig = &ChainConfig{ TestnetChainConfig = &ChainConfig{
ChainId: big.NewInt(3), ChainId: big.NewInt(3),
HomesteadBlock: big.NewInt(0), HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil, DAOForkBlock: nil,
DAOForkSupport: true, DAOForkSupport: true,
EIP150Block: big.NewInt(0), EIP150Block: big.NewInt(0),
EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"),
EIP155Block: big.NewInt(10), EIP155Block: big.NewInt(10),
EIP158Block: big.NewInt(10), EIP158Block: big.NewInt(10),
ByzantiumBlock: big.NewInt(1700000), ByzantiumBlock: big.NewInt(1700000),
ConstantinopleBlock: nil,
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
} }
// RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network. // RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network.
RinkebyChainConfig = &ChainConfig{ RinkebyChainConfig = &ChainConfig{
ChainId: big.NewInt(4), ChainId: big.NewInt(4),
HomesteadBlock: big.NewInt(1), HomesteadBlock: big.NewInt(1),
DAOForkBlock: nil, DAOForkBlock: nil,
DAOForkSupport: true, DAOForkSupport: true,
EIP150Block: big.NewInt(2), EIP150Block: big.NewInt(2),
EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"), EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"),
EIP155Block: big.NewInt(3), EIP155Block: big.NewInt(3),
EIP158Block: big.NewInt(3), EIP158Block: big.NewInt(3),
ByzantiumBlock: big.NewInt(1035301), ByzantiumBlock: big.NewInt(1035301),
ConstantinopleBlock: nil,
Clique: &CliqueConfig{ Clique: &CliqueConfig{
Period: 15, Period: 15,
Epoch: 30000, Epoch: 30000,
@ -82,16 +82,16 @@ var (
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // adding flags to the config to also have to set these fields.
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), new(EthashConfig), nil} AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus. // and accepted by the Ethereum core developers into the Clique consensus.
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // adding flags to the config to also have to set these fields.
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &CliqueConfig{Period: 0, Epoch: 30000}} AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), new(EthashConfig), nil} TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
TestRules = TestChainConfig.Rules(new(big.Int)) TestRules = TestChainConfig.Rules(new(big.Int))
) )
@ -115,7 +115,8 @@ type ChainConfig struct {
EIP155Block *big.Int `json:"eip155Block,omitempty"` // EIP155 HF block EIP155Block *big.Int `json:"eip155Block,omitempty"` // EIP155 HF block
EIP158Block *big.Int `json:"eip158Block,omitempty"` // EIP158 HF block EIP158Block *big.Int `json:"eip158Block,omitempty"` // EIP158 HF block
ByzantiumBlock *big.Int `json:"byzantiumBlock,omitempty"` // Byzantium switch block (nil = no fork, 0 = already on byzantium) ByzantiumBlock *big.Int `json:"byzantiumBlock,omitempty"` // Byzantium switch block (nil = no fork, 0 = already on byzantium)
ConstantinopleBlock *big.Int `json:"constantinopleBlock,omitempty"` // Constantinople switch block (nil = no fork, 0 = already activated)
// Various consensus engines // Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"` Ethash *EthashConfig `json:"ethash,omitempty"`
@ -152,7 +153,7 @@ func (c *ChainConfig) String() string {
default: default:
engine = "unknown" engine = "unknown"
} }
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Engine: %v}", return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Engine: %v}",
c.ChainId, c.ChainId,
c.HomesteadBlock, c.HomesteadBlock,
c.DAOForkBlock, c.DAOForkBlock,
@ -161,6 +162,7 @@ func (c *ChainConfig) String() string {
c.EIP155Block, c.EIP155Block,
c.EIP158Block, c.EIP158Block,
c.ByzantiumBlock, c.ByzantiumBlock,
c.ConstantinopleBlock,
engine, engine,
) )
} }
@ -191,6 +193,10 @@ func (c *ChainConfig) IsByzantium(num *big.Int) bool {
return isForked(c.ByzantiumBlock, num) return isForked(c.ByzantiumBlock, num)
} }
func (c *ChainConfig) IsConstantinople(num *big.Int) bool {
return isForked(c.ConstantinopleBlock, num)
}
// GasTable returns the gas table corresponding to the current phase (homestead or homestead reprice). // GasTable returns the gas table corresponding to the current phase (homestead or homestead reprice).
// //
// The returned GasTable's fields shouldn't, under any circumstances, be changed. // The returned GasTable's fields shouldn't, under any circumstances, be changed.
@ -251,6 +257,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi
if isForkIncompatible(c.ByzantiumBlock, newcfg.ByzantiumBlock, head) { if isForkIncompatible(c.ByzantiumBlock, newcfg.ByzantiumBlock, head) {
return newCompatError("Byzantium fork block", c.ByzantiumBlock, newcfg.ByzantiumBlock) return newCompatError("Byzantium fork block", c.ByzantiumBlock, newcfg.ByzantiumBlock)
} }
if isForkIncompatible(c.ConstantinopleBlock, newcfg.ConstantinopleBlock, head) {
return newCompatError("Constantinople fork block", c.ConstantinopleBlock, newcfg.ConstantinopleBlock)
}
return nil return nil
} }

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/contracts/ens" "github.com/ethereum/go-ethereum/contracts/ens"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
@ -52,6 +53,24 @@ func (e *ErrResourceReturn) Key() string {
return e.key return e.key
} }
var (
apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil)
apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil)
apiPutCount = metrics.NewRegisteredCounter("api.put.count", nil)
apiPutFail = metrics.NewRegisteredCounter("api.put.fail", nil)
apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil)
apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil)
apiGetHttp300 = metrics.NewRegisteredCounter("api.get.http.300", nil)
apiModifyCount = metrics.NewRegisteredCounter("api.modify.count", nil)
apiModifyFail = metrics.NewRegisteredCounter("api.modify.fail", nil)
apiAddFileCount = metrics.NewRegisteredCounter("api.addfile.count", nil)
apiAddFileFail = metrics.NewRegisteredCounter("api.addfile.fail", nil)
apiRmFileCount = metrics.NewRegisteredCounter("api.removefile.count", nil)
apiRmFileFail = metrics.NewRegisteredCounter("api.removefile.fail", nil)
apiAppendFileCount = metrics.NewRegisteredCounter("api.appendfile.count", nil)
apiAppendFileFail = metrics.NewRegisteredCounter("api.appendfile.fail", nil)
)
type Resolver interface { type Resolver interface {
Resolve(string) (common.Hash, error) Resolve(string) (common.Hash, error)
} }
@ -230,6 +249,7 @@ type ErrResolve error
// DNS Resolver // DNS Resolver
func (self *Api) Resolve(uri *URI) (storage.Key, error) { func (self *Api) Resolve(uri *URI) (storage.Key, error) {
apiResolveCount.Inc(1)
log.Trace(fmt.Sprintf("Resolving : %v", uri.Addr)) log.Trace(fmt.Sprintf("Resolving : %v", uri.Addr))
// if the URI is immutable, check if the address is a hash // if the URI is immutable, check if the address is a hash
@ -244,6 +264,7 @@ func (self *Api) Resolve(uri *URI) (storage.Key, error) {
// if DNS is not configured, check if the address is a hash // if DNS is not configured, check if the address is a hash
if self.dns == nil { if self.dns == nil {
if !isHash { if !isHash {
apiResolveFail.Inc(1)
return nil, fmt.Errorf("no DNS to resolve name: %q", uri.Addr) return nil, fmt.Errorf("no DNS to resolve name: %q", uri.Addr)
} }
return common.Hex2Bytes(uri.Addr), nil return common.Hex2Bytes(uri.Addr), nil
@ -254,6 +275,7 @@ func (self *Api) Resolve(uri *URI) (storage.Key, error) {
if err == nil { if err == nil {
return resolved[:], nil return resolved[:], nil
} else if !isHash { } else if !isHash {
apiResolveFail.Inc(1)
return nil, err return nil, err
} }
return common.Hex2Bytes(uri.Addr), nil return common.Hex2Bytes(uri.Addr), nil
@ -261,15 +283,18 @@ func (self *Api) Resolve(uri *URI) (storage.Key, error) {
// Put provides singleton manifest creation on top of dpa store // Put provides singleton manifest creation on top of dpa store
func (self *Api) Put(content, contentType string) (k storage.Key, wait func(), err error) { func (self *Api) Put(content, contentType string) (k storage.Key, wait func(), err error) {
apiPutCount.Inc(1)
r := strings.NewReader(content) r := strings.NewReader(content)
key, waitContent, err := self.dpa.Store(r, int64(len(content))) key, waitContent, err := self.dpa.Store(r, int64(len(content)))
if err != nil { if err != nil {
apiPutFail.Inc(1)
return nil, nil, err return nil, nil, err
} }
manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType) manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType)
r = strings.NewReader(manifest) r = strings.NewReader(manifest)
key, waitManifest, err := self.dpa.Store(r, int64(len(manifest))) key, waitManifest, err := self.dpa.Store(r, int64(len(manifest)))
if err != nil { if err != nil {
apiPutFail.Inc(1)
return nil, nil, err return nil, nil, err
} }
return key, func() { return key, func() {
@ -282,8 +307,10 @@ func (self *Api) Put(content, contentType string) (k storage.Key, wait func(), e
// to resolve basePath to content using dpa retrieve // to resolve basePath to content using dpa retrieve
// it returns a section reader, mimeType, status and an error // it returns a section reader, mimeType, status and an error
func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionReader, mimeType string, status int, err error) { func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionReader, mimeType string, status int, err error) {
apiGetCount.Inc(1)
trie, err := loadManifest(self.dpa, key, nil) trie, err := loadManifest(self.dpa, key, nil)
if err != nil { if err != nil {
apiGetNotFound.Inc(1)
status = http.StatusNotFound status = http.StatusNotFound
log.Warn(fmt.Sprintf("loadManifestTrie error: %v", err)) log.Warn(fmt.Sprintf("loadManifestTrie error: %v", err))
return return
@ -309,6 +336,7 @@ func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionRe
key = common.Hex2Bytes(entry.Hash) key = common.Hex2Bytes(entry.Hash)
status = entry.Status status = entry.Status
if status == http.StatusMultipleChoices { if status == http.StatusMultipleChoices {
apiGetHttp300.Inc(1)
return return
} else { } else {
mimeType = entry.ContentType mimeType = entry.ContentType
@ -317,6 +345,7 @@ func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionRe
} }
} else { } else {
status = http.StatusNotFound status = http.StatusNotFound
apiGetNotFound.Inc(1)
err = fmt.Errorf("manifest entry for '%s' not found", path) err = fmt.Errorf("manifest entry for '%s' not found", path)
log.Warn(fmt.Sprintf("%v", err)) log.Warn(fmt.Sprintf("%v", err))
} }
@ -324,9 +353,11 @@ func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionRe
} }
func (self *Api) Modify(key storage.Key, path, contentHash, contentType string) (storage.Key, error) { func (self *Api) Modify(key storage.Key, path, contentHash, contentType string) (storage.Key, error) {
apiModifyCount.Inc(1)
quitC := make(chan bool) quitC := make(chan bool)
trie, err := loadManifest(self.dpa, key, quitC) trie, err := loadManifest(self.dpa, key, quitC)
if err != nil { if err != nil {
apiModifyFail.Inc(1)
return nil, err return nil, err
} }
if contentHash != "" { if contentHash != "" {
@ -341,19 +372,23 @@ func (self *Api) Modify(key storage.Key, path, contentHash, contentType string)
} }
if err := trie.recalcAndStore(); err != nil { if err := trie.recalcAndStore(); err != nil {
apiModifyFail.Inc(1)
return nil, err return nil, err
} }
return trie.hash, nil return trie.hash, nil
} }
func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver bool) (storage.Key, string, error) { func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver bool) (storage.Key, string, error) {
apiAddFileCount.Inc(1)
uri, err := Parse("bzz:/" + mhash) uri, err := Parse("bzz:/" + mhash)
if err != nil { if err != nil {
apiAddFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
mkey, err := self.Resolve(uri) mkey, err := self.Resolve(uri)
if err != nil { if err != nil {
apiAddFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
@ -372,16 +407,19 @@ func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver
mw, err := self.NewManifestWriter(mkey, nil) mw, err := self.NewManifestWriter(mkey, nil)
if err != nil { if err != nil {
apiAddFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
fkey, err := mw.AddEntry(bytes.NewReader(content), entry) fkey, err := mw.AddEntry(bytes.NewReader(content), entry)
if err != nil { if err != nil {
apiAddFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
newMkey, err := mw.Store() newMkey, err := mw.Store()
if err != nil { if err != nil {
apiAddFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
@ -391,13 +429,16 @@ func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver
} }
func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (string, error) { func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (string, error) {
apiRmFileCount.Inc(1)
uri, err := Parse("bzz:/" + mhash) uri, err := Parse("bzz:/" + mhash)
if err != nil { if err != nil {
apiRmFileFail.Inc(1)
return "", err return "", err
} }
mkey, err := self.Resolve(uri) mkey, err := self.Resolve(uri)
if err != nil { if err != nil {
apiRmFileFail.Inc(1)
return "", err return "", err
} }
@ -408,16 +449,19 @@ func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (strin
mw, err := self.NewManifestWriter(mkey, nil) mw, err := self.NewManifestWriter(mkey, nil)
if err != nil { if err != nil {
apiRmFileFail.Inc(1)
return "", err return "", err
} }
err = mw.RemoveEntry(filepath.Join(path, fname)) err = mw.RemoveEntry(filepath.Join(path, fname))
if err != nil { if err != nil {
apiRmFileFail.Inc(1)
return "", err return "", err
} }
newMkey, err := mw.Store() newMkey, err := mw.Store()
if err != nil { if err != nil {
apiRmFileFail.Inc(1)
return "", err return "", err
} }
@ -426,6 +470,7 @@ func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (strin
} }
func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, content []byte, oldKey storage.Key, offset int64, addSize int64, nameresolver bool) (storage.Key, string, error) { func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, content []byte, oldKey storage.Key, offset int64, addSize int64, nameresolver bool) (storage.Key, string, error) {
apiAppendFileCount.Inc(1)
buffSize := offset + addSize buffSize := offset + addSize
if buffSize < existingSize { if buffSize < existingSize {
@ -454,10 +499,12 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
uri, err := Parse("bzz:/" + mhash) uri, err := Parse("bzz:/" + mhash)
if err != nil { if err != nil {
apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
mkey, err := self.Resolve(uri) mkey, err := self.Resolve(uri)
if err != nil { if err != nil {
apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
@ -468,11 +515,13 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
mw, err := self.NewManifestWriter(mkey, nil) mw, err := self.NewManifestWriter(mkey, nil)
if err != nil { if err != nil {
apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
err = mw.RemoveEntry(filepath.Join(path, fname)) err = mw.RemoveEntry(filepath.Join(path, fname))
if err != nil { if err != nil {
apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
@ -486,11 +535,13 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
fkey, err := mw.AddEntry(io.Reader(combinedReader), entry) fkey, err := mw.AddEntry(io.Reader(combinedReader), entry)
if err != nil { if err != nil {
apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
newMkey, err := mw.Store() newMkey, err := mw.Store()
if err != nil { if err != nil {
apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
@ -500,6 +551,7 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
} }
func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storage.Key, manifestEntryMap map[string]*manifestTrieEntry, err error) { func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storage.Key, manifestEntryMap map[string]*manifestTrieEntry, err error) {
uri, err := Parse("bzz:/" + mhash) uri, err := Parse("bzz:/" + mhash)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View file

@ -29,12 +29,19 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
) )
//templateMap holds a mapping of an HTTP error code to a template //templateMap holds a mapping of an HTTP error code to a template
var templateMap map[int]*template.Template var templateMap map[int]*template.Template
//metrics variables
var (
htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil)
jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil)
)
//parameters needed for formatting the correct HTML page //parameters needed for formatting the correct HTML page
type ResponseParams struct { type ResponseParams struct {
Msg string Msg string
@ -132,6 +139,7 @@ func respond(w http.ResponseWriter, r *http.Request, params *ResponseParams) {
//return a HTML page //return a HTML page
func respondHtml(w http.ResponseWriter, params *ResponseParams) { func respondHtml(w http.ResponseWriter, params *ResponseParams) {
htmlCounter.Inc(1)
err := params.template.Execute(w, params) err := params.template.Execute(w, params)
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
@ -140,6 +148,7 @@ func respondHtml(w http.ResponseWriter, params *ResponseParams) {
//return JSON //return JSON
func respondJson(w http.ResponseWriter, params *ResponseParams) { func respondJson(w http.ResponseWriter, params *ResponseParams) {
jsonCounter.Inc(1)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(params) json.NewEncoder(w).Encode(params)
} }

View file

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/pborman/uuid" "github.com/pborman/uuid"
@ -50,6 +51,28 @@ type resourceResponse struct {
Update storage.Key `json:"update"` Update storage.Key `json:"update"`
} }
var (
postRawCount = metrics.NewRegisteredCounter("api.http.post.raw.count", nil)
postRawFail = metrics.NewRegisteredCounter("api.http.post.raw.fail", nil)
postFilesCount = metrics.NewRegisteredCounter("api.http.post.files.count", nil)
postFilesFail = metrics.NewRegisteredCounter("api.http.post.files.fail", nil)
deleteCount = metrics.NewRegisteredCounter("api.http.delete.count", nil)
deleteFail = metrics.NewRegisteredCounter("api.http.delete.fail", nil)
getCount = metrics.NewRegisteredCounter("api.http.get.count", nil)
getFail = metrics.NewRegisteredCounter("api.http.get.fail", nil)
getFileCount = metrics.NewRegisteredCounter("api.http.get.file.count", nil)
getFileNotFound = metrics.NewRegisteredCounter("api.http.get.file.notfound", nil)
getFileFail = metrics.NewRegisteredCounter("api.http.get.file.fail", nil)
getFilesCount = metrics.NewRegisteredCounter("api.http.get.files.count", nil)
getFilesFail = metrics.NewRegisteredCounter("api.http.get.files.fail", nil)
getListCount = metrics.NewRegisteredCounter("api.http.get.list.count", nil)
getListFail = metrics.NewRegisteredCounter("api.http.get.list.fail", nil)
requestCount = metrics.NewRegisteredCounter("http.request.count", nil)
htmlRequestCount = metrics.NewRegisteredCounter("http.request.html.count", nil)
jsonRequestCount = metrics.NewRegisteredCounter("http.request.json.count", nil)
requestTimer = metrics.NewRegisteredResettingTimer("http.request.time", nil)
)
// ServerConfig is the basic configuration needed for the HTTP server and also // ServerConfig is the basic configuration needed for the HTTP server and also
// includes CORS settings. // includes CORS settings.
type ServerConfig struct { type ServerConfig struct {
@ -98,18 +121,22 @@ type Request struct {
// HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request // HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request
// body in swarm and returns the resulting storage key as a text/plain response // body in swarm and returns the resulting storage key as a text/plain response
func (s *Server) HandlePostRaw(w http.ResponseWriter, r *Request) { func (s *Server) HandlePostRaw(w http.ResponseWriter, r *Request) {
postRawCount.Inc(1)
if r.uri.Path != "" { if r.uri.Path != "" {
postRawFail.Inc(1)
Respond(w, r, "raw POST request cannot contain a path", http.StatusBadRequest) Respond(w, r, "raw POST request cannot contain a path", http.StatusBadRequest)
return return
} }
if r.Header.Get("Content-Length") == "" { if r.Header.Get("Content-Length") == "" {
postRawFail.Inc(1)
Respond(w, r, "missing Content-Length header in request", http.StatusBadRequest) Respond(w, r, "missing Content-Length header in request", http.StatusBadRequest)
return return
} }
key, _, err := s.api.Store(r.Body, r.ContentLength) key, _, err := s.api.Store(r.Body, r.ContentLength)
if err != nil { if err != nil {
postRawFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) Respond(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
@ -126,8 +153,10 @@ func (s *Server) HandlePostRaw(w http.ResponseWriter, r *Request) {
// existing manifest or to a new manifest under <path> and returns the // existing manifest or to a new manifest under <path> and returns the
// resulting manifest hash as a text/plain response // resulting manifest hash as a text/plain response
func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) { func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) {
postFilesCount.Inc(1)
contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil { if err != nil {
postFilesFail.Inc(1)
Respond(w, r, err.Error(), http.StatusBadRequest) Respond(w, r, err.Error(), http.StatusBadRequest)
return return
} }
@ -136,12 +165,14 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) {
if r.uri.Addr != "" { if r.uri.Addr != "" {
key, err = s.api.Resolve(r.uri) key, err = s.api.Resolve(r.uri)
if err != nil { if err != nil {
postFilesFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError)
return return
} }
} else { } else {
key, err = s.api.NewManifest() key, err = s.api.NewManifest()
if err != nil { if err != nil {
postFilesFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) Respond(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
@ -161,6 +192,7 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) {
} }
}) })
if err != nil { if err != nil {
postFilesFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError) Respond(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError)
return return
} }
@ -279,8 +311,10 @@ func (s *Server) handleDirectUpload(req *Request, mw *api.ManifestWriter) error
// <path> from <manifest> and returns the resulting manifest hash as a // <path> from <manifest> and returns the resulting manifest hash as a
// text/plain response // text/plain response
func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) {
deleteCount.Inc(1)
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.uri)
if err != nil { if err != nil {
deleteFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError)
return return
} }
@ -290,6 +324,7 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) {
return mw.RemoveEntry(r.uri.Path) return mw.RemoveEntry(r.uri.Path)
}) })
if err != nil { if err != nil {
deleteFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot update manifest: %s", err), http.StatusInternalServerError) Respond(w, r, fmt.Sprintf("cannot update manifest: %s", err), http.StatusInternalServerError)
return return
} }
@ -435,8 +470,10 @@ func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supEr
// - bzz-hash://<key> and responds with the hash of the content stored // - bzz-hash://<key> and responds with the hash of the content stored
// at the given storage key as a text/plain response // at the given storage key as a text/plain response
func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
getCount.Inc(1)
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.uri)
if err != nil { if err != nil {
getFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError)
return return
} }
@ -446,6 +483,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
if r.uri.Path != "" { if r.uri.Path != "" {
walker, err := s.api.NewManifestWalker(key, nil) walker, err := s.api.NewManifestWalker(key, nil)
if err != nil { if err != nil {
getFail.Inc(1)
Respond(w, r, fmt.Sprintf("%s is not a manifest", key), http.StatusBadRequest) Respond(w, r, fmt.Sprintf("%s is not a manifest", key), http.StatusBadRequest)
return return
} }
@ -474,6 +512,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
return api.SkipManifest return api.SkipManifest
}) })
if entry == nil { if entry == nil {
getFail.Inc(1)
Respond(w, r, fmt.Sprintf("manifest entry could not be loaded"), http.StatusNotFound) Respond(w, r, fmt.Sprintf("manifest entry could not be loaded"), http.StatusNotFound)
return return
} }
@ -483,12 +522,13 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
// check the root chunk exists by retrieving the file's size // check the root chunk exists by retrieving the file's size
reader := s.api.Retrieve(key) reader := s.api.Retrieve(key)
if _, err := reader.Size(nil); err != nil { if _, err := reader.Size(nil); err != nil {
getFail.Inc(1)
Respond(w, r, fmt.Sprintf("root chunk not found %s: %s", key, err), http.StatusNotFound) Respond(w, r, fmt.Sprintf("root chunk not found %s: %s", key, err), http.StatusNotFound)
return return
} }
switch { switch {
case r.uri.Raw(): case r.uri.Raw() || r.uri.DeprecatedRaw():
// allow the request to overwrite the content type using a query // allow the request to overwrite the content type using a query
// parameter // parameter
contentType := "application/octet-stream" contentType := "application/octet-stream"
@ -508,19 +548,23 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
// header of "application/x-tar" and returns a tar stream of all files // header of "application/x-tar" and returns a tar stream of all files
// contained in the manifest // contained in the manifest
func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) { func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) {
getFilesCount.Inc(1)
if r.uri.Path != "" { if r.uri.Path != "" {
getFilesFail.Inc(1)
Respond(w, r, "files request cannot contain a path", http.StatusBadRequest) Respond(w, r, "files request cannot contain a path", http.StatusBadRequest)
return return
} }
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.uri)
if err != nil { if err != nil {
getFilesFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError)
return return
} }
walker, err := s.api.NewManifestWalker(key, nil) walker, err := s.api.NewManifestWalker(key, nil)
if err != nil { if err != nil {
getFilesFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) Respond(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
@ -568,6 +612,7 @@ func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) {
return nil return nil
}) })
if err != nil { if err != nil {
getFilesFail.Inc(1)
log.Error(fmt.Sprintf("error generating tar stream: %s", err)) log.Error(fmt.Sprintf("error generating tar stream: %s", err))
} }
} }
@ -576,6 +621,7 @@ func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) {
// a list of all files contained in <manifest> under <path> grouped into // a list of all files contained in <manifest> under <path> grouped into
// common prefixes using "/" as a delimiter // common prefixes using "/" as a delimiter
func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) { func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
getListCount.Inc(1)
// ensure the root path has a trailing slash so that relative URLs work // ensure the root path has a trailing slash so that relative URLs work
if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently)
@ -584,6 +630,7 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.uri)
if err != nil { if err != nil {
getListFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError)
return return
} }
@ -591,6 +638,7 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
list, err := s.getManifestList(key, r.uri.Path) list, err := s.getManifestList(key, r.uri.Path)
if err != nil { if err != nil {
getListFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) Respond(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
@ -608,6 +656,7 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
List: &list, List: &list,
}) })
if err != nil { if err != nil {
getListFail.Inc(1)
log.Error(fmt.Sprintf("error rendering list HTML: %s", err)) log.Error(fmt.Sprintf("error rendering list HTML: %s", err))
} }
return return
@ -676,6 +725,7 @@ func (s *Server) getManifestList(key storage.Key, prefix string) (list api.Manif
// HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds
// with the content of the file at <path> from the given <manifest> // with the content of the file at <path> from the given <manifest>
func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
getFileCount.Inc(1)
// ensure the root path has a trailing slash so that relative URLs work // ensure the root path has a trailing slash so that relative URLs work
if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently)
@ -684,6 +734,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.uri)
if err != nil { if err != nil {
getFileFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError)
return return
} }
@ -699,8 +750,10 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
} }
switch status { switch status {
case http.StatusNotFound: case http.StatusNotFound:
getFileNotFound.Inc(1)
Respond(w, r, err.Error(), http.StatusNotFound) Respond(w, r, err.Error(), http.StatusNotFound)
default: default:
getFileFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) Respond(w, r, err.Error(), http.StatusInternalServerError)
} }
return return
@ -712,6 +765,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
list, err := s.getManifestList(key, r.uri.Path) list, err := s.getManifestList(key, r.uri.Path)
if err != nil { if err != nil {
getFileFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) Respond(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
@ -724,6 +778,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
// check the root chunk exists by retrieving the file's size // check the root chunk exists by retrieving the file's size
if _, err := reader.Size(nil); err != nil { if _, err := reader.Size(nil); err != nil {
getFileNotFound.Inc(1)
Respond(w, r, fmt.Sprintf("file not found %s: %s", r.uri, err), http.StatusNotFound) Respond(w, r, fmt.Sprintf("file not found %s: %s", r.uri, err), http.StatusNotFound)
return return
} }
@ -735,6 +790,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
req := &Request{Request: *r, ruid: uuid.New()[:8]} req := &Request{Request: *r, ruid: uuid.New()[:8]}
requestCount.Inc(1)
log.Info("serve request", "ruid", req.ruid, "method", r.Method, "url", r.RequestURI) log.Info("serve request", "ruid", req.ruid, "method", r.Method, "url", r.RequestURI)
uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/"))

View file

@ -47,7 +47,6 @@ func externalUnmount(mountPoint string) error {
} }
func addFileToSwarm(sf *SwarmFile, content []byte, size int) error { func addFileToSwarm(sf *SwarmFile, content []byte, size int) error {
fkey, mhash, err := sf.mountInfo.swarmApi.AddFile(sf.mountInfo.LatestManifest, sf.path, sf.name, content, true) fkey, mhash, err := sf.mountInfo.swarmApi.AddFile(sf.mountInfo.LatestManifest, sf.path, sf.name, content, true)
if err != nil { if err != nil {
return err return err
@ -64,11 +63,9 @@ func addFileToSwarm(sf *SwarmFile, content []byte, size int) error {
log.Info("Added new file:", "fname", sf.name, "New Manifest hash", mhash) log.Info("Added new file:", "fname", sf.name, "New Manifest hash", mhash)
return nil return nil
} }
func removeFileFromSwarm(sf *SwarmFile) error { func removeFileFromSwarm(sf *SwarmFile) error {
mkey, err := sf.mountInfo.swarmApi.RemoveFile(sf.mountInfo.LatestManifest, sf.path, sf.name, true) mkey, err := sf.mountInfo.swarmApi.RemoveFile(sf.mountInfo.LatestManifest, sf.path, sf.name, true)
if err != nil { if err != nil {
return err return err
@ -83,7 +80,6 @@ func removeFileFromSwarm(sf *SwarmFile) error {
} }
func removeDirectoryFromSwarm(sd *SwarmDir) error { func removeDirectoryFromSwarm(sd *SwarmDir) error {
if len(sd.directories) == 0 && len(sd.files) == 0 { if len(sd.directories) == 0 && len(sd.files) == 0 {
return nil return nil
} }
@ -103,11 +99,9 @@ func removeDirectoryFromSwarm(sd *SwarmDir) error {
} }
return nil return nil
} }
func appendToExistingFileInSwarm(sf *SwarmFile, content []byte, offset int64, length int64) error { func appendToExistingFileInSwarm(sf *SwarmFile, content []byte, offset int64, length int64) error {
fkey, mhash, err := sf.mountInfo.swarmApi.AppendFile(sf.mountInfo.LatestManifest, sf.path, sf.name, sf.fileSize, content, sf.key, offset, length, true) fkey, mhash, err := sf.mountInfo.swarmApi.AppendFile(sf.mountInfo.LatestManifest, sf.path, sf.name, sf.fileSize, content, sf.key, offset, length, true)
if err != nil { if err != nil {
return err return err
@ -124,5 +118,4 @@ func appendToExistingFileInSwarm(sf *SwarmFile, content []byte, offset int64, le
log.Info("Appended file:", "fname", sf.name, "New Manifest hash", mhash) log.Info("Appended file:", "fname", sf.name, "New Manifest hash", mhash)
return nil return nil
} }

91
swarm/metrics/flags.go Normal file
View file

@ -0,0 +1,91 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package metrics
import (
"time"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/log"
gethmetrics "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/metrics/influxdb"
"gopkg.in/urfave/cli.v1"
)
var (
metricsEnableInfluxDBExportFlag = cli.BoolFlag{
Name: "metrics.influxdb.export",
Usage: "Enable metrics export/push to an external InfluxDB database",
}
metricsInfluxDBEndpointFlag = cli.StringFlag{
Name: "metrics.influxdb.endpoint",
Usage: "Metrics InfluxDB endpoint",
Value: "http://127.0.0.1:8086",
}
metricsInfluxDBDatabaseFlag = cli.StringFlag{
Name: "metrics.influxdb.database",
Usage: "Metrics InfluxDB database",
Value: "metrics",
}
metricsInfluxDBUsernameFlag = cli.StringFlag{
Name: "metrics.influxdb.username",
Usage: "Metrics InfluxDB username",
Value: "",
}
metricsInfluxDBPasswordFlag = cli.StringFlag{
Name: "metrics.influxdb.password",
Usage: "Metrics InfluxDB password",
Value: "",
}
// The `host` tag is part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB.
// It is used so that we can group all nodes and average a measurement across all of them, but also so
// that we can select a specific node and inspect its measurements.
// https://docs.influxdata.com/influxdb/v1.4/concepts/key_concepts/#tag-key
metricsInfluxDBHostTagFlag = cli.StringFlag{
Name: "metrics.influxdb.host.tag",
Usage: "Metrics InfluxDB `host` tag attached to all measurements",
Value: "localhost",
}
)
// Flags holds all command-line flags required for metrics collection.
var Flags = []cli.Flag{
utils.MetricsEnabledFlag,
metricsEnableInfluxDBExportFlag,
metricsInfluxDBEndpointFlag, metricsInfluxDBDatabaseFlag, metricsInfluxDBUsernameFlag, metricsInfluxDBPasswordFlag, metricsInfluxDBHostTagFlag,
}
func Setup(ctx *cli.Context) {
if gethmetrics.Enabled {
log.Info("Enabling swarm metrics collection")
var (
enableExport = ctx.GlobalBool(metricsEnableInfluxDBExportFlag.Name)
endpoint = ctx.GlobalString(metricsInfluxDBEndpointFlag.Name)
database = ctx.GlobalString(metricsInfluxDBDatabaseFlag.Name)
username = ctx.GlobalString(metricsInfluxDBUsernameFlag.Name)
password = ctx.GlobalString(metricsInfluxDBPasswordFlag.Name)
hosttag = ctx.GlobalString(metricsInfluxDBHostTagFlag.Name)
)
if enableExport {
log.Info("Enabling swarm metrics export to InfluxDB")
go influxdb.InfluxDBWithTags(gethmetrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "swarm.", map[string]string{
"host": hosttag,
})
}
}
}

View file

@ -26,12 +26,26 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/protocols" "github.com/ethereum/go-ethereum/p2p/protocols"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
//metrics variables
var (
storeRequestMsgCounter = metrics.NewRegisteredCounter("network.protocol.msg.storerequest.count", nil)
retrieveRequestMsgCounter = metrics.NewRegisteredCounter("network.protocol.msg.retrieverequest.count", nil)
peersMsgCounter = metrics.NewRegisteredCounter("network.protocol.msg.peers.count", nil)
syncRequestMsgCounter = metrics.NewRegisteredCounter("network.protocol.msg.syncrequest.count", nil)
unsyncedKeysMsgCounter = metrics.NewRegisteredCounter("network.protocol.msg.unsyncedkeys.count", nil)
deliverRequestMsgCounter = metrics.NewRegisteredCounter("network.protocol.msg.deliverrequest.count", nil)
paymentMsgCounter = metrics.NewRegisteredCounter("network.protocol.msg.payment.count", nil)
invalidMsgCounter = metrics.NewRegisteredCounter("network.protocol.msg.invalid.count", nil)
handleStatusMsgCounter = metrics.NewRegisteredCounter("network.protocol.msg.handlestatus.count", nil)
)
const ( const (
// NetworkID swarm network id // NetworkID swarm network id
NetworkID = 322 // BZZ in l33t NetworkID = 322 // BZZ in l33t

View file

@ -22,6 +22,8 @@ import (
"io" "io"
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/metrics"
) )
/* /*
@ -62,6 +64,11 @@ var (
errOperationTimedOut = errors.New("operation timed out") errOperationTimedOut = errors.New("operation timed out")
) )
//metrics variables
var (
newChunkCounter = metrics.NewRegisteredCounter("storage.chunks.new", nil)
)
type TreeChunker struct { type TreeChunker struct {
branches int64 branches int64
hashFunc SwarmHasher hashFunc SwarmHasher
@ -279,6 +286,13 @@ func (self *TreeChunker) hashChunk(hasher SwarmHash, job *hashJob, chunkC chan *
job.parentWg.Done() job.parentWg.Done()
if chunkC != nil { if chunkC != nil {
//NOTE: this increases the chunk count even if the local node already has this chunk;
//on file upload the node will increase this counter even if the same file has already been uploaded
//So it should be evaluated whether it is worth keeping this counter
//and/or actually better track when the chunk is Put to the local database
//(which may question the need for disambiguation when a completely new chunk has been created
//and/or a chunk is being put to the local DB; for chunk tracking it may be worth distinguishing
newChunkCounter.Inc(1)
chunkC <- newChunk chunkC <- newChunk
storeWg.Add(1) storeWg.Add(1)
go func() { go func() {

View file

@ -33,12 +33,19 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/swarm/storage/mock" "github.com/ethereum/go-ethereum/swarm/storage/mock"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/opt"
) )
//metrics variables
var (
gcCounter = metrics.NewRegisteredCounter("storage.db.dbstore.gc.count", nil)
dbStoreDeleteCounter = metrics.NewRegisteredCounter("storage.db.dbstore.rm.count", nil)
)
const ( const (
defaultDbCapacity = 5000000 defaultDbCapacity = 5000000
defaultRadius = 0 // not yet used defaultRadius = 0 // not yet used
@ -328,6 +335,7 @@ func (s *DbStore) collectGarbage(ratio float32) {
// actual gc // actual gc
for i := 0; i < gcnt; i++ { for i := 0; i < gcnt; i++ {
if s.gcArray[i].value <= cutval { if s.gcArray[i].value <= cutval {
gcCounter.Inc(1)
s.delete(s.gcArray[i].idx, s.gcArray[i].idxKey, s.po(Key(s.gcPos[1:]))) s.delete(s.gcArray[i].idx, s.gcArray[i].idxKey, s.po(Key(s.gcPos[1:])))
} }
} }
@ -507,6 +515,7 @@ func (s *DbStore) delete(idx uint64, idxKey []byte, po uint8) {
batch := new(leveldb.Batch) batch := new(leveldb.Batch)
batch.Delete(idxKey) batch.Delete(idxKey)
batch.Delete(getDataKey(idx, po)) batch.Delete(getDataKey(idx, po))
dbStoreDeleteCounter.Inc(1)
s.entryCnt-- s.entryCnt--
s.bucketCnt[po]-- s.bucketCnt[po]--
cntKey := make([]byte, 2) cntKey := make([]byte, 2)

View file

@ -21,9 +21,14 @@ import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/storage/mock" "github.com/ethereum/go-ethereum/swarm/storage/mock"
) )
var (
dbStorePutCounter = metrics.NewRegisteredCounter("storage.db.dbstore.put.count", nil)
)
type StoreParams struct { type StoreParams struct {
ChunkDbPath string ChunkDbPath string
DbCapacity uint64 DbCapacity uint64
@ -85,6 +90,10 @@ func NewTestLocalStoreForAddr(path string, basekey []byte) (*LocalStore, error)
return localStore, nil return localStore, nil
} }
func (self *LocalStore) CacheCounter() uint64 {
return uint64(self.memStore.(*MemStore).Counter())
}
// LocalStore is itself a chunk store // LocalStore is itself a chunk store
// unsafe, in that the data is not integrity checked // unsafe, in that the data is not integrity checked
func (self *LocalStore) Put(chunk *Chunk) { func (self *LocalStore) Put(chunk *Chunk) {
@ -95,6 +104,8 @@ func (self *LocalStore) Put(chunk *Chunk) {
Size: chunk.Size, Size: chunk.Size,
dbStored: chunk.dbStored, dbStored: chunk.dbStored,
} }
dbStorePutCounter.Inc(1)
self.memStore.Put(c) self.memStore.Put(c)
self.DbStore.Put(c) self.DbStore.Put(c)
} }

View file

@ -23,6 +23,13 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
)
//metrics variables
var (
memstorePutCounter = metrics.NewRegisteredCounter("storage.db.memstore.put.count", nil)
memstoreRemoveCounter = metrics.NewRegisteredCounter("storage.db.memstore.rm.count", nil)
) )
const ( const (
@ -130,6 +137,10 @@ func (s *MemStore) setCapacity(c uint) {
s.capacity = c s.capacity = c
} }
func (s *MemStore) Counter() uint {
return s.entryCnt
}
// entry (not its copy) is going to be in MemStore // entry (not its copy) is going to be in MemStore
func (s *MemStore) Put(entry *Chunk) { func (s *MemStore) Put(entry *Chunk) {
if s.capacity == 0 { if s.capacity == 0 {
@ -145,6 +156,8 @@ func (s *MemStore) Put(entry *Chunk) {
s.accessCnt++ s.accessCnt++
memstorePutCounter.Inc(1)
node := s.memtree node := s.memtree
bitpos := uint(0) bitpos := uint(0)
for node.entry == nil { for node.entry == nil {
@ -285,6 +298,7 @@ func (s *MemStore) removeOldest() {
log.Trace(fmt.Sprintf("Memstore Clean: Chunk %v saved to DBStore. Ready to clear from mem.", node.entry.Key.Log())) log.Trace(fmt.Sprintf("Memstore Clean: Chunk %v saved to DBStore. Ready to clear from mem.", node.entry.Key.Log()))
if node.entry.ReqC == nil { if node.entry.ReqC == nil {
memstoreRemoveCounter.Inc(1)
node.entry = nil node.entry = nil
s.entryCnt-- s.entryCnt--
} else { } else {

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
@ -50,6 +51,15 @@ import (
"github.com/ethereum/go-ethereum/swarm/storage/mock" "github.com/ethereum/go-ethereum/swarm/storage/mock"
) )
var (
startTime time.Time
updateGaugesPeriod = 5 * time.Second
startCounter = metrics.NewRegisteredCounter("stack,start", nil)
stopCounter = metrics.NewRegisteredCounter("stack,stop", nil)
uptimeGauge = metrics.NewRegisteredGauge("stack.uptime", nil)
cacheSizeGauge = metrics.NewRegisteredGauge("storage.db.cache.size", nil)
)
// the swarm stack // the swarm stack
type Swarm struct { type Swarm struct {
config *api.Config // swarm configuration config *api.Config // swarm configuration
@ -309,11 +319,11 @@ Start is called when the stack is started
*/ */
// implements the node.Service interface // implements the node.Service interface
func (self *Swarm) Start(srv *p2p.Server) error { func (self *Swarm) Start(srv *p2p.Server) error {
startTime = time.Now()
// update uaddr to correct enode // update uaddr to correct enode
newaddr := self.bzz.UpdateLocalAddr([]byte(srv.Self().String())) newaddr := self.bzz.UpdateLocalAddr([]byte(srv.Self().String()))
log.Warn("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%s", newaddr.UAddr)) log.Warn("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%s", newaddr.UAddr))
// set chequebook // set chequebook
if self.config.SwapEnabled { if self.config.SwapEnabled {
ctx := context.Background() // The initial setup has no deadline. ctx := context.Background() // The initial setup has no deadline.
@ -358,9 +368,27 @@ func (self *Swarm) Start(srv *p2p.Server) error {
log.Debug(fmt.Sprintf("Swarm http proxy started with corsdomain: %v", self.config.Cors)) log.Debug(fmt.Sprintf("Swarm http proxy started with corsdomain: %v", self.config.Cors))
} }
self.periodicallyUpdateGauges()
startCounter.Inc(1)
return nil return nil
} }
func (self *Swarm) periodicallyUpdateGauges() {
ticker := time.NewTicker(updateGaugesPeriod)
go func() {
for range ticker.C {
self.updateGauges()
}
}()
}
func (self *Swarm) updateGauges() {
cacheSizeGauge.Update(int64(self.lstore.CacheCounter()))
uptimeGauge.Update(time.Since(startTime).Nanoseconds())
}
// implements the node.Service interface // implements the node.Service interface
// stops all component services. // stops all component services.
func (self *Swarm) Stop() error { func (self *Swarm) Stop() error {
@ -377,6 +405,7 @@ func (self *Swarm) Stop() error {
self.lstore.DbStore.Close() self.lstore.DbStore.Close()
} }
self.sfs.Stop() self.sfs.Stop()
stopCounter.Inc(1)
return self.bzz.Stop() return self.bzz.Stop()
} }

View file

@ -24,7 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/rcrowley/go-metrics" "github.com/ethereum/go-ethereum/metrics"
) )
var ( var (

20
vendor/github.com/influxdata/influxdb/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013-2016 Errplane Inc.
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.

Some files were not shown because too many files have changed in this diff Show more